From 9965e7900ca88fac91abededfcb4cc7ce88a2d0f Mon Sep 17 00:00:00 2001 From: oliexdev Date: Sat, 19 Sep 2020 14:11:37 +0200 Subject: [PATCH 1/8] reformatted layout --- android_app/app/build.gradle | 6 +- .../gui/overview/MeasurementAdapter.java | 104 ++++++++++++++++ .../gui/overview/OverviewAdapter.java | 112 ++++++++++++++++++ .../gui/overview/OverviewFragment.java | 25 +++- .../src/main/res/drawable/ic_expand_more.xml | 10 ++ .../src/main/res/layout/fragment_overview.xml | 14 +-- .../src/main/res/layout/item_measurement.xml | 30 +++++ .../app/src/main/res/layout/item_overview.xml | 64 ++++++++++ 8 files changed, 345 insertions(+), 20 deletions(-) create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/overview/MeasurementAdapter.java create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java create mode 100644 android_app/app/src/main/res/drawable/ic_expand_more.xml create mode 100644 android_app/app/src/main/res/layout/item_measurement.xml create mode 100644 android_app/app/src/main/res/layout/item_overview.xml diff --git a/android_app/app/build.gradle b/android_app/app/build.gradle index 2f648e6f..46e53312 100644 --- a/android_app/app/build.gradle +++ b/android_app/app/build.gradle @@ -128,14 +128,14 @@ android { } dependencies { - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' - 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' implementation 'androidx.recyclerview:recyclerview:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.google.android:flexbox:0.3.2' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.navigation:navigation-fragment:2.3.0' implementation 'androidx.navigation:navigation-ui:2.3.0' diff --git a/android_app/app/src/main/java/com/health/openscale/gui/overview/MeasurementAdapter.java b/android_app/app/src/main/java/com/health/openscale/gui/overview/MeasurementAdapter.java new file mode 100644 index 00000000..5c3e2204 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/overview/MeasurementAdapter.java @@ -0,0 +1,104 @@ +package com.health.openscale.gui.overview; + +import android.content.Context; +import android.graphics.drawable.GradientDrawable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.health.openscale.R; +import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.gui.measurement.FloatMeasurementView; +import com.health.openscale.gui.measurement.MeasurementView; +import com.health.openscale.gui.measurement.WeightMeasurementView; +import com.health.openscale.gui.utils.ColorUtil; + +import java.util.ArrayList; +import java.util.List; + +class MeasurementAdapter extends RecyclerView.Adapter { + private Context context; + private ScaleMeasurement scaleMeasurement; + private ScaleMeasurement prevScaleMeasurement; + private List measurementViewList; + + public MeasurementAdapter(Context aContext, ScaleMeasurement scaleMeasurement, ScaleMeasurement prevScaleMeasurement) { + context = aContext; + this.scaleMeasurement = scaleMeasurement; + this.prevScaleMeasurement = prevScaleMeasurement; + measurementViewList = new ArrayList<>(); + List measurementDefaultViewList = MeasurementView.getMeasurementList(context, MeasurementView.DateTimeOrder.LAST); + + for (MeasurementView measurementView : measurementDefaultViewList) { + if (measurementView instanceof FloatMeasurementView && measurementView.isVisible() && !(measurementView instanceof WeightMeasurementView)) { + measurementViewList.add((FloatMeasurementView)measurementView); + } + } + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_measurement, parent, false); + + MeasurementAdapter.ViewHolder viewHolder = new MeasurementAdapter.ViewHolder(view); + + return viewHolder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + FloatMeasurementView measurementView = measurementViewList.get(position); + + measurementView.loadFrom(scaleMeasurement, prevScaleMeasurement); + + GradientDrawable iconViewBackground = new GradientDrawable(); + iconViewBackground.setColor(((FloatMeasurementView) measurementView).getColor()); + iconViewBackground.setShape(GradientDrawable.OVAL); + iconViewBackground.setGradientRadius(holder.iconView.getWidth()); + holder.iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + holder.iconView.setColorFilter(ColorUtil.COLOR_BLACK); + holder.iconView.setBackground(iconViewBackground); + holder.iconView.setImageDrawable(measurementView.getIcon()); + + SpannableStringBuilder value = new SpannableStringBuilder(); + value.append("◆ "); + value.setSpan(new ForegroundColorSpan(measurementView.getIndicatorColor()), 0, 1, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + value.append(measurementView.getValueAsString(true)); + measurementView.appendDiffValue(value, true); + + holder.valueView.setText(value); + } + + @Override + public long getItemId(int position) { + return measurementViewList.get(position).getId(); + } + + @Override + public int getItemCount() { + return measurementViewList.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + TextView valueView; + ImageView iconView; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + + valueView = itemView.findViewById(R.id.valueView); + iconView = itemView.findViewById(R.id.iconView); + } + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java new file mode 100644 index 00000000..c52ad573 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java @@ -0,0 +1,112 @@ +package com.health.openscale.gui.overview; + +import android.content.Context; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.flexbox.AlignItems; +import com.google.android.flexbox.FlexDirection; +import com.google.android.flexbox.FlexboxLayoutManager; +import com.google.android.flexbox.JustifyContent; +import com.health.openscale.R; +import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.gui.measurement.WeightMeasurementView; + +import java.text.DateFormat; +import java.util.List; + +class OverviewAdapter extends RecyclerView.Adapter { + private Context context; + private List scaleMeasurementList; + + public OverviewAdapter(Context aContext, List scaleMeasurementList) { + this.context = aContext; + this.scaleMeasurementList = scaleMeasurementList; + } + + @Override + public OverviewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_overview, parent, false); + + ViewHolder viewHolder = new ViewHolder(view); + + return viewHolder; + } + + @Override + public void onBindViewHolder(@NonNull OverviewAdapter.ViewHolder holder, int position) { + ScaleMeasurement scaleMeasurement = scaleMeasurementList.get(position); + ScaleMeasurement prevScaleMeasurement = scaleMeasurementList.get(position-1); + + holder.expandMoreView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (holder.measurementRecyclerView.getVisibility() == View.GONE) { + holder.measurementRecyclerView.setVisibility(View.VISIBLE); + } else { + holder.measurementRecyclerView.setVisibility(View.GONE); + } + } + }); + + FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(context); + layoutManager.setFlexDirection(FlexDirection.ROW); + layoutManager.setJustifyContent(JustifyContent.SPACE_AROUND); + layoutManager.setAlignItems(AlignItems.CENTER); + holder.measurementRecyclerView.setLayoutManager(layoutManager); + holder.measurementRecyclerView.setHasFixedSize(true); + holder.measurementRecyclerView.setNestedScrollingEnabled(false); + holder.measurementRecyclerView.setAdapter(new MeasurementAdapter(context, scaleMeasurement, prevScaleMeasurement)); + + WeightMeasurementView weightMeasurementView = new WeightMeasurementView(context); + weightMeasurementView.loadFrom(scaleMeasurement, prevScaleMeasurement); + SpannableStringBuilder weightValue = new SpannableStringBuilder(); + weightValue.append("◆ "); + weightValue.setSpan(new ForegroundColorSpan(weightMeasurementView.getIndicatorColor()), 0, 1, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + weightValue.append(weightMeasurementView.getValueAsString(true)); + int start = weightValue.length(); + weightMeasurementView.appendDiffValue(weightValue, true); + weightValue.setSpan(new RelativeSizeSpan(0.9f), start, weightValue.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + holder.weightView.setText(weightValue); + holder.dateView.setText(DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT).format(scaleMeasurement.getDateTime())); + } + + @Override + public long getItemId(int position) { + return scaleMeasurementList.get(position).getId(); + } + + @Override + public int getItemCount() { + return scaleMeasurementList.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + TextView dateView; + TextView weightView; + ImageView expandMoreView; + RecyclerView measurementRecyclerView; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + + dateView = itemView.findViewById(R.id.dateView); + weightView = itemView.findViewById(R.id.weightView); + expandMoreView = itemView.findViewById(R.id.expandMoreView); + measurementRecyclerView = itemView.findViewById(R.id.measurementRecyclerView); + } + } +} 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 3d683066..f783cfcc 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 @@ -30,7 +30,6 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.Spinner; -import android.widget.TableLayout; import android.widget.TextView; import android.widget.Toast; @@ -38,6 +37,8 @@ import androidx.activity.OnBackPressedCallback; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.navigation.Navigation; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.highlight.Highlight; @@ -62,6 +63,8 @@ public class OverviewFragment extends Fragment { private List lastMeasurementViews; + private RecyclerView recyclerView; + private OverviewAdapter overviewAdapter; private ChartMeasurementView chartView; private ChartActionBarView chartActionBarView; @@ -93,6 +96,12 @@ public class OverviewFragment extends Fragment { chartView.setOnChartValueSelectedListener(new onChartSelectedListener()); chartView.setProgressBar(overviewView.findViewById(R.id.progressBar)); chartView.setIsInGraphKey(false); + chartView.getLegend().setEnabled(false); + chartView.getAxisRight().setDrawLabels(false); + chartView.getAxisRight().setDrawGridLines(false); + chartView.getAxisLeft().setDrawGridLines(false); + chartView.getAxisLeft().setDrawLabels(false); + chartView.getXAxis().setDrawGridLines(false); chartActionBarView = overviewView.findViewById(R.id.chartActionBar); chartActionBarView.setIsInGraphKey(false); @@ -181,11 +190,12 @@ public class OverviewFragment extends Fragment { lastMeasurementViews = MeasurementView.getMeasurementList( getContext(), MeasurementView.DateTimeOrder.LAST); - TableLayout tableOverviewLayout = overviewView.findViewById(R.id.tableLayoutMeasurements); - - for (MeasurementView measurement : lastMeasurementViews) { - tableOverviewLayout.addView(measurement); - } + recyclerView = overviewView.findViewById(R.id.recyclerView); + LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); + layoutManager.setInitialPrefetchItemCount(5); + layoutManager.setReverseLayout(true); + layoutManager.setStackFromEnd(true); + recyclerView.setLayoutManager(layoutManager); spinUserAdapter = new ArrayAdapter<>(overviewView.getContext(), R.layout.spinner_item, new ArrayList()); spinUser.setAdapter(spinUserAdapter); @@ -259,6 +269,9 @@ public class OverviewFragment extends Fragment { markedMeasurement = scaleMeasurementList.get(0); } + overviewAdapter = new OverviewAdapter(getContext(), scaleMeasurementList); + recyclerView.setAdapter(overviewAdapter); + updateUserSelection(); updateMesurementViews(markedMeasurement); chartView.updateMeasurementList(scaleMeasurementList); diff --git a/android_app/app/src/main/res/drawable/ic_expand_more.xml b/android_app/app/src/main/res/drawable/ic_expand_more.xml new file mode 100644 index 00000000..adc215c4 --- /dev/null +++ b/android_app/app/src/main/res/drawable/ic_expand_more.xml @@ -0,0 +1,10 @@ + + + diff --git a/android_app/app/src/main/res/layout/fragment_overview.xml b/android_app/app/src/main/res/layout/fragment_overview.xml index 41c7f9e0..98bcc9c0 100644 --- a/android_app/app/src/main/res/layout/fragment_overview.xml +++ b/android_app/app/src/main/res/layout/fragment_overview.xml @@ -8,13 +8,6 @@ android:orientation="vertical" android:weightSum="100"> - - - - - + + + + + + \ No newline at end of file diff --git a/android_app/app/src/main/res/layout/item_overview.xml b/android_app/app/src/main/res/layout/item_overview.xml new file mode 100644 index 00000000..d5092739 --- /dev/null +++ b/android_app/app/src/main/res/layout/item_overview.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 10baf31d89adb06237961de65a8a4213e9d562a0 Mon Sep 17 00:00:00 2001 From: oliexdev Date: Sun, 20 Sep 2020 15:27:25 +0200 Subject: [PATCH 2/8] show comment if available --- .../gui/overview/OverviewAdapter.java | 39 +++++++++++++++---- .../gui/overview/OverviewFragment.java | 2 +- .../app/src/main/res/layout/item_overview.xml | 34 +++++++++++++--- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java index c52ad573..755a9d90 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java @@ -1,6 +1,6 @@ package com.health.openscale.gui.overview; -import android.content.Context; +import android.app.Activity; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.ForegroundColorSpan; @@ -12,6 +12,7 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.navigation.Navigation; import androidx.recyclerview.widget.RecyclerView; import com.google.android.flexbox.AlignItems; @@ -20,17 +21,18 @@ import com.google.android.flexbox.FlexboxLayoutManager; import com.google.android.flexbox.JustifyContent; import com.health.openscale.R; import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.gui.measurement.MeasurementEntryFragment; import com.health.openscale.gui.measurement.WeightMeasurementView; import java.text.DateFormat; import java.util.List; class OverviewAdapter extends RecyclerView.Adapter { - private Context context; + private Activity activity; private List scaleMeasurementList; - public OverviewAdapter(Context aContext, List scaleMeasurementList) { - this.context = aContext; + public OverviewAdapter(Activity activity, List scaleMeasurementList) { + this.activity = activity; this.scaleMeasurementList = scaleMeasurementList; } @@ -59,16 +61,35 @@ class OverviewAdapter extends RecyclerView.Adapter { } }); - FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(context); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OverviewFragmentDirections.ActionNavOverviewToNavDataentry action = OverviewFragmentDirections.actionNavOverviewToNavDataentry(); + action.setMeasurementId(scaleMeasurement.getId()); + action.setMode(MeasurementEntryFragment.DATA_ENTRY_MODE.VIEW); + Navigation.findNavController(activity, R.id.nav_host_fragment).navigate(action); + } + }); + + if (!scaleMeasurement.getComment().isEmpty()) { + holder.commentTextView.setVisibility(View.VISIBLE); + holder.commentIconView.setVisibility(View.VISIBLE); + holder.commentTextView.setText(scaleMeasurement.getComment()); + } else { + holder.commentTextView.setVisibility(View.GONE); + holder.commentIconView.setVisibility(View.GONE); + } + + FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(activity); layoutManager.setFlexDirection(FlexDirection.ROW); layoutManager.setJustifyContent(JustifyContent.SPACE_AROUND); layoutManager.setAlignItems(AlignItems.CENTER); holder.measurementRecyclerView.setLayoutManager(layoutManager); holder.measurementRecyclerView.setHasFixedSize(true); holder.measurementRecyclerView.setNestedScrollingEnabled(false); - holder.measurementRecyclerView.setAdapter(new MeasurementAdapter(context, scaleMeasurement, prevScaleMeasurement)); + holder.measurementRecyclerView.setAdapter(new MeasurementAdapter(activity, scaleMeasurement, prevScaleMeasurement)); - WeightMeasurementView weightMeasurementView = new WeightMeasurementView(context); + WeightMeasurementView weightMeasurementView = new WeightMeasurementView(activity); weightMeasurementView.loadFrom(scaleMeasurement, prevScaleMeasurement); SpannableStringBuilder weightValue = new SpannableStringBuilder(); weightValue.append("◆ "); @@ -99,6 +120,8 @@ class OverviewAdapter extends RecyclerView.Adapter { TextView weightView; ImageView expandMoreView; RecyclerView measurementRecyclerView; + ImageView commentIconView; + TextView commentTextView; public ViewHolder(@NonNull View itemView) { super(itemView); @@ -107,6 +130,8 @@ class OverviewAdapter extends RecyclerView.Adapter { weightView = itemView.findViewById(R.id.weightView); expandMoreView = itemView.findViewById(R.id.expandMoreView); measurementRecyclerView = itemView.findViewById(R.id.measurementRecyclerView); + commentIconView = itemView.findViewById(R.id.commentIconView); + commentTextView = itemView.findViewById(R.id.commentTextView); } } } 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 f783cfcc..62bc745d 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 @@ -269,7 +269,7 @@ public class OverviewFragment extends Fragment { markedMeasurement = scaleMeasurementList.get(0); } - overviewAdapter = new OverviewAdapter(getContext(), scaleMeasurementList); + overviewAdapter = new OverviewAdapter(getActivity(), scaleMeasurementList); recyclerView.setAdapter(overviewAdapter); updateUserSelection(); diff --git a/android_app/app/src/main/res/layout/item_overview.xml b/android_app/app/src/main/res/layout/item_overview.xml index d5092739..389772a1 100644 --- a/android_app/app/src/main/res/layout/item_overview.xml +++ b/android_app/app/src/main/res/layout/item_overview.xml @@ -22,7 +22,7 @@ + app:srcCompat="@drawable/ic_expand_more" + app:tint="?attr/colorControlNormal" /> + app:layout_constraintTop_toBottomOf="@+id/weightView" > + + + + + + + From a6c85d754838e04440f9990e32749c3527020d02 Mon Sep 17 00:00:00 2001 From: oliexdev Date: Sun, 20 Sep 2020 16:44:50 +0200 Subject: [PATCH 3/8] show goal and difference weight on overview fragment --- .../gui/overview/OverviewFragment.java | 79 +++++++++++++++++++ .../src/main/res/layout/fragment_overview.xml | 45 +++++++++-- 2 files changed, 119 insertions(+), 5 deletions(-) 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 62bc745d..b099eca3 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 @@ -21,6 +21,10 @@ import android.content.SharedPreferences; import android.graphics.Color; import android.os.Bundle; import android.preference.PreferenceManager; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.RelativeSizeSpan; +import android.text.style.StyleSpan; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -47,13 +51,17 @@ import com.health.openscale.R; import com.health.openscale.core.OpenScale; import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleUser; +import com.health.openscale.core.utils.DateTimeHelpers; import com.health.openscale.gui.measurement.ChartActionBarView; import com.health.openscale.gui.measurement.ChartMeasurementView; import com.health.openscale.gui.measurement.MeasurementEntryFragment; import com.health.openscale.gui.measurement.MeasurementView; +import com.health.openscale.gui.measurement.WeightMeasurementView; import com.health.openscale.gui.utils.ColorUtil; +import java.text.DateFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; public class OverviewFragment extends Fragment { @@ -76,6 +84,10 @@ public class OverviewFragment extends Fragment { private ImageView editEntry; private ImageView deleteEntry; + private TextView differenceWeightView; + private TextView initialWeightView; + private TextView goalWeightView; + private ScaleUser currentScaleUser; private ArrayAdapter spinUserAdapter; @@ -92,6 +104,10 @@ public class OverviewFragment extends Fragment { txtTitleUser = overviewView.findViewById(R.id.txtTitleUser); + differenceWeightView = overviewView.findViewById(R.id.differenceWeightView); + initialWeightView = overviewView.findViewById(R.id.initialWeightView); + goalWeightView = overviewView.findViewById(R.id.goalWeightView); + chartView = overviewView.findViewById(R.id.chartView); chartView.setOnChartValueSelectedListener(new onChartSelectedListener()); chartView.setProgressBar(overviewView.findViewById(R.id.progressBar)); @@ -315,6 +331,69 @@ public class OverviewFragment extends Fragment { int visibility = spinUserAdapter.getCount() < 2 ? View.GONE : View.VISIBLE; txtTitleUser.setVisibility(visibility); spinUser.setVisibility(visibility); + + + WeightMeasurementView weightMeasurementView = new WeightMeasurementView(getContext()); + ScaleMeasurement initialWeightMeasurement = OpenScale.getInstance().getLastScaleMeasurement(); + initialWeightMeasurement.setWeight(initialWeightMeasurement.getWeight()); + weightMeasurementView.loadFrom(initialWeightMeasurement, null); + + SpannableStringBuilder initialWeightValue = new SpannableStringBuilder(); + initialWeightValue.append(getResources().getString(R.string.label_weight)); + initialWeightValue.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, initialWeightValue.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + initialWeightValue.append("\n"); + initialWeightValue.append(weightMeasurementView.getValueAsString(true)); + initialWeightValue.append(("\n")); + int start = initialWeightValue.length(); + initialWeightValue.append(DateFormat.getDateInstance(DateFormat.MEDIUM).format(initialWeightMeasurement.getDateTime())); + initialWeightValue.setSpan(new RelativeSizeSpan(0.8f), start, initialWeightValue.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + initialWeightView.setText(initialWeightValue); + + ScaleMeasurement goalWeightMeasurement = new ScaleMeasurement(); + goalWeightMeasurement.setWeight(currentScaleUser.getGoalWeight()); + weightMeasurementView.loadFrom(goalWeightMeasurement, null); + + SpannableStringBuilder goalWeightValue = new SpannableStringBuilder(); + goalWeightValue.append(getResources().getString(R.string.label_goal_weight)); + goalWeightValue.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, goalWeightValue.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + goalWeightValue.append("\n"); + goalWeightValue.append(weightMeasurementView.getValueAsString(true)); + goalWeightValue.append(("\n")); + start = goalWeightValue.length(); + goalWeightValue.append(DateFormat.getDateInstance(DateFormat.MEDIUM).format(currentScaleUser.getGoalDate())); + goalWeightValue.setSpan(new RelativeSizeSpan(0.8f), start, goalWeightValue.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + goalWeightView.setText(goalWeightValue); + + ScaleMeasurement differenceWeightMeasurement = new ScaleMeasurement(); + if (initialWeightMeasurement.getWeight() > goalWeightMeasurement.getWeight()) { + differenceWeightMeasurement.setWeight(initialWeightMeasurement.getWeight() -goalWeightMeasurement.getWeight()); + } else { + differenceWeightMeasurement.setWeight(goalWeightMeasurement.getWeight() - initialWeightMeasurement.getWeight()); + } + weightMeasurementView.loadFrom(differenceWeightMeasurement, null); + + Calendar initialCalendar = Calendar.getInstance(); + initialCalendar.setTime(initialWeightMeasurement.getDateTime()); + Calendar goalCalendar = Calendar.getInstance(); + goalCalendar.setTime(currentScaleUser.getGoalDate()); + int daysBetween = Math.max(0, DateTimeHelpers.daysBetween(initialCalendar, goalCalendar)); + + SpannableStringBuilder differenceWeightValue = new SpannableStringBuilder(); + differenceWeightValue.append(getResources().getString(R.string.label_weight_difference)); + differenceWeightValue.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, differenceWeightValue.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + differenceWeightValue.append("\n"); + differenceWeightValue.append(weightMeasurementView.getValueAsString(true)); + differenceWeightValue.append(("\n")); + start = differenceWeightValue.length(); + differenceWeightValue.append(daysBetween + " " + getString(R.string.label_days_left)); + differenceWeightValue.setSpan(new RelativeSizeSpan(0.8f), start, differenceWeightValue.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + differenceWeightView.setText(differenceWeightValue); } private class onChartSelectedListener implements OnChartValueSelectedListener { diff --git a/android_app/app/src/main/res/layout/fragment_overview.xml b/android_app/app/src/main/res/layout/fragment_overview.xml index 98bcc9c0..c6890c62 100644 --- a/android_app/app/src/main/res/layout/fragment_overview.xml +++ b/android_app/app/src/main/res/layout/fragment_overview.xml @@ -46,7 +46,7 @@ android:layout_height="wrap_content" android:layout_marginRight="10dp" android:scaleType="centerInside" - android:tint="#d3d3d3" + app:tint="#d3d3d3" app:srcCompat="@drawable/ic_show" /> @@ -87,7 +87,42 @@ android:id="@+id/chartView" android:layout_width="match_parent" android:layout_height="200dp" - android:layout_gravity="center" /> + android:layout_gravity="center" > + + + + + + + + + + + Date: Sat, 21 Jan 2023 18:20:48 +0100 Subject: [PATCH 4/8] add an expandable recycler view --- .../gui/measurement/MeasurementView.java | 12 +- .../gui/overview/MeasurementAdapter.java | 104 ---------------- .../gui/overview/OverviewAdapter.java | 113 +++++++++--------- .../gui/overview/OverviewFragment.java | 6 +- .../src/main/res/drawable/ic_expand_less.xml | 10 ++ .../src/main/res/layout/item_measurement.xml | 30 ----- .../app/src/main/res/layout/item_overview.xml | 54 +++------ 7 files changed, 89 insertions(+), 240 deletions(-) delete mode 100644 android_app/app/src/main/java/com/health/openscale/gui/overview/MeasurementAdapter.java create mode 100644 android_app/app/src/main/res/drawable/ic_expand_less.xml delete mode 100644 android_app/app/src/main/res/layout/item_measurement.xml diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java index cd13d5ea..fa11d0dd 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java @@ -15,6 +15,11 @@ */ package com.health.openscale.gui.measurement; +import static com.health.openscale.gui.measurement.MeasurementView.MeasurementViewMode.ADD; +import static com.health.openscale.gui.measurement.MeasurementView.MeasurementViewMode.EDIT; +import static com.health.openscale.gui.measurement.MeasurementView.MeasurementViewMode.STATISTIC; +import static com.health.openscale.gui.measurement.MeasurementView.MeasurementViewMode.VIEW; + import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; @@ -53,11 +58,6 @@ import com.health.openscale.gui.utils.ColorUtil; import java.util.ArrayList; import java.util.List; -import static com.health.openscale.gui.measurement.MeasurementView.MeasurementViewMode.ADD; -import static com.health.openscale.gui.measurement.MeasurementView.MeasurementViewMode.EDIT; -import static com.health.openscale.gui.measurement.MeasurementView.MeasurementViewMode.STATISTIC; -import static com.health.openscale.gui.measurement.MeasurementView.MeasurementViewMode.VIEW; - public abstract class MeasurementView extends TableLayout { public enum MeasurementViewMode {VIEW, EDIT, ADD, STATISTIC} @@ -207,7 +207,7 @@ public abstract class MeasurementView extends TableLayout { iconView.setImageResource(iconId); iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - iconView.setPadding(25,25,25,25); + iconView.setPadding(15,15,15,15); iconView.setColorFilter(ColorUtil.COLOR_BLACK); iconView.setBackground(iconViewBackground); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/overview/MeasurementAdapter.java b/android_app/app/src/main/java/com/health/openscale/gui/overview/MeasurementAdapter.java deleted file mode 100644 index 5c3e2204..00000000 --- a/android_app/app/src/main/java/com/health/openscale/gui/overview/MeasurementAdapter.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.health.openscale.gui.overview; - -import android.content.Context; -import android.graphics.drawable.GradientDrawable; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.ForegroundColorSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.health.openscale.R; -import com.health.openscale.core.datatypes.ScaleMeasurement; -import com.health.openscale.gui.measurement.FloatMeasurementView; -import com.health.openscale.gui.measurement.MeasurementView; -import com.health.openscale.gui.measurement.WeightMeasurementView; -import com.health.openscale.gui.utils.ColorUtil; - -import java.util.ArrayList; -import java.util.List; - -class MeasurementAdapter extends RecyclerView.Adapter { - private Context context; - private ScaleMeasurement scaleMeasurement; - private ScaleMeasurement prevScaleMeasurement; - private List measurementViewList; - - public MeasurementAdapter(Context aContext, ScaleMeasurement scaleMeasurement, ScaleMeasurement prevScaleMeasurement) { - context = aContext; - this.scaleMeasurement = scaleMeasurement; - this.prevScaleMeasurement = prevScaleMeasurement; - measurementViewList = new ArrayList<>(); - List measurementDefaultViewList = MeasurementView.getMeasurementList(context, MeasurementView.DateTimeOrder.LAST); - - for (MeasurementView measurementView : measurementDefaultViewList) { - if (measurementView instanceof FloatMeasurementView && measurementView.isVisible() && !(measurementView instanceof WeightMeasurementView)) { - measurementViewList.add((FloatMeasurementView)measurementView); - } - } - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_measurement, parent, false); - - MeasurementAdapter.ViewHolder viewHolder = new MeasurementAdapter.ViewHolder(view); - - return viewHolder; - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - FloatMeasurementView measurementView = measurementViewList.get(position); - - measurementView.loadFrom(scaleMeasurement, prevScaleMeasurement); - - GradientDrawable iconViewBackground = new GradientDrawable(); - iconViewBackground.setColor(((FloatMeasurementView) measurementView).getColor()); - iconViewBackground.setShape(GradientDrawable.OVAL); - iconViewBackground.setGradientRadius(holder.iconView.getWidth()); - holder.iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - holder.iconView.setColorFilter(ColorUtil.COLOR_BLACK); - holder.iconView.setBackground(iconViewBackground); - holder.iconView.setImageDrawable(measurementView.getIcon()); - - SpannableStringBuilder value = new SpannableStringBuilder(); - value.append("◆ "); - value.setSpan(new ForegroundColorSpan(measurementView.getIndicatorColor()), 0, 1, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - value.append(measurementView.getValueAsString(true)); - measurementView.appendDiffValue(value, true); - - holder.valueView.setText(value); - } - - @Override - public long getItemId(int position) { - return measurementViewList.get(position).getId(); - } - - @Override - public int getItemCount() { - return measurementViewList.size(); - } - - static class ViewHolder extends RecyclerView.ViewHolder { - TextView valueView; - ImageView iconView; - - public ViewHolder(@NonNull View itemView) { - super(itemView); - - valueView = itemView.findViewById(R.id.valueView); - iconView = itemView.findViewById(R.id.iconView); - } - } -} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java index 755a9d90..e0bb9cca 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java @@ -1,28 +1,27 @@ package com.health.openscale.gui.overview; import android.app.Activity; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.ForegroundColorSpan; -import android.text.style.RelativeSizeSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.TableLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; import androidx.navigation.Navigation; import androidx.recyclerview.widget.RecyclerView; +import androidx.transition.AutoTransition; +import androidx.transition.TransitionManager; -import com.google.android.flexbox.AlignItems; -import com.google.android.flexbox.FlexDirection; -import com.google.android.flexbox.FlexboxLayoutManager; -import com.google.android.flexbox.JustifyContent; import com.health.openscale.R; import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.gui.measurement.DateMeasurementView; import com.health.openscale.gui.measurement.MeasurementEntryFragment; -import com.health.openscale.gui.measurement.WeightMeasurementView; +import com.health.openscale.gui.measurement.MeasurementView; +import com.health.openscale.gui.measurement.TimeMeasurementView; +import com.health.openscale.gui.measurement.UserMeasurementView; import java.text.DateFormat; import java.util.List; @@ -30,10 +29,13 @@ import java.util.List; class OverviewAdapter extends RecyclerView.Adapter { private Activity activity; private List scaleMeasurementList; + private int maxMeasurementView; + public OverviewAdapter(Activity activity, List scaleMeasurementList) { this.activity = activity; this.scaleMeasurementList = scaleMeasurementList; + this.maxMeasurementView = 3; } @Override @@ -48,18 +50,11 @@ class OverviewAdapter extends RecyclerView.Adapter { @Override public void onBindViewHolder(@NonNull OverviewAdapter.ViewHolder holder, int position) { ScaleMeasurement scaleMeasurement = scaleMeasurementList.get(position); - ScaleMeasurement prevScaleMeasurement = scaleMeasurementList.get(position-1); + ScaleMeasurement prevScaleMeasurement = new ScaleMeasurement(); - holder.expandMoreView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (holder.measurementRecyclerView.getVisibility() == View.GONE) { - holder.measurementRecyclerView.setVisibility(View.VISIBLE); - } else { - holder.measurementRecyclerView.setVisibility(View.GONE); - } - } - }); + if (scaleMeasurementList.size() > 2) { + prevScaleMeasurement = scaleMeasurementList.get(position - 1); + } holder.itemView.setOnClickListener(new View.OnClickListener() { @Override @@ -71,38 +66,41 @@ class OverviewAdapter extends RecyclerView.Adapter { } }); - if (!scaleMeasurement.getComment().isEmpty()) { - holder.commentTextView.setVisibility(View.VISIBLE); - holder.commentIconView.setVisibility(View.VISIBLE); - holder.commentTextView.setText(scaleMeasurement.getComment()); - } else { - holder.commentTextView.setVisibility(View.GONE); - holder.commentIconView.setVisibility(View.GONE); - } + holder.expandMeasurementView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + TransitionManager.beginDelayedTransition(holder.measurementViews, new AutoTransition()); - FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(activity); - layoutManager.setFlexDirection(FlexDirection.ROW); - layoutManager.setJustifyContent(JustifyContent.SPACE_AROUND); - layoutManager.setAlignItems(AlignItems.CENTER); - holder.measurementRecyclerView.setLayoutManager(layoutManager); - holder.measurementRecyclerView.setHasFixedSize(true); - holder.measurementRecyclerView.setNestedScrollingEnabled(false); - holder.measurementRecyclerView.setAdapter(new MeasurementAdapter(activity, scaleMeasurement, prevScaleMeasurement)); + if (holder.measurementViews.getVisibility() == View.VISIBLE) { + holder.measurementViews.setVisibility(View.GONE); + holder.expandMeasurementView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_expand_more)); + } else { + holder.measurementViews.setVisibility(View.VISIBLE); + holder.expandMeasurementView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_expand_less)); + } + } + }); - WeightMeasurementView weightMeasurementView = new WeightMeasurementView(activity); - weightMeasurementView.loadFrom(scaleMeasurement, prevScaleMeasurement); - SpannableStringBuilder weightValue = new SpannableStringBuilder(); - weightValue.append("◆ "); - weightValue.setSpan(new ForegroundColorSpan(weightMeasurementView.getIndicatorColor()), 0, 1, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - weightValue.append(weightMeasurementView.getValueAsString(true)); - int start = weightValue.length(); - weightMeasurementView.appendDiffValue(weightValue, true); - weightValue.setSpan(new RelativeSizeSpan(0.9f), start, weightValue.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - holder.weightView.setText(weightValue); holder.dateView.setText(DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT).format(scaleMeasurement.getDateTime())); + + List measurementViewList = MeasurementView.getMeasurementList(activity, MeasurementView.DateTimeOrder.LAST); + + int i = 0; + for (MeasurementView measurementView : measurementViewList) { + i++; + if (measurementView instanceof DateMeasurementView || measurementView instanceof TimeMeasurementView || measurementView instanceof UserMeasurementView) { + measurementView.setVisible(false); + } + else { + measurementView.loadFrom(scaleMeasurement, prevScaleMeasurement); + + if (i <= maxMeasurementView) { + holder.measurementHighlightViews.addView(measurementView); + } else { + holder.measurementViews.addView(measurementView); + } + } + } } @Override @@ -117,21 +115,18 @@ class OverviewAdapter extends RecyclerView.Adapter { static class ViewHolder extends RecyclerView.ViewHolder { TextView dateView; - TextView weightView; - ImageView expandMoreView; - RecyclerView measurementRecyclerView; - ImageView commentIconView; - TextView commentTextView; + TableLayout measurementHighlightViews; + ImageView expandMeasurementView; + TableLayout measurementViews; public ViewHolder(@NonNull View itemView) { super(itemView); dateView = itemView.findViewById(R.id.dateView); - weightView = itemView.findViewById(R.id.weightView); - expandMoreView = itemView.findViewById(R.id.expandMoreView); - measurementRecyclerView = itemView.findViewById(R.id.measurementRecyclerView); - commentIconView = itemView.findViewById(R.id.commentIconView); - commentTextView = itemView.findViewById(R.id.commentTextView); + measurementHighlightViews = itemView.findViewById(R.id.measurementHighlightViews); + expandMeasurementView = itemView.findViewById(R.id.expandMoreView); + measurementViews = itemView.findViewById(R.id.measurementViews); + measurementViews.setVisibility(View.GONE); } } } 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 b099eca3..9cde13c6 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 @@ -309,7 +309,6 @@ public class OverviewFragment extends Fragment { } private void updateUserSelection() { - currentScaleUser = OpenScale.getInstance().getSelectedScaleUser(); spinUserAdapter.clear(); @@ -335,6 +334,11 @@ public class OverviewFragment extends Fragment { WeightMeasurementView weightMeasurementView = new WeightMeasurementView(getContext()); ScaleMeasurement initialWeightMeasurement = OpenScale.getInstance().getLastScaleMeasurement(); + + if (initialWeightMeasurement == null) { + initialWeightMeasurement = new ScaleMeasurement(); + } + initialWeightMeasurement.setWeight(initialWeightMeasurement.getWeight()); weightMeasurementView.loadFrom(initialWeightMeasurement, null); diff --git a/android_app/app/src/main/res/drawable/ic_expand_less.xml b/android_app/app/src/main/res/drawable/ic_expand_less.xml new file mode 100644 index 00000000..a55069f0 --- /dev/null +++ b/android_app/app/src/main/res/drawable/ic_expand_less.xml @@ -0,0 +1,10 @@ + + + diff --git a/android_app/app/src/main/res/layout/item_measurement.xml b/android_app/app/src/main/res/layout/item_measurement.xml deleted file mode 100644 index 71e79494..00000000 --- a/android_app/app/src/main/res/layout/item_measurement.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/android_app/app/src/main/res/layout/item_overview.xml b/android_app/app/src/main/res/layout/item_overview.xml index 389772a1..11c81597 100644 --- a/android_app/app/src/main/res/layout/item_overview.xml +++ b/android_app/app/src/main/res/layout/item_overview.xml @@ -29,57 +29,31 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + + - - - - - - - + android:layout_marginTop="5dp" + app:layout_constraintTop_toBottomOf="@id/expandMoreView"> + From 5cb0adde0abfe0e970a998b2ee98810000d61b31 Mon Sep 17 00:00:00 2001 From: oliexdev Date: Sun, 22 Jan 2023 12:55:48 +0100 Subject: [PATCH 5/8] - add measurement option to stick a measurement to the card view in the overview page - add weekday into the date time format --- .../gui/measurement/FloatMeasurementView.java | 1 + .../gui/measurement/MeasurementView.java | 14 +++++++++++-- .../measurement/MeasurementViewSettings.java | 21 +++++++++++++++++++ .../gui/overview/OverviewAdapter.java | 16 +++++++------- .../app/src/main/res/layout/item_overview.xml | 2 +- .../app/src/main/res/values/strings.xml | 1 + 6 files changed, 43 insertions(+), 12 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/FloatMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/FloatMeasurementView.java index 3aed5cdf..cb9ce151 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/FloatMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/FloatMeasurementView.java @@ -566,6 +566,7 @@ public abstract class FloatMeasurementView extends MeasurementView { @Override public void prepareExtraPreferencesScreen(PreferenceScreen screen) { + super.prepareExtraPreferencesScreen(screen); MeasurementViewSettings settings = getSettings(); CheckBoxPreference rightAxis = new CheckBoxPreference(screen.getContext()); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java index fa11d0dd..28444199 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java @@ -45,6 +45,7 @@ import android.widget.TableRow; import android.widget.TextView; import androidx.core.content.ContextCompat; +import androidx.preference.CheckBoxPreference; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; @@ -423,8 +424,17 @@ public abstract class MeasurementView extends TableLayout { } public String getPreferenceSummary() { return ""; } - public boolean hasExtraPreferences() { return false; } - public void prepareExtraPreferencesScreen(PreferenceScreen screen) { } + public boolean hasExtraPreferences() { return true; } + public void prepareExtraPreferencesScreen(PreferenceScreen screen) { + MeasurementViewSettings settings = getSettings(); + + CheckBoxPreference isSticky = new CheckBoxPreference(screen.getContext()); + isSticky.setKey(settings.getIsStickyGraphKey()); + isSticky.setTitle(R.string.label_is_sticky); + isSticky.setPersistent(true); + isSticky.setDefaultValue(settings.isSticky()); + screen.addPreference(isSticky); + } protected abstract View getInputView(); protected abstract boolean validateAndSetInput(View view); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementViewSettings.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementViewSettings.java index 608a177f..a8df3f46 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementViewSettings.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementViewSettings.java @@ -27,6 +27,7 @@ public class MeasurementViewSettings { private final String key; private static final String PREFERENCE_SUFFIX_ENABLE = "Enable"; + private static final String PREFERENCE_SUFFIX_IS_STICKY = "IsSticky"; private static final String PREFERENCE_SUFFIX_IN_OVERVIEW_GRAPH = "InOverviewGraph"; private static final String PREFERENCE_SUFFIX_ON_RIGHT_AXIS = "OnRightAxis"; private static final String PREFERENCE_SUFFIX_IN_GRAPH = "InGraph"; @@ -116,6 +117,26 @@ public class MeasurementViewSettings { return isEnabledIgnoringDependencies() && areDependenciesEnabled(); } + public boolean isSticky() { + boolean defaultValue; + switch (key) { + case WeightMeasurementView.KEY: + case WaterMeasurementView.KEY: + case MuscleMeasurementView.KEY: + case FatMeasurementView.KEY: + defaultValue = true; + break; + default: + defaultValue = false; + break; + } + return preferences.getBoolean(getIsStickyGraphKey(), defaultValue); + } + + public String getIsStickyGraphKey() { + return getPreferenceKey(PREFERENCE_SUFFIX_IS_STICKY); + } + public String getInOverviewGraphKey() { return getPreferenceKey(PREFERENCE_SUFFIX_IN_OVERVIEW_GRAPH); } diff --git a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java index e0bb9cca..373a9950 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java @@ -24,18 +24,16 @@ import com.health.openscale.gui.measurement.TimeMeasurementView; import com.health.openscale.gui.measurement.UserMeasurementView; import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.List; class OverviewAdapter extends RecyclerView.Adapter { private Activity activity; private List scaleMeasurementList; - private int maxMeasurementView; - public OverviewAdapter(Activity activity, List scaleMeasurementList) { this.activity = activity; this.scaleMeasurementList = scaleMeasurementList; - this.maxMeasurementView = 3; } @Override @@ -81,22 +79,22 @@ class OverviewAdapter extends RecyclerView.Adapter { } }); - holder.dateView.setText(DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT).format(scaleMeasurement.getDateTime())); + holder.dateView.setText(DateFormat.getDateInstance(DateFormat.MEDIUM).format(scaleMeasurement.getDateTime()) + + " (" + new SimpleDateFormat("EE").format(scaleMeasurement.getDateTime()) + ") "+ + DateFormat.getTimeInstance(DateFormat.SHORT).format(scaleMeasurement.getDateTime())); List measurementViewList = MeasurementView.getMeasurementList(activity, MeasurementView.DateTimeOrder.LAST); - int i = 0; for (MeasurementView measurementView : measurementViewList) { - i++; if (measurementView instanceof DateMeasurementView || measurementView instanceof TimeMeasurementView || measurementView instanceof UserMeasurementView) { measurementView.setVisible(false); } - else { + else if (measurementView.isVisible()) { measurementView.loadFrom(scaleMeasurement, prevScaleMeasurement); - if (i <= maxMeasurementView) { + if (measurementView.getSettings().isSticky()) { holder.measurementHighlightViews.addView(measurementView); - } else { + } else{ holder.measurementViews.addView(measurementView); } } diff --git a/android_app/app/src/main/res/layout/item_overview.xml b/android_app/app/src/main/res/layout/item_overview.xml index 11c81597..f204090a 100644 --- a/android_app/app/src/main/res/layout/item_overview.xml +++ b/android_app/app/src/main/res/layout/item_overview.xml @@ -39,7 +39,7 @@ Set default order Overwrite previous export \"%s\"? Is on right axis + Is sticky Measurement in % Estimate measurement Estimation formula From 49dca6770c356c465bcce400363a3456f7ed1673 Mon Sep 17 00:00:00 2001 From: oliexdev Date: Sun, 22 Jan 2023 15:42:48 +0100 Subject: [PATCH 6/8] refactored fragment overview to use constraint layout --- .../gui/measurement/ChartActionBarView.java | 4 + .../gui/measurement/ChartMeasurementView.java | 8 +- .../gui/measurement/FloatMeasurementView.java | 3 - .../gui/measurement/MeasurementView.java | 1 - .../gui/overview/OverviewAdapter.java | 69 +++++- .../gui/overview/OverviewFragment.java | 132 +---------- .../preferences/MeasurementPreferences.java | 7 - .../src/main/res/layout/fragment_overview.xml | 206 +++++++----------- .../app/src/main/res/layout/item_overview.xml | 42 +++- .../app/src/main/res/layout/spinner_item.xml | 8 +- 10 files changed, 206 insertions(+), 274 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartActionBarView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartActionBarView.java index 07162eaf..2a027f52 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartActionBarView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/ChartActionBarView.java @@ -55,6 +55,10 @@ public class ChartActionBarView extends HorizontalScrollView { } private void init() { + if (isInEditMode()) { + return; + } + actionBarView = new LinearLayout(getContext()); actionBarView.setOrientation(LinearLayout.HORIZONTAL); actionBarView.setBackgroundColor(ColorUtil.COLOR_BLACK); 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 0c960485..cbab5e72 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 @@ -16,6 +16,8 @@ package com.health.openscale.gui.measurement; +import static java.time.temporal.ChronoUnit.DAYS; + import android.content.Context; import android.content.SharedPreferences; import android.graphics.Color; @@ -53,8 +55,6 @@ 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, @@ -195,6 +195,10 @@ public class ChartMeasurementView extends LineChart { } private void initChart() { + if (isInEditMode()) { + return; + } + prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); openScale = OpenScale.getInstance(); measurementViews = MeasurementView.getMeasurementList(getContext(), MeasurementView.DateTimeOrder.NONE); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/FloatMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/FloatMeasurementView.java index cb9ce151..4913b82f 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/FloatMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/FloatMeasurementView.java @@ -537,9 +537,6 @@ public abstract class FloatMeasurementView extends MeasurementView { return ""; } - @Override - public boolean hasExtraPreferences() { return true; } - private class ListPreferenceWithNeutralButton extends ListPreference { ListPreferenceWithNeutralButton(Context context) { super(context); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java index 28444199..7875b809 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java @@ -424,7 +424,6 @@ public abstract class MeasurementView extends TableLayout { } public String getPreferenceSummary() { return ""; } - public boolean hasExtraPreferences() { return true; } public void prepareExtraPreferencesScreen(PreferenceScreen screen) { MeasurementViewSettings settings = getSettings(); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java index 373a9950..98543a61 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java @@ -1,12 +1,17 @@ package com.health.openscale.gui.overview; import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TableLayout; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; @@ -16,6 +21,7 @@ import androidx.transition.AutoTransition; import androidx.transition.TransitionManager; import com.health.openscale.R; +import com.health.openscale.core.OpenScale; import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.gui.measurement.DateMeasurementView; import com.health.openscale.gui.measurement.MeasurementEntryFragment; @@ -36,6 +42,38 @@ class OverviewAdapter extends RecyclerView.Adapter { this.scaleMeasurementList = scaleMeasurementList; } + private void deleteMeasurement(int measurementId) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + boolean deleteConfirmationEnable = prefs.getBoolean("deleteConfirmationEnable", true); + + if (deleteConfirmationEnable) { + AlertDialog.Builder deleteAllDialog = new AlertDialog.Builder(activity); + deleteAllDialog.setMessage(activity.getResources().getString(R.string.question_really_delete)); + + deleteAllDialog.setPositiveButton(activity.getResources().getString(R.string.label_yes), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + doDeleteMeasurement(measurementId); + } + }); + + deleteAllDialog.setNegativeButton(activity.getResources().getString(R.string.label_no), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + + deleteAllDialog.show(); + } + else { + doDeleteMeasurement(measurementId); + } + } + + private void doDeleteMeasurement(int measurementId) { + OpenScale.getInstance().deleteScaleMeasurement(measurementId); + Toast.makeText(activity, activity.getResources().getString(R.string.info_data_deleted), Toast.LENGTH_SHORT).show(); + } + @Override public OverviewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_overview, parent, false); @@ -48,13 +86,16 @@ class OverviewAdapter extends RecyclerView.Adapter { @Override public void onBindViewHolder(@NonNull OverviewAdapter.ViewHolder holder, int position) { ScaleMeasurement scaleMeasurement = scaleMeasurementList.get(position); - ScaleMeasurement prevScaleMeasurement = new ScaleMeasurement(); + ScaleMeasurement prevScaleMeasurement; - if (scaleMeasurementList.size() > 2) { + // for the first measurement no previous measurement are available, use standard measurement instead + if (position == 0) { + prevScaleMeasurement = new ScaleMeasurement(); + } else { prevScaleMeasurement = scaleMeasurementList.get(position - 1); } - holder.itemView.setOnClickListener(new View.OnClickListener() { + holder.showEntry.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { OverviewFragmentDirections.ActionNavOverviewToNavDataentry action = OverviewFragmentDirections.actionNavOverviewToNavDataentry(); @@ -64,6 +105,22 @@ class OverviewAdapter extends RecyclerView.Adapter { } }); + holder.editEntry.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OverviewFragmentDirections.ActionNavOverviewToNavDataentry action = OverviewFragmentDirections.actionNavOverviewToNavDataentry(); + action.setMeasurementId(scaleMeasurement.getId()); + action.setMode(MeasurementEntryFragment.DATA_ENTRY_MODE.EDIT); + Navigation.findNavController(activity, R.id.nav_host_fragment).navigate(action); + } + }); + holder.deleteEntry.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + deleteMeasurement(scaleMeasurement.getId()); + } + }); + holder.expandMeasurementView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -113,6 +170,9 @@ class OverviewAdapter extends RecyclerView.Adapter { static class ViewHolder extends RecyclerView.ViewHolder { TextView dateView; + ImageView showEntry; + ImageView editEntry; + ImageView deleteEntry; TableLayout measurementHighlightViews; ImageView expandMeasurementView; TableLayout measurementViews; @@ -121,6 +181,9 @@ class OverviewAdapter extends RecyclerView.Adapter { super(itemView); dateView = itemView.findViewById(R.id.dateView); + showEntry = itemView.findViewById(R.id.showEntry); + editEntry = itemView.findViewById(R.id.editEntry); + deleteEntry = itemView.findViewById(R.id.deleteEntry); measurementHighlightViews = itemView.findViewById(R.id.measurementHighlightViews); expandMeasurementView = itemView.findViewById(R.id.expandMoreView); measurementViews = itemView.findViewById(R.id.measurementViews); 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 9cde13c6..336033d7 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 @@ -15,8 +15,6 @@ */ package com.health.openscale.gui.overview; -import android.app.AlertDialog; -import android.content.DialogInterface; import android.content.SharedPreferences; import android.graphics.Color; import android.os.Bundle; @@ -35,14 +33,14 @@ import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.Spinner; import android.widget.TextView; -import android.widget.Toast; import androidx.activity.OnBackPressedCallback; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; -import androidx.navigation.Navigation; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.transition.ChangeScroll; +import androidx.transition.TransitionManager; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.highlight.Highlight; @@ -54,10 +52,7 @@ import com.health.openscale.core.datatypes.ScaleUser; import com.health.openscale.core.utils.DateTimeHelpers; import com.health.openscale.gui.measurement.ChartActionBarView; import com.health.openscale.gui.measurement.ChartMeasurementView; -import com.health.openscale.gui.measurement.MeasurementEntryFragment; -import com.health.openscale.gui.measurement.MeasurementView; import com.health.openscale.gui.measurement.WeightMeasurementView; -import com.health.openscale.gui.utils.ColorUtil; import java.text.DateFormat; import java.util.ArrayList; @@ -69,8 +64,6 @@ public class OverviewFragment extends Fragment { private TextView txtTitleUser; - private List lastMeasurementViews; - private RecyclerView recyclerView; private OverviewAdapter overviewAdapter; private ChartMeasurementView chartView; @@ -80,10 +73,6 @@ public class OverviewFragment extends Fragment { private PopupMenu rangePopupMenu; - private ImageView showEntry; - private ImageView editEntry; - private ImageView deleteEntry; - private TextView differenceWeightView; private TextView initialWeightView; private TextView goalWeightView; @@ -94,6 +83,7 @@ public class OverviewFragment extends Fragment { private SharedPreferences prefs; + private List scaleMeasurementList; private ScaleMeasurement markedMeasurement; @Override @@ -102,8 +92,6 @@ public class OverviewFragment extends Fragment { prefs = PreferenceManager.getDefaultSharedPreferences(overviewView.getContext()); - txtTitleUser = overviewView.findViewById(R.id.txtTitleUser); - differenceWeightView = overviewView.findViewById(R.id.differenceWeightView); initialWeightView = overviewView.findViewById(R.id.initialWeightView); goalWeightView = overviewView.findViewById(R.id.goalWeightView); @@ -203,9 +191,6 @@ public class OverviewFragment extends Fragment { chartActionBarView.setVisibility(View.GONE); } - lastMeasurementViews = MeasurementView.getMeasurementList( - getContext(), MeasurementView.DateTimeOrder.LAST); - recyclerView = overviewView.findViewById(R.id.recyclerView); LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); layoutManager.setInitialPrefetchItemCount(5); @@ -224,39 +209,6 @@ public class OverviewFragment extends Fragment { } }); - showEntry = overviewView.findViewById(R.id.showEntry); - showEntry.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - OverviewFragmentDirections.ActionNavOverviewToNavDataentry action = OverviewFragmentDirections.actionNavOverviewToNavDataentry(); - action.setMeasurementId(markedMeasurement.getId()); - action.setMode(MeasurementEntryFragment.DATA_ENTRY_MODE.VIEW); - Navigation.findNavController(getActivity(), R.id.nav_host_fragment).navigate(action); - } - }); - - editEntry = overviewView.findViewById(R.id.editEntry); - editEntry.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - OverviewFragmentDirections.ActionNavOverviewToNavDataentry action = OverviewFragmentDirections.actionNavOverviewToNavDataentry(); - action.setMeasurementId(markedMeasurement.getId()); - action.setMode(MeasurementEntryFragment.DATA_ENTRY_MODE.EDIT); - Navigation.findNavController(getActivity(), R.id.nav_host_fragment).navigate(action); - } - }); - deleteEntry = overviewView.findViewById(R.id.deleteEntry); - deleteEntry.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - deleteMeasurement(); - } - }); - - showEntry.setEnabled(false); - editEntry.setEnabled(false); - deleteEntry.setEnabled(false); - chartView.animateY(700); OpenScale.getInstance().getScaleMeasurementsLiveData().observe(getViewLifecycleOwner(), new Observer>() { @@ -279,17 +231,12 @@ public class OverviewFragment extends Fragment { } public void updateOnView(List scaleMeasurementList) { - if (scaleMeasurementList.isEmpty()) { - markedMeasurement = new ScaleMeasurement(); - } else { - markedMeasurement = scaleMeasurementList.get(0); - } + this.scaleMeasurementList = scaleMeasurementList; overviewAdapter = new OverviewAdapter(getActivity(), scaleMeasurementList); recyclerView.setAdapter(overviewAdapter); updateUserSelection(); - updateMesurementViews(markedMeasurement); chartView.updateMeasurementList(scaleMeasurementList); updateChartView(); } @@ -299,15 +246,6 @@ public class OverviewFragment extends Fragment { chartView.setViewRange(selectedRangeMode); } - private void updateMesurementViews(ScaleMeasurement selectedMeasurement) { - ScaleMeasurement[] tupleScaleData = OpenScale.getInstance().getTupleOfScaleMeasurement(selectedMeasurement.getId()); - ScaleMeasurement prevScaleMeasurement = tupleScaleData[0]; - - for (MeasurementView measurement : lastMeasurementViews) { - measurement.loadFrom(selectedMeasurement, prevScaleMeasurement); - } - } - private void updateUserSelection() { currentScaleUser = OpenScale.getInstance().getSelectedScaleUser(); @@ -328,7 +266,6 @@ public class OverviewFragment extends Fragment { // Hide user selector when there is only one user int visibility = spinUserAdapter.getCount() < 2 ? View.GONE : View.VISIBLE; - txtTitleUser.setVisibility(visibility); spinUser.setVisibility(visibility); @@ -409,26 +346,15 @@ public class OverviewFragment extends Fragment { markedMeasurement = (ScaleMeasurement)extraData[0]; //MeasurementView measurementView = (MeasurementView)extraData[1]; - showEntry.setEnabled(true); - editEntry.setEnabled(true); - deleteEntry.setEnabled(true); - - showEntry.setColorFilter(ColorUtil.COLOR_BLUE); - editEntry.setColorFilter(ColorUtil.COLOR_GREEN); - deleteEntry.setColorFilter(ColorUtil.COLOR_RED); - - updateMesurementViews(markedMeasurement); + if (scaleMeasurementList.contains(markedMeasurement)) { + TransitionManager.beginDelayedTransition(recyclerView, new ChangeScroll()); + recyclerView.scrollToPosition(scaleMeasurementList.indexOf(markedMeasurement)); + } } @Override public void onNothingSelected() { - showEntry.setEnabled(false); - editEntry.setEnabled(false); - deleteEntry.setEnabled(false); - - showEntry.setColorFilter(ColorUtil.COLOR_GRAY); - editEntry.setColorFilter(ColorUtil.COLOR_GRAY); - deleteEntry.setColorFilter(ColorUtil.COLOR_GRAY); + // empty } } @@ -454,44 +380,4 @@ public class OverviewFragment extends Fragment { } } - - private void deleteMeasurement() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(overviewView.getContext()); - boolean deleteConfirmationEnable = prefs.getBoolean("deleteConfirmationEnable", true); - - if (deleteConfirmationEnable) { - AlertDialog.Builder deleteAllDialog = new AlertDialog.Builder(overviewView.getContext()); - deleteAllDialog.setMessage(getResources().getString(R.string.question_really_delete)); - - deleteAllDialog.setPositiveButton(getResources().getString(R.string.label_yes), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - doDeleteMeasurement(); - } - }); - - deleteAllDialog.setNegativeButton(getResources().getString(R.string.label_no), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.dismiss(); - } - }); - - deleteAllDialog.show(); - } - else { - doDeleteMeasurement(); - } - } - - private void doDeleteMeasurement() { - OpenScale.getInstance().deleteScaleMeasurement(markedMeasurement.getId()); - Toast.makeText(overviewView.getContext(), getResources().getString(R.string.info_data_deleted), Toast.LENGTH_SHORT).show(); - - showEntry.setEnabled(false); - editEntry.setEnabled(false); - deleteEntry.setEnabled(false); - - showEntry.setColorFilter(ColorUtil.COLOR_GRAY); - editEntry.setColorFilter(ColorUtil.COLOR_GRAY); - deleteEntry.setColorFilter(ColorUtil.COLOR_GRAY); - } } diff --git a/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java b/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java index 2a301710..7e6a09c2 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java @@ -245,13 +245,6 @@ public class MeasurementPreferences extends PreferenceFragmentCompat { public boolean onSingleTapUp(MotionEvent e) { boundView.setPressed(false); - if (!measurement.hasExtraPreferences()) { - if (switchView.getVisibility() == View.VISIBLE) { - switchView.toggle(); - } - return true; - } - // Must be enabled to show extra preferences screen if (!measurement.getSettings().isEnabled()) { return true; diff --git a/android_app/app/src/main/res/layout/fragment_overview.xml b/android_app/app/src/main/res/layout/fragment_overview.xml index c6890c62..770a770c 100644 --- a/android_app/app/src/main/res/layout/fragment_overview.xml +++ b/android_app/app/src/main/res/layout/fragment_overview.xml @@ -1,140 +1,94 @@ - + android:layout_width="match_parent" + android:layout_height="match_parent"> - + - + - + - - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - + - + app:layout_constraintBottom_toBottomOf="@+id/chartView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/chartActionBar" /> + + diff --git a/android_app/app/src/main/res/layout/item_overview.xml b/android_app/app/src/main/res/layout/item_overview.xml index f204090a..9bc14b2d 100644 --- a/android_app/app/src/main/res/layout/item_overview.xml +++ b/android_app/app/src/main/res/layout/item_overview.xml @@ -25,22 +25,56 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" - android:textSize="12sp" + android:textSize="16sp" + android:textStyle="normal" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + + - + android:layout_marginTop="8dp" + app:layout_constraintTop_toBottomOf="@id/dateView"> + android:gravity="center" /> From 8047e9e252873bafeff5eb16de1d1da037034548 Mon Sep 17 00:00:00 2001 From: oliexdev Date: Sun, 29 Jan 2023 13:09:20 +0100 Subject: [PATCH 7/8] replaced table view with sticky header and column table view. --- .../measurement/CommentMeasurementView.java | 4 + .../gui/measurement/DateMeasurementView.java | 4 + .../gui/measurement/MeasurementView.java | 2 + .../gui/measurement/TimeMeasurementView.java | 4 + .../gui/measurement/UserMeasurementView.java | 4 + .../gui/table/StickyHeaderTableView.java | 1464 +++++++++++++++++ .../openscale/gui/table/TableFragment.java | 270 +-- .../src/main/res/layout/fragment_table.xml | 45 +- .../app/src/main/res/values/styles.xml | 22 + 9 files changed, 1601 insertions(+), 218 deletions(-) create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/table/StickyHeaderTableView.java diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/CommentMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/CommentMeasurementView.java index 1b2baaba..dae0c9eb 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/CommentMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/CommentMeasurementView.java @@ -23,6 +23,7 @@ import android.widget.EditText; import com.health.openscale.R; import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.gui.utils.ColorUtil; public class CommentMeasurementView extends MeasurementView { // Don't change key value, it may be stored persistent in preferences @@ -71,6 +72,9 @@ public class CommentMeasurementView extends MeasurementView { state.putString(getKey(), comment); } + @Override + public int getColor() { return ColorUtil.COLOR_GRAY; }; + @Override public String getValueAsString(boolean withUnit) { return comment; diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/DateMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/DateMeasurementView.java index 252c0ff5..0843b1d1 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/DateMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/DateMeasurementView.java @@ -23,6 +23,7 @@ import android.widget.DatePicker; import com.health.openscale.R; import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.gui.utils.ColorUtil; import java.text.DateFormat; import java.util.Calendar; @@ -88,6 +89,9 @@ public class DateMeasurementView extends MeasurementView { state.putLong(getKey(), date.getTime()); } + @Override + public int getColor() { return ColorUtil.COLOR_GRAY; }; + @Override public String getValueAsString(boolean withUnit) { return dateFormat.format(date); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java index 7875b809..942bfd4b 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java @@ -358,6 +358,8 @@ public abstract class MeasurementView extends TableLayout { return background.getColor(); } + abstract public int getColor(); + protected void showEvaluatorRow(boolean show) { if (show) { evaluatorRow.setVisibility(View.VISIBLE); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/TimeMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/TimeMeasurementView.java index 6f2662fe..c1dd00be 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/TimeMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/TimeMeasurementView.java @@ -22,6 +22,7 @@ import android.widget.TimePicker; import com.health.openscale.R; import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.gui.utils.ColorUtil; import java.text.DateFormat; import java.util.Calendar; @@ -89,6 +90,9 @@ public class TimeMeasurementView extends MeasurementView { state.putLong(getKey(), time.getTime()); } + @Override + public int getColor() { return ColorUtil.COLOR_GRAY; }; + @Override public String getValueAsString(boolean withUnit) { return timeFormat.format(time); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/UserMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/UserMeasurementView.java index 66e3799a..4ec9c7db 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/UserMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/UserMeasurementView.java @@ -25,6 +25,7 @@ import com.health.openscale.R; import com.health.openscale.core.OpenScale; import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleUser; +import com.health.openscale.gui.utils.ColorUtil; import java.util.ArrayList; @@ -80,6 +81,9 @@ public class UserMeasurementView extends MeasurementView { state.putInt(getKey(), userId); } + @Override + public int getColor() { return ColorUtil.COLOR_GRAY; }; + @Override public String getValueAsString(boolean withUnit) { return openScale.getScaleUser(userId).getUserName(); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/table/StickyHeaderTableView.java b/android_app/app/src/main/java/com/health/openscale/gui/table/StickyHeaderTableView.java new file mode 100644 index 00000000..22326791 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/table/StickyHeaderTableView.java @@ -0,0 +1,1464 @@ +package com.health.openscale.gui.table; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.SparseIntArray; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.DecelerateInterpolator; + +import androidx.core.view.NestedScrollingChild; +import androidx.core.view.NestedScrollingChildHelper; +import androidx.core.view.ViewCompat; + +import com.health.openscale.R; +import com.health.openscale.gui.utils.ColorUtil; + +/** + * Created by Mitul Varmora on 11/8/2016. + * StickyHeaderTableView, see https://github.com/MitulVarmora/StickyHeaderTableView + * MIT License + * modified 2023 by olie.xdev + */ + +public class StickyHeaderTableView extends View implements NestedScrollingChild { + + private final Paint paintStrokeRect = new Paint(); + private final Paint paintHeaderCellFillRect = new Paint(); + private final Paint paintContentCellFillRect = new Paint(); + private final TextPaint paintLabelText = new TextPaint(); + private final Paint paintDrawable = new Paint(); + + private final TextPaint paintHeaderText = new TextPaint(); + private final Rect textRectBounds = new Rect(); + /** + * Visible rect size of view which is displayed on screen + */ + private final Rect visibleContentRect = new Rect(0, 0, 0, 0); + /** + * based on scrolling this rect value will update + */ + private final Rect scrolledRect = new Rect(0, 0, 0, 0); + /** + * Actual rect size of canvas drawn content (Which may be larger or smaller than mobile screen) + */ + private final Rect actualContentRect = new Rect(0, 0, 0, 0); + // below variables are used for fling animation (Not for scrolling) + private final DecelerateInterpolator animateInterpolator = new DecelerateInterpolator(); + private NestedScrollingChildHelper nestedScrollingChildHelper; + private int NESTED_SCROLL_AXIS = ViewCompat.SCROLL_AXIS_NONE; + private OnTableCellClickListener onTableCellClickListener = null; + private boolean isScrollingHorizontally = false; + private boolean isScrollingVertically = false; + /** + * This is used to stop fling animation if user has touch intercepted + */ + private boolean isFlinging = false; + // Below are configurable variables via xml (also can be used via setter methods) + private boolean isDisplayLeftHeadersVertically = false; + private boolean is2DScrollingEnabled; + private boolean isWrapHeightOfEachRow = false; + private boolean isWrapWidthOfEachColumn = false; + private int textLabelColor; + private int textHeaderColor; + private int dividerColor; + private int textLabelSize; + private int textHeaderSize; + private int dividerThickness; + private int headerCellFillColor; + private int contentCellFillColor; + private int cellPadding; + /** + * Used to identify clicked position for #OnTableCellClickListener + */ + private Rect[][] rectEachCellBoundData = new Rect[][]{}; + private Object[][] data = null; + private int maxWidthOfCell = 0; + private int maxHeightOfCell = 0; + private SparseIntArray maxHeightSparseIntArray = new SparseIntArray(); + private SparseIntArray maxWidthSparseIntArray = new SparseIntArray(); + /** + * Used for scroll events + */ + private GestureDetector gestureDetector; + private long startTime; + private long endTime; + private float totalAnimDx; + private float totalAnimDy; + private float lastAnimDx; + private float lastAnimDy; + + public interface OnTableCellClickListener { + public void onTableCellClicked(int rowPosition, int columnPosition); + } + + public StickyHeaderTableView(Context context) { + this(context, null, 0); + } + + public StickyHeaderTableView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public StickyHeaderTableView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + final int defaultTextSize = (int) dpToPixels(getContext(), 14); + + TypedArray a = context.getTheme().obtainStyledAttributes( + attrs, R.styleable.StickyHeaderTableView, defStyleAttr, defStyleAttr); + + if (a != null) { + try { + textLabelColor = a.getColor( + R.styleable.StickyHeaderTableView_shtv_textLabelColor, Color.BLACK); + textHeaderColor = a.getColor( + R.styleable.StickyHeaderTableView_shtv_textHeaderColor, Color.BLACK); + dividerColor = a.getColor( + R.styleable.StickyHeaderTableView_shtv_dividerColor, Color.BLACK); + + textLabelSize = a.getDimensionPixelSize( + R.styleable.StickyHeaderTableView_shtv_textLabelSize, defaultTextSize); + textHeaderSize = a.getDimensionPixelSize( + R.styleable.StickyHeaderTableView_shtv_textHeaderSize, defaultTextSize); + dividerThickness = a.getDimensionPixelSize(R.styleable.StickyHeaderTableView_shtv_dividerThickness, 0); + cellPadding = a.getDimensionPixelSize(R.styleable.StickyHeaderTableView_shtv_cellPadding, 0); + + is2DScrollingEnabled = a.getBoolean(R.styleable.StickyHeaderTableView_shtv_is2DScrollEnabled, false); + isDisplayLeftHeadersVertically = a.getBoolean(R.styleable.StickyHeaderTableView_shtv_isDisplayLeftHeadersVertically, false); + isWrapHeightOfEachRow = a.getBoolean(R.styleable.StickyHeaderTableView_shtv_isWrapHeightOfEachRow, false); + isWrapWidthOfEachColumn = a.getBoolean(R.styleable.StickyHeaderTableView_shtv_isWrapWidthOfEachColumn, false); + + headerCellFillColor = a.getColor( + R.styleable.StickyHeaderTableView_shtv_headerCellFillColor, Color.TRANSPARENT); + + contentCellFillColor = a.getColor( + R.styleable.StickyHeaderTableView_shtv_contentCellFillColor, Color.TRANSPARENT); + + } catch (Exception e) { + textLabelColor = Color.BLACK; + textHeaderColor = Color.BLACK; + dividerColor = Color.BLACK; + textLabelSize = defaultTextSize; + textHeaderSize = defaultTextSize; + dividerThickness = 0; + cellPadding = 0; + is2DScrollingEnabled = false; + headerCellFillColor = Color.TRANSPARENT; + contentCellFillColor = Color.TRANSPARENT; + } finally { + a.recycle(); + } + } else { + textLabelColor = Color.BLACK; + textHeaderColor = Color.BLACK; + dividerColor = Color.BLACK; + textLabelSize = defaultTextSize; + textHeaderSize = defaultTextSize; + dividerThickness = 0; + cellPadding = 0; + is2DScrollingEnabled = false; + headerCellFillColor = Color.TRANSPARENT; + contentCellFillColor = Color.TRANSPARENT; + } + + setupPaint(); + setupScrolling(); + } + + private float dpToPixels(Context context, float dpValue) { + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, metrics); + } + + private void setupPaint() { + paintStrokeRect.setStyle(Paint.Style.STROKE); + paintStrokeRect.setColor(dividerColor); + paintStrokeRect.setStrokeWidth(dividerThickness); + + paintHeaderCellFillRect.setStyle(Paint.Style.FILL); + paintHeaderCellFillRect.setColor(headerCellFillColor); + + paintContentCellFillRect.setStyle(Paint.Style.FILL); + paintContentCellFillRect.setColor(contentCellFillColor); + + paintLabelText.setStyle(Paint.Style.FILL); + paintLabelText.setColor(textLabelColor); + paintLabelText.setTextSize(textLabelSize); + paintLabelText.setTextAlign(Paint.Align.LEFT); + + paintHeaderText.setStyle(Paint.Style.FILL); + paintHeaderText.setColor(textHeaderColor); + paintHeaderText.setTextSize(textHeaderSize); + paintHeaderText.setTextAlign(Paint.Align.LEFT); + } + + private void setupScrolling() { + + nestedScrollingChildHelper = new NestedScrollingChildHelper(this); + + GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() { + + public boolean onDown(MotionEvent e) { + if (isNestedScrollingEnabled()) { + startNestedScroll(NESTED_SCROLL_AXIS); + } + if (isFlinging) { + isFlinging = false; + } + return true; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (isNestedScrollingEnabled()) { + dispatchNestedPreFling(velocityX, velocityY); + } + + if (!canScrollHorizontally() && !canScrollVertically()) { + return false; + } + + final float distanceTimeFactor = 0.4f; + totalAnimDx = (distanceTimeFactor * velocityX / 2); + totalAnimDy = (distanceTimeFactor * velocityY / 2); + lastAnimDx = 0; + lastAnimDy = 0; + startTime = System.currentTimeMillis(); + endTime = startTime + (long) (1000 * distanceTimeFactor); + + float deltaY = e2.getY() - e1.getY(); + float deltaX = e2.getX() - e1.getX(); + + if (!is2DScrollingEnabled) { + if (Math.abs(deltaX) > Math.abs(deltaY)) { + isScrollingHorizontally = true; + } else { + isScrollingVertically = true; + } + } + isFlinging = true; + + if (onFlingAnimateStep()) { + if (isNestedScrollingEnabled()) { + dispatchNestedFling(-velocityX, -velocityY, true); + } + return true; + } else { + if (isNestedScrollingEnabled()) { + dispatchNestedFling(-velocityX, -velocityY, false); + } + return false; + } + + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + + if (isNestedScrollingEnabled()) { + dispatchNestedPreScroll((int) distanceX, (int) distanceY, null, null); + } + + boolean isScrolled; + + if (is2DScrollingEnabled) { + isScrolled = scroll2D(distanceX, distanceY); + } else { + + if (isScrollingHorizontally) { + isScrolled = scrollHorizontal(distanceX); + } else if (isScrollingVertically) { + isScrolled = scrollVertical(distanceY); + } else { + + float deltaY = e2.getY() - e1.getY(); + float deltaX = e2.getX() - e1.getX(); + + if (Math.abs(deltaX) > Math.abs(deltaY)) { + // if deltaX > 0 : the user made a sliding right gesture + // else : the user made a sliding left gesture + isScrollingHorizontally = true; + isScrolled = scrollHorizontal(distanceX); + } else { + // if deltaY > 0 : the user made a sliding down gesture + // else : the user made a sliding up gesture + isScrollingVertically = true; + isScrolled = scrollVertical(distanceY); + } + } + } + + // Fix scrolling (if any parent view is scrollable in layout hierarchy, + // than this will disallow intercepting touch event) + if (getParent() != null && isScrolled) { + getParent().requestDisallowInterceptTouchEvent(true); + } + + if (isScrolled) { + if (isNestedScrollingEnabled()) { + dispatchNestedScroll((int) distanceX, (int) distanceY, 0, 0, null); + } + } else { + if (isNestedScrollingEnabled()) { + dispatchNestedScroll(0, 0, (int) distanceX, (int) distanceY, null); + } + } + + return isScrolled; + } + + public boolean onSingleTapUp(MotionEvent e) { + + if (onTableCellClickListener != null) { + + final float x = e.getX(); + final float y = e.getY(); + + boolean isEndLoop = false; + + for (int i = 0; i < rectEachCellBoundData.length; i++) { + + if (rectEachCellBoundData[i][0].top <= y && rectEachCellBoundData[i][0].bottom >= y) { + + for (int j = 0; j < rectEachCellBoundData[0].length; j++) { + + if (rectEachCellBoundData[i][j].left <= x && rectEachCellBoundData[i][j].right >= x) { + isEndLoop = true; + onTableCellClickListener.onTableCellClicked(i, j); + break; + } + } + } + if (isEndLoop) { + break; + } + } + } + + return super.onSingleTapUp(e); + } + + public void onLongPress(MotionEvent e) { + super.onLongPress(e); + } + + public boolean onDoubleTapEvent(MotionEvent e) { + return super.onDoubleTapEvent(e); + } + + }; + gestureDetector = new GestureDetector(getContext(), simpleOnGestureListener); + } + + /** + * This will start fling animation + * + * @return true if fling animation consumed + */ + private boolean onFlingAnimateStep() { + + boolean isScrolled = false; + + long curTime = System.currentTimeMillis(); + float percentTime = (float) (curTime - startTime) / (float) (endTime - startTime); + float percentDistance = animateInterpolator.getInterpolation(percentTime); + float curDx = percentDistance * totalAnimDx; + float curDy = percentDistance * totalAnimDy; + + float distanceX = curDx - lastAnimDx; + float distanceY = curDy - lastAnimDy; + lastAnimDx = curDx; + lastAnimDy = curDy; + + if (is2DScrollingEnabled) { + isScrolled = scroll2D(-distanceX, -distanceY); + } else if (isScrollingHorizontally) { + isScrolled = scrollHorizontal(-distanceX); + } else if (isScrollingVertically) { + isScrolled = scrollVertical(-distanceY); + } + + // This will stop fling animation if user has touch intercepted + if (!isFlinging) { + return false; + } + + if (percentTime < 1.0f) { + // fling animation running + post(this::onFlingAnimateStep); + } else { + // fling animation ended + isFlinging = false; + isScrollingVertically = false; + isScrollingHorizontally = false; + } + return isScrolled; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int desiredWidth = 0; + int desiredHeight = 0; + + if (data != null) { + updateMaxWidthHeightOfCell(); + if (isWrapHeightOfEachRow) { + + for (int i = 0; i < maxHeightSparseIntArray.size(); i++) { + desiredHeight = desiredHeight + maxHeightSparseIntArray.get(i, 0); + } + desiredHeight = desiredHeight + (dividerThickness / 2); + } else { + desiredHeight = maxHeightOfCell * data.length + (dividerThickness / 2); + } + + if (isWrapWidthOfEachColumn) { + + for (int i = 0; i < maxWidthSparseIntArray.size(); i++) { + desiredWidth = desiredWidth + maxWidthSparseIntArray.get(i, 0); + } + desiredWidth = desiredWidth + (dividerThickness / 2); + + } else { + desiredWidth = maxWidthOfCell * data[0].length + (dividerThickness / 2); + } + + scrolledRect.set(0, 0, desiredWidth, desiredHeight); + actualContentRect.set(0, 0, desiredWidth, desiredHeight); + } + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + int width; + int height; + + //Measure Width + if (widthMode == MeasureSpec.EXACTLY) { + //Must be this size + width = widthSize; + } else if (widthMode == MeasureSpec.AT_MOST) { + //Can't be bigger than... + width = Math.min(desiredWidth, widthSize); + } else { + //Be whatever you want + width = desiredWidth; + } + + //Measure Height + if (heightMode == MeasureSpec.EXACTLY) { + //Must be this size + height = heightSize; + } else if (heightMode == MeasureSpec.AT_MOST) { + //Can't be bigger than... + height = Math.min(desiredHeight, heightSize); + } else { + //Be whatever you want + height = desiredHeight; + } + + //MUST CALL THIS + setMeasuredDimension(width, height); + } + + /** + * Calculate and update max width height of cell
+ * Required for onMeasure() method + */ + private void updateMaxWidthHeightOfCell() { + + maxWidthOfCell = 0; + maxHeightOfCell = 0; + maxHeightSparseIntArray = new SparseIntArray(); + maxWidthSparseIntArray = new SparseIntArray(); + + final int doubleCellPadding = cellPadding + cellPadding; + + for (int i = 0; i < data.length; i++) { + + for (int j = 0; j < data[0].length; j++) { + + if (i == 0 && j == 0) { +// data[0][0] = "xx"; + + if (data[i][j] instanceof String) { + String str = (String)data[i][j]; + paintHeaderText.getTextBounds(str, 0, str.length(), textRectBounds); + } else if (data[i][j] instanceof Drawable) { + Drawable icon = (Drawable) data[i][j]; + textRectBounds.set(0,0, icon.getIntrinsicWidth() + 30, icon.getIntrinsicHeight()); + } + + if (maxWidthOfCell < textRectBounds.width()) { + maxWidthOfCell = textRectBounds.width(); + } + if (maxHeightOfCell < textRectBounds.height()) { + maxHeightOfCell = textRectBounds.height(); + } + + if (maxWidthSparseIntArray.get(j, 0) < textRectBounds.width()) { + maxWidthSparseIntArray.put(j, textRectBounds.width()); + } + if (maxHeightSparseIntArray.get(i, 0) < textRectBounds.height()) { + maxHeightSparseIntArray.put(i, textRectBounds.height()); + } + } else if (i == 0) { + // Top headers cells + + if (data[i][j] instanceof String) { + String str = (String)data[i][j]; + paintHeaderText.getTextBounds(str, 0, str.length(), textRectBounds); + } else if (data[i][j] instanceof Drawable) { + Drawable icon = (Drawable) data[i][j]; + textRectBounds.set(0,0,icon.getIntrinsicWidth() + 30, icon.getIntrinsicHeight()); + } + if (maxWidthOfCell < textRectBounds.width()) { + maxWidthOfCell = textRectBounds.width(); + } + if (maxHeightOfCell < textRectBounds.height()) { + maxHeightOfCell = textRectBounds.height(); + } + + if (maxWidthSparseIntArray.get(j, 0) < textRectBounds.width()) { + maxWidthSparseIntArray.put(j, textRectBounds.width()); + } + if (maxHeightSparseIntArray.get(i, 0) < textRectBounds.height()) { + maxHeightSparseIntArray.put(i, textRectBounds.height()); + } + + } else if (j == 0) { + // Left headers cells + if (data[i][j] instanceof String) { + String str = (String)data[i][j]; + if (str.indexOf("\n") != -1) { + String[] split = str.split("\n"); + + if (split[0].length() >= split[1].length()) { + str = split[0]; + } else { + str = split[1]; + } + } + paintHeaderText.getTextBounds(str, 0, str.length(), textRectBounds); + StaticLayout staticLayout = StaticLayout.Builder.obtain(str, 0, str.length(), paintHeaderText, textRectBounds.width()).build(); + + //textRectBounds.right = 50; + textRectBounds.bottom = staticLayout.getHeight(); + } else if (data[i][j] instanceof Drawable) { + Drawable icon = (Drawable) data[i][j]; + textRectBounds.set(0,0,icon.getIntrinsicWidth(), icon.getIntrinsicHeight() / 2); + } + + if (isDisplayLeftHeadersVertically) { + + if (maxWidthOfCell < textRectBounds.height()) { + maxWidthOfCell = textRectBounds.height(); + } + if (maxHeightOfCell < textRectBounds.width()) { + maxHeightOfCell = textRectBounds.width(); + } + + if (maxWidthSparseIntArray.get(j, 0) < textRectBounds.height()) { + maxWidthSparseIntArray.put(j, textRectBounds.height()); + } + if (maxHeightSparseIntArray.get(i, 0) < textRectBounds.width()) { + maxHeightSparseIntArray.put(i, textRectBounds.width()); + } + + } else { + + if (maxWidthOfCell < textRectBounds.width()) { + maxWidthOfCell = textRectBounds.width(); + } + if (maxHeightOfCell < textRectBounds.height()) { + maxHeightOfCell = textRectBounds.height(); + } + + if (maxWidthSparseIntArray.get(j, 0) < textRectBounds.width()) { + maxWidthSparseIntArray.put(j, textRectBounds.width()); + } + if (maxHeightSparseIntArray.get(i, 0) < textRectBounds.height()) { + maxHeightSparseIntArray.put(i, textRectBounds.height()); + } + } + } else { + // Other content cells + if (data[i][j] instanceof String) { + String str = (String)data[i][j]; + + if (str.indexOf("\n") != -1) { + String[] split = str.split("\n"); + + if (split[0].length() >= split[1].length()) { + str = split[0]; + } else { + str = split[1]; + } + } + paintLabelText.getTextBounds(str, 0, str.length(), textRectBounds); + StaticLayout staticLayout = StaticLayout.Builder.obtain(str, 0, str.length(), paintLabelText, textRectBounds.width()).build(); + + textRectBounds.bottom = staticLayout.getHeight(); + } else if (data[i][j] instanceof Drawable) { + Drawable icon = (Drawable) data[i][j]; + textRectBounds.set(0,0,icon.getIntrinsicWidth(), icon.getIntrinsicHeight() / 2); + } + + if (maxWidthOfCell < textRectBounds.width()) { + maxWidthOfCell = textRectBounds.width(); + } + if (maxHeightOfCell < textRectBounds.height()) { + maxHeightOfCell = textRectBounds.height(); + } + + if (maxWidthSparseIntArray.get(j, 0) < textRectBounds.width()) { + maxWidthSparseIntArray.put(j, textRectBounds.width()); + } + if (maxHeightSparseIntArray.get(i, 0) < textRectBounds.height()) { + maxHeightSparseIntArray.put(i, textRectBounds.height()); + } + } + } + } + maxWidthOfCell = maxWidthOfCell + doubleCellPadding; + maxHeightOfCell = maxHeightOfCell + doubleCellPadding; + + for (int i = 0; i < maxHeightSparseIntArray.size(); i++) { + maxHeightSparseIntArray.put(i, maxHeightSparseIntArray.get(i, 0) + doubleCellPadding); + } + + for (int i = 0; i < maxWidthSparseIntArray.size(); i++) { + maxWidthSparseIntArray.put(i, maxWidthSparseIntArray.get(i, 0) + doubleCellPadding); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldW, int oldH) { + super.onSizeChanged(w, h, oldW, oldH); + + visibleContentRect.set(0, 0, w, h); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (data == null) { + return; + } + + int cellLeftX; + int cellTopY = scrolledRect.top; + int cellRightX; + int cellBottomY = scrolledRect.top + getHeightOfRow(0); + int halfDividerThickness = dividerThickness / 2; + + float drawTextX; + float drawTextY; + String textToDraw; + Drawable iconToDraw; + + // *************************** Calculate each cells to draw ************************** + // This is top-left most cell (0,0) + updateRectPointData(0, 0, halfDividerThickness, halfDividerThickness, getWidthOfColumn(0), getHeightOfRow(0)); + + for (int i = 0; i < data.length; i++) { + cellRightX = scrolledRect.left; + int heightOfRowI = getHeightOfRow(i); + if (i == 0) { + cellTopY = halfDividerThickness; + for (int j = 0; j < data[i].length; j++) { + cellLeftX = cellRightX - halfDividerThickness; + cellRightX += getWidthOfColumn(j); + if (j != 0) { + // This are top header cells (0,*) + updateRectPointData(i, j, cellLeftX, cellTopY, cellRightX, heightOfRowI); + } + } + cellBottomY = scrolledRect.top + getHeightOfRow(i); + } else { + // These are content cells + for (int j = 0; j < data[0].length; j++) { + cellLeftX = cellRightX - halfDividerThickness; + cellRightX += getWidthOfColumn(j); + if (j != 0) { + updateRectPointData(i, j, cellLeftX, cellTopY, cellRightX, cellBottomY); + } + } + + // This are left header cells (*,0) + cellRightX = 0; + cellLeftX = cellRightX + halfDividerThickness; + cellRightX += getWidthOfColumn(0); + updateRectPointData(i, 0, cellLeftX, cellTopY, cellRightX, cellBottomY); + } + cellTopY = cellBottomY - halfDividerThickness; + cellBottomY = cellBottomY + getHeightOfRow(i + 1); + } + + // ******************** Draw contents & left headers ******************** + boolean isLeftVisible; + boolean isTopVisible; + boolean isRightVisible; + boolean isBottomVisible; + + for (int i = 1; i < data.length; i++) { + isTopVisible = rectEachCellBoundData[i][0].top >= rectEachCellBoundData[0][0].bottom + && rectEachCellBoundData[i][0].top <= visibleContentRect.bottom; + isBottomVisible = rectEachCellBoundData[i][0].bottom >= rectEachCellBoundData[0][0].bottom + && rectEachCellBoundData[i][0].bottom <= visibleContentRect.bottom; + + if (isTopVisible || isBottomVisible) { + + // ******************** Draw contents ******************** + for (int j = 1; j < data[i].length; j++) { + isLeftVisible = rectEachCellBoundData[i][j].left >= rectEachCellBoundData[i][0].right + && rectEachCellBoundData[i][j].left <= visibleContentRect.right; + isRightVisible = rectEachCellBoundData[i][j].right >= rectEachCellBoundData[i][0].right + && rectEachCellBoundData[i][j].right <= visibleContentRect.right; + + if (isLeftVisible || isRightVisible) { + canvas.drawRect(rectEachCellBoundData[i][j].left, rectEachCellBoundData[i][j].top, rectEachCellBoundData[i][j].right, rectEachCellBoundData[i][j].bottom, paintContentCellFillRect); + if (dividerThickness != 0) { + canvas.drawRect(rectEachCellBoundData[i][j].left, rectEachCellBoundData[i][j].top, rectEachCellBoundData[i][j].right, rectEachCellBoundData[i][j].bottom, paintStrokeRect); + } + + textToDraw = (String)data[i][j]; + // paintLabelText.getTextBounds(textToDraw, 0, textToDraw.length(), textRectBounds); + + drawTextX = rectEachCellBoundData[i][j].right - getWidthOfColumn(j) + getCellPadding(); + drawTextY = rectEachCellBoundData[i][j].bottom - (getHeightOfRow(i)) + (textRectBounds.height() / 2f); + + StaticLayout staticLayout = StaticLayout.Builder.obtain(textToDraw, 0, textToDraw.length(), paintLabelText, getWidthOfColumn(j)).build(); + + canvas.save(); + canvas.translate(drawTextX, drawTextY); + staticLayout.draw(canvas); + canvas.restore(); + + //canvas.drawText(textToDraw, 0, textToDraw.length(), drawTextX, drawTextY, paintLabelText); + } + } + + // ******************** Draw left header (*,0) ******************** + canvas.drawRect(rectEachCellBoundData[i][0].left, rectEachCellBoundData[i][0].top, rectEachCellBoundData[i][0].right, rectEachCellBoundData[i][0].bottom, paintHeaderCellFillRect); + if (dividerThickness != 0) { + canvas.drawRect(rectEachCellBoundData[i][0].left, rectEachCellBoundData[i][0].top, rectEachCellBoundData[i][0].right, rectEachCellBoundData[i][0].bottom, paintStrokeRect); + } + + textToDraw = (String)data[i][0]; + // paintHeaderText.getTextBounds(textToDraw, 0, textToDraw.length(), textRectBounds); + + if (isDisplayLeftHeadersVertically) { + drawTextX = rectEachCellBoundData[i][0].right - (getWidthOfColumn(0)) + (textRectBounds.height()); + drawTextY = rectEachCellBoundData[i][0].bottom - getCellPadding(); + + StaticLayout staticLayout = StaticLayout.Builder.obtain(textToDraw, 0, textToDraw.length(), paintHeaderText, getHeightOfRow(i)).build(); + + canvas.save(); + canvas.translate(drawTextX, drawTextY); + canvas.rotate(-90); + staticLayout.draw(canvas); + //canvas.drawText(textToDraw, 0, textToDraw.length(), drawTextX, drawTextY, paintHeaderText); + canvas.restore(); + } else { + drawTextX = rectEachCellBoundData[i][0].right - getWidthOfColumn(0) + getCellPadding(); + drawTextY = rectEachCellBoundData[i][0].bottom - (getHeightOfRow(i)) + (textRectBounds.height() / 2f); + + StaticLayout staticLayout = StaticLayout.Builder.obtain(textToDraw, 0, textToDraw.length(), paintLabelText, getWidthOfColumn(0)).build(); + + canvas.save(); + canvas.translate(drawTextX, drawTextY); + staticLayout.draw(canvas); + canvas.restore(); + + //canvas.drawText(textToDraw, 0, textToDraw.length(), drawTextX, drawTextY, paintHeaderText); + } + } + } + + // ******************** Draw top headers (0,*) ******************** + for (int j = 1; j < data[0].length; j++) { + isLeftVisible = rectEachCellBoundData[0][j].left >= rectEachCellBoundData[0][0].right + && rectEachCellBoundData[0][j].left <= visibleContentRect.right; + isRightVisible = rectEachCellBoundData[0][j].right >= rectEachCellBoundData[0][0].right + && rectEachCellBoundData[0][j].right <= visibleContentRect.right; + + if (isLeftVisible || isRightVisible) { + canvas.drawRect(rectEachCellBoundData[0][j].left, rectEachCellBoundData[0][j].top, rectEachCellBoundData[0][j].right, rectEachCellBoundData[0][j].bottom, paintHeaderCellFillRect); + if (dividerThickness != 0) { + canvas.drawRect(rectEachCellBoundData[0][j].left, rectEachCellBoundData[0][j].top, rectEachCellBoundData[0][j].right, rectEachCellBoundData[0][j].bottom, paintStrokeRect); + } + + if (data[0][j] instanceof String) { + textToDraw = (String)data[0][j]; + // paintHeaderText.getTextBounds(textToDraw, 0, textToDraw.length(), textRectBounds); + + drawTextX = rectEachCellBoundData[0][j].right - (getWidthOfColumn(j) / 2f) - (textRectBounds.width() / 2f); + drawTextY = rectEachCellBoundData[0][j].bottom - (getHeightOfRow(0) / 2f) + (textRectBounds.height() / 2f); + + canvas.drawText(textToDraw, 0, textToDraw.length(), drawTextX, drawTextY, paintHeaderText); + } else if (data[0][j] instanceof Drawable) { + iconToDraw = (Drawable) data[0][j]; + + drawTextX = rectEachCellBoundData[0][j].right - (getWidthOfColumn(j) / 2f) - (iconToDraw.getIntrinsicWidth() / 2f); + //drawTextY = rectEachCellBoundData[0][j].bottom - (getHeightOfRow(0) / 2f) + (iconToDraw.getIntrinsicHeight() / 2f); + + iconToDraw.setBounds((int)drawTextX, 25, (int)drawTextX + iconToDraw.getIntrinsicWidth(), 25 + iconToDraw.getIntrinsicHeight()); + + // draw circle with the tinted icon color and tint the icon with black + paintDrawable.setColorFilter(iconToDraw.getColorFilter()); + iconToDraw.setColorFilter(ColorUtil.COLOR_BLACK, PorterDuff.Mode.SRC_ATOP); + canvas.drawOval((int)drawTextX-25, 10, drawTextX+ iconToDraw.getIntrinsicWidth()+25, 45 + iconToDraw.getIntrinsicHeight(), paintDrawable); + + iconToDraw.draw(canvas); + + // save the tinted icon color back to the icon + iconToDraw.setColorFilter(paintDrawable.getColorFilter()); + } + } + } + + // ******************** Draw top-left most cell (0,0) ******************** + canvas.drawRect(rectEachCellBoundData[0][0].left, rectEachCellBoundData[0][0].top, rectEachCellBoundData[0][0].right, rectEachCellBoundData[0][0].bottom, paintHeaderCellFillRect); + + if (dividerThickness != 0) { + canvas.drawRect(rectEachCellBoundData[0][0].left, rectEachCellBoundData[0][0].top, rectEachCellBoundData[0][0].right, rectEachCellBoundData[0][0].bottom, paintStrokeRect); + } + + if (data[0][0] instanceof String) { + textToDraw = (String)data[0][0]; + + // paintHeaderText.getTextBounds(textToDraw, 0, textToDraw.length(), textRectBounds); + + drawTextX = getWidthOfColumn(0) - (getWidthOfColumn(0) / 2f) - (textRectBounds.width()/ 2f); + drawTextY = getHeightOfRow(0) - (getHeightOfRow(0) / 2f) + (textRectBounds.height() / 2f); + + canvas.drawText(textToDraw, 0, textToDraw.length(), drawTextX, drawTextY, paintHeaderText); + } else if (data[0][0] instanceof Drawable) { + iconToDraw = (Drawable) data[0][0]; + + drawTextX = getWidthOfColumn(0) - (getWidthOfColumn(0) / 2f) - (iconToDraw.getIntrinsicWidth()/ 2f); + //drawTextY = getHeightOfRow(0) - (getHeightOfRow(0) / 2f) + (iconToDraw.getIntrinsicHeight() / 2f); + iconToDraw.setBounds((int)drawTextX, 25, (int)drawTextX + iconToDraw.getIntrinsicWidth(), 25 + iconToDraw.getIntrinsicHeight()); + + // draw circle with the tinted icon color and tint the icon with black + paintDrawable.setColorFilter(iconToDraw.getColorFilter()); + iconToDraw.setColorFilter(ColorUtil.COLOR_BLACK, PorterDuff.Mode.SRC_ATOP); + canvas.drawOval((int)drawTextX-25, 10, drawTextX+ iconToDraw.getIntrinsicWidth()+25, 45 + iconToDraw.getIntrinsicHeight(), paintDrawable); + + iconToDraw.draw(canvas); + + // save the tinted icon color back to the icon + iconToDraw.setColorFilter(paintDrawable.getColorFilter()); + } + + + // ******************** Draw whole view border same as cell border ******************** + if (dividerThickness != 0) { + canvas.drawRect(visibleContentRect.left, visibleContentRect.top, visibleContentRect.right - halfDividerThickness, visibleContentRect.bottom - halfDividerThickness, paintStrokeRect); + } + } + + private int getWidthOfColumn(int key) { + if (isWrapWidthOfEachColumn) { + return maxWidthSparseIntArray.get(key, 0); + } else { + return maxWidthOfCell; + } + } + + private int getHeightOfRow(int key) { + if (isWrapHeightOfEachRow) { + return maxHeightSparseIntArray.get(key, 0); + } else { + return maxHeightOfCell; + } + } + + /** + * This will update cell bound rect data, which is used for handling cell click event + * + * @param i row position + * @param j column position + * @param cellLeftX leftX + * @param cellTopY topY + * @param cellRightX rightX + * @param cellBottomY bottomY + */ + private void updateRectPointData(int i, int j, int cellLeftX, int cellTopY, int cellRightX, int cellBottomY) { + if (rectEachCellBoundData[i][j] == null) { + rectEachCellBoundData[i][j] = new Rect(cellLeftX, cellTopY, cellRightX, cellBottomY); + } else { + rectEachCellBoundData[i][j].left = cellLeftX; + rectEachCellBoundData[i][j].top = cellTopY; + rectEachCellBoundData[i][j].right = cellRightX; + rectEachCellBoundData[i][j].bottom = cellBottomY; + } + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + + switch (event.getActionMasked()) { + + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + isScrollingHorizontally = false; + isScrollingVertically = false; + break; + } + + return gestureDetector.onTouchEvent(event); + //return true; + } + + private void updateLayoutChanges() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + if (!isInLayout()) { + requestLayout(); + } else { + invalidate(); + } + } else { + requestLayout(); + } + } + + /** + * Check if content width is bigger than view width + * + * @return true if content width is bigger than view width + */ + public boolean canScrollHorizontally() { + return actualContentRect.right > visibleContentRect.right; + } + + /** + * Check if content height is bigger than view height + * + * @return true if content height is bigger than view height + */ + public boolean canScrollVertically() { + return actualContentRect.bottom > visibleContentRect.bottom; + } + + /** + * Scroll horizontally + * + * @param distanceX distance to scroll + * @return true if horizontally scrolled, false otherwise + */ + public boolean scrollHorizontal(float distanceX) { + + if (!canScrollHorizontally() || distanceX == 0) { + return false; + } + + int newScrolledLeft = scrolledRect.left - (int) distanceX; + int newScrolledRight = scrolledRect.right - (int) distanceX; + + if (newScrolledLeft > 0) { + newScrolledLeft = 0; + newScrolledRight = actualContentRect.right; + } else if (newScrolledLeft < -(actualContentRect.right - visibleContentRect.right)) { + newScrolledLeft = -(actualContentRect.right - visibleContentRect.right); + newScrolledRight = visibleContentRect.right; + } + + if (scrolledRect.left == newScrolledLeft) { + return false; + } + scrolledRect.set(newScrolledLeft, scrolledRect.top, newScrolledRight, scrolledRect.bottom); + invalidate(); + return true; + } + + /** + * Scroll vertically + * + * @param distanceY distance to scroll + * @return true if vertically scrolled, false otherwise + */ + public boolean scrollVertical(float distanceY) { + + if (!canScrollVertically() || distanceY == 0) { + return false; + } + + int newScrolledTop = scrolledRect.top - (int) distanceY; + int newScrolledBottom = scrolledRect.bottom - (int) distanceY; + + if (newScrolledTop > 0) { + newScrolledTop = 0; + newScrolledBottom = actualContentRect.bottom; + } else if (newScrolledTop < -(actualContentRect.bottom - visibleContentRect.bottom)) { + newScrolledTop = -(actualContentRect.bottom - visibleContentRect.bottom); + newScrolledBottom = visibleContentRect.bottom; + } + + if (scrolledRect.top == newScrolledTop) { + return false; + } + scrolledRect.set(scrolledRect.left, newScrolledTop, scrolledRect.right, newScrolledBottom); + invalidate(); + return true; + } + + /** + * Scroll vertically & horizontal both side + * + * @param distanceX distance to scroll + * @param distanceY distance to scroll + * @return true if scrolled, false otherwise + */ + public boolean scroll2D(float distanceX, float distanceY) { + + boolean isScrollHappened = false; + int newScrolledLeft; + int newScrolledTop; + int newScrolledRight; + int newScrolledBottom; + + if (canScrollHorizontally()) { + newScrolledLeft = scrolledRect.left - (int) distanceX; + newScrolledRight = scrolledRect.right - (int) distanceX; + + if (newScrolledLeft > 0) { + newScrolledLeft = 0; + } + if (newScrolledLeft < -(actualContentRect.right - visibleContentRect.right)) { + newScrolledLeft = -(actualContentRect.right - visibleContentRect.right); + } + isScrollHappened = true; + } else { + newScrolledLeft = scrolledRect.left; + newScrolledRight = scrolledRect.right; + } + + if (canScrollVertically()) { + newScrolledTop = scrolledRect.top - (int) distanceY; + newScrolledBottom = scrolledRect.bottom - (int) distanceY; + + if (newScrolledTop > 0) { + newScrolledTop = 0; + } + if (newScrolledTop < -(actualContentRect.bottom - visibleContentRect.bottom)) { + newScrolledTop = -(actualContentRect.bottom - visibleContentRect.bottom); + } + isScrollHappened = true; + } else { + newScrolledTop = scrolledRect.top; + newScrolledBottom = scrolledRect.bottom; + } + + if (!isScrollHappened) { + return false; + } + + scrolledRect.set(newScrolledLeft, newScrolledTop, newScrolledRight, newScrolledBottom); + invalidate(); + return true; + } + + /** + * @return true if content are scrollable from top to bottom side + */ + public boolean canScrollTop() { + return scrolledRect.top < visibleContentRect.top; + } + + /** + * @return true if content are scrollable from bottom to top side + */ + public boolean canScrollBottom() { + return scrolledRect.bottom > visibleContentRect.bottom; + } + + /** + * @return true if content are scrollable from left to right side + */ + public boolean canScrollRight() { + return scrolledRect.right > visibleContentRect.right; + } + + /** + * @return true if content are scrollable from right to left side + */ + public boolean canScrollLeft() { + return scrolledRect.left < visibleContentRect.left; + } + + + // *************************** implemented NestedScrollChild methods ******************************************* + + @Override + public boolean isNestedScrollingEnabled() { + return nestedScrollingChildHelper.isNestedScrollingEnabled(); + } + + @Override + public void setNestedScrollingEnabled(boolean enabled) { + nestedScrollingChildHelper.setNestedScrollingEnabled(enabled); + } + + @Override + public boolean hasNestedScrollingParent() { + return nestedScrollingChildHelper.hasNestedScrollingParent(); + } + + /** + * default Nested scroll axis is ViewCompat.SCROLL_AXIS_NONE
+ * Nested scroll axis must be one of the
ViewCompat.SCROLL_AXIS_NONE
or ViewCompat.SCROLL_AXIS_HORIZONTAL
or ViewCompat.SCROLL_AXIS_VERTICAL + * + * @param nestedScrollAxis value of nested scroll direction + */ + public void setNestedScrollAxis(int nestedScrollAxis) { + switch (nestedScrollAxis) { + + case ViewCompat.SCROLL_AXIS_HORIZONTAL: + NESTED_SCROLL_AXIS = ViewCompat.SCROLL_AXIS_HORIZONTAL; + break; + case ViewCompat.SCROLL_AXIS_VERTICAL: + NESTED_SCROLL_AXIS = ViewCompat.SCROLL_AXIS_VERTICAL; + break; + default: + NESTED_SCROLL_AXIS = ViewCompat.SCROLL_AXIS_NONE; + break; + } + } + + @Override + public boolean startNestedScroll(int axes) { + return nestedScrollingChildHelper.startNestedScroll(axes); + } + + @Override + public void stopNestedScroll() { + nestedScrollingChildHelper.stopNestedScroll(); + } + + @Override + public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { + return nestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + return nestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { + return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean dispatchNestedPreFling(float velocityX, float velocityY) { + return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + nestedScrollingChildHelper.onDetachedFromWindow(); + } + + // *************************** Getter/Setter methods ******************************************* + + /** + * @return data which is previously set by setData(data) method. otherwise null. + */ + public Object[][] getData() { + return data; + } + + /** + * Set you table content data + * + * @param data table content data + */ + public void setData(Object[][] data) { + this.data = data; + rectEachCellBoundData = new Rect[data.length][data[0].length]; + updateLayoutChanges(); + } + + /** + * set the cell click event + * + * @param onTableCellClickListener tableCellClickListener + */ + public void setOnTableCellClickListener(OnTableCellClickListener onTableCellClickListener) { + this.onTableCellClickListener = onTableCellClickListener; + } + + /** + * enable or disable 2 directional scroll + * + * @param is2DScrollingEnabled true if you wants to enable 2 directional scroll + */ + public void setIs2DScrollingEnabled(boolean is2DScrollingEnabled) { + this.is2DScrollingEnabled = is2DScrollingEnabled; + } + + /** + * Check whether is 2 directional scroll is enabled or not + * + * @return true if 2 directional scroll is enabled + */ + public boolean is2DScrollingEnabled() { + return is2DScrollingEnabled; + } + + /** + * @return text color of the content cells + */ + public int getTextLabelColor() { + return textLabelColor; + } + + /** + * Set text color for content cells + * + * @param textLabelColor color + */ + public void setTextLabelColor(int textLabelColor) { + this.textLabelColor = textLabelColor; + invalidate(); + } + + /** + * @return text color of the header cells + */ + public int getTextHeaderColor() { + return textHeaderColor; + } + + /** + * Set text color for header cells + * + * @param textHeaderColor color + */ + public void setTextHeaderColor(int textHeaderColor) { + this.textHeaderColor = textHeaderColor; + invalidate(); + } + + /** + * @return color of the cell divider or cell border + */ + public int getDividerColor() { + return dividerColor; + } + + /** + * Set divider or border color for cell + * + * @param dividerColor color + */ + public void setDividerColor(int dividerColor) { + this.dividerColor = dividerColor; + invalidate(); + } + + /** + * @return text size in pixels of content cells + */ + public int getTextLabelSize() { + return textLabelSize; + } + + /** + * Set text size in pixels for content cells
+ * You can use {@link DisplayMatrixHelper#dpToPixels(Context, float)} method to convert dp to pixel + * + * @param textLabelSize text size in pixels + */ + public void setTextLabelSize(int textLabelSize) { + this.textLabelSize = textLabelSize; + updateLayoutChanges(); + } + + /** + * @return text header size in pixels of header cells + */ + public int getTextHeaderSize() { + return textHeaderSize; + } + + /** + * Set text header size in pixels for header cells
+ * You can use {@link DisplayMatrixHelper#dpToPixels(Context, float)} method to convert dp to pixel + * + * @param textHeaderSize text header size in pixels + */ + public void setTextHeaderSize(int textHeaderSize) { + this.textHeaderSize = textHeaderSize; + updateLayoutChanges(); + } + + /** + * @return divider thickness in pixels + */ + public int getDividerThickness() { + return dividerThickness; + } + + /** + * Set divider thickness size in pixels for all cells
+ * You can use {@link DisplayMatrixHelper#dpToPixels(Context, float)} method to convert dp to pixel + * + * @param dividerThickness divider thickness size in pixels + */ + public void setDividerThickness(int dividerThickness) { + this.dividerThickness = dividerThickness; + invalidate(); + } + + /** + * @return header cell's fill color + */ + public int getHeaderCellFillColor() { + return headerCellFillColor; + } + + /** + * Set header cell fill color + * + * @param headerCellFillColor color to fill in header cell + */ + public void setHeaderCellFillColor(int headerCellFillColor) { + this.headerCellFillColor = headerCellFillColor; + invalidate(); + } + + /** + * @return content cell's fill color + */ + public int getContentCellFillColor() { + return contentCellFillColor; + } + + /** + * Set content cell fill color + * + * @param contentCellFillColor color to fill in content cell + */ + public void setContentCellFillColor(int contentCellFillColor) { + this.contentCellFillColor = contentCellFillColor; + invalidate(); + } + + /** + * @return cell padding in pixels + */ + public int getCellPadding() { + return cellPadding; + } + + /** + * Set padding for all cell of table
+ * You can use {@link DisplayMatrixHelper#dpToPixels(Context, float)} method to convert dp to pixel + * + * @param cellPadding cell padding in pixels + */ + public void setCellPadding(int cellPadding) { + this.cellPadding = cellPadding; + updateLayoutChanges(); + } + + /** + * @return true if left header cell text are displayed vertically enabled + */ + public boolean isDisplayLeftHeadersVertically() { + return isDisplayLeftHeadersVertically; + } + + /** + * Set left header text display vertically or horizontal + * + * @param displayLeftHeadersVertically true if you wants to set left header text display vertically + */ + public void setDisplayLeftHeadersVertically(boolean displayLeftHeadersVertically) { + isDisplayLeftHeadersVertically = displayLeftHeadersVertically; + updateLayoutChanges(); + } + + /** + * @return true if you settled true for wrap height of each row + */ + public boolean isWrapHeightOfEachRow() { + return isWrapHeightOfEachRow; + } + + /** + * Set whether height of each row should wrap or not + * + * @param wrapHeightOfEachRow pass true if you wants to set each row should wrap the height + */ + public void setWrapHeightOfEachRow(boolean wrapHeightOfEachRow) { + isWrapHeightOfEachRow = wrapHeightOfEachRow; + updateLayoutChanges(); + } + + /** + * @return true if you settled true for wrap width of each column + */ + public boolean isWrapWidthOfEachColumn() { + return isWrapWidthOfEachColumn; + } + + /** + * Set whether width of each column should wrap or not + * + * @param wrapWidthOfEachColumn pass true if you wants to set each column should wrap the width + */ + public void setWrapWidthOfEachColumn(boolean wrapWidthOfEachColumn) { + isWrapWidthOfEachColumn = wrapWidthOfEachColumn; + updateLayoutChanges(); + } + + /** + * @return the Rect object which is visible area on screen + */ + public Rect getVisibleContentRect() { + return visibleContentRect; + } + + /** + * @return the Rect object which is last scrolled area from actual content rectangle + */ + public Rect getScrolledRect() { + return scrolledRect; + } + + /** + * @return the Rect object which is actual content area + */ + public Rect getActualContentRect() { + return actualContentRect; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/table/TableFragment.java b/android_app/app/src/main/java/com/health/openscale/gui/table/TableFragment.java index 2cb252e3..b7727475 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/table/TableFragment.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/table/TableFragment.java @@ -15,50 +15,41 @@ */ package com.health.openscale.gui.table; -import android.content.res.Configuration; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.SpannableStringBuilder; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TableRow; -import android.widget.TextView; import androidx.activity.OnBackPressedCallback; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.navigation.Navigation; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import com.health.openscale.R; import com.health.openscale.core.OpenScale; import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.gui.measurement.DateMeasurementView; import com.health.openscale.gui.measurement.MeasurementEntryFragment; import com.health.openscale.gui.measurement.MeasurementView; +import com.health.openscale.gui.measurement.TimeMeasurementView; import com.health.openscale.gui.measurement.UserMeasurementView; -import com.health.openscale.gui.utils.ColorUtil; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; import java.util.List; -import static android.util.TypedValue.COMPLEX_UNIT_DIP; - public class TableFragment extends Fragment { private View tableView; - private LinearLayout tableHeaderView; - private RecyclerView recyclerView; - private MeasurementsAdapter adapter; - private LinearLayoutManager layoutManager; + private StickyHeaderTableView tableDataView; private List measurementViews; + private List scaleMeasurementList; + private ArrayList iconList; public TableFragment() { @@ -68,25 +59,34 @@ public class TableFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { tableView = inflater.inflate(R.layout.fragment_table, container, false); - tableHeaderView = tableView.findViewById(R.id.tableHeaderView); - recyclerView = tableView.findViewById(R.id.tableDataView); + tableDataView = tableView.findViewById(R.id.tableDataView); - recyclerView.setHasFixedSize(true); - - layoutManager = new LinearLayoutManager(getContext()); - recyclerView.setLayoutManager(layoutManager); - - recyclerView.addItemDecoration(new DividerItemDecoration( - recyclerView.getContext(), layoutManager.getOrientation())); - - adapter = new MeasurementsAdapter(); - recyclerView.setAdapter(adapter); + tableDataView.setOnTableCellClickListener(new StickyHeaderTableView.OnTableCellClickListener() { + @Override + public void onTableCellClicked(int rowPosition, int columnPosition) { + if (rowPosition > 0) { + TableFragmentDirections.ActionNavTableToNavDataentry action = TableFragmentDirections.actionNavTableToNavDataentry(); + action.setMeasurementId(scaleMeasurementList.get(rowPosition-1).getId()); + action.setMode(MeasurementEntryFragment.DATA_ENTRY_MODE.VIEW); + Navigation.findNavController(getActivity(), R.id.nav_host_fragment).navigate(action); + } + } + }); measurementViews = MeasurementView.getMeasurementList( getContext(), MeasurementView.DateTimeOrder.FIRST); - for (MeasurementView measurement : measurementViews) { - measurement.setUpdateViews(false); + iconList = new ArrayList<>(); + + for (MeasurementView measurementView : measurementViews) { + if (!measurementView.isVisible() || measurementView instanceof UserMeasurementView || measurementView instanceof TimeMeasurementView) { + continue; + } + + // measurementView.setUpdateViews(false); + + measurementView.getIcon().setColorFilter(measurementView.getColor(), PorterDuff.Mode.SRC_ATOP); + iconList.add(measurementView.getIcon()); } OpenScale.getInstance().getScaleMeasurementsLiveData().observe(getViewLifecycleOwner(), new Observer>() { @@ -111,177 +111,53 @@ public class TableFragment extends Fragment { public void updateOnView(List scaleMeasurementList) { - tableHeaderView.removeAllViews(); + this.scaleMeasurementList = scaleMeasurementList; - final int iconHeight = pxImageDp(20); - ArrayList visibleMeasurements = new ArrayList<>(); + Object[][] tableData = new Object[scaleMeasurementList.size()+1][iconList.size()]; - for (MeasurementView measurement : measurementViews) { - if (!measurement.isVisible() || measurement instanceof UserMeasurementView) { - continue; - } - - - ImageView headerIcon = new ImageView(tableView.getContext()); - headerIcon.setImageDrawable(measurement.getIcon()); - headerIcon.setColorFilter(ColorUtil.getTintColor(tableView.getContext())); - headerIcon.setLayoutParams(new TableRow.LayoutParams(0, iconHeight, 1)); - headerIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - - tableHeaderView.addView(headerIcon); - - visibleMeasurements.add(measurement); + // add header icons to the first table data row + for (int j=0; j { - public static final int VIEW_TYPE_MEASUREMENT = 0; - public static final int VIEW_TYPE_YEAR = 1; - public class ViewHolder extends RecyclerView.ViewHolder { - public LinearLayout measurementView; - public ViewHolder(LinearLayout view) { - super(view); - measurementView = view; - } - } - - private List visibleMeasurements; - private List scaleMeasurements; - - public void setMeasurements(List visibleMeasurements, - List scaleMeasurements) { - this.visibleMeasurements = visibleMeasurements; - this.scaleMeasurements = new ArrayList<>(scaleMeasurements.size() + 10); - - Calendar calendar = Calendar.getInstance(); - if (!scaleMeasurements.isEmpty()) { - calendar.setTime(scaleMeasurements.get(0).getDateTime()); - } - calendar.set(calendar.get(Calendar.YEAR), 0, 1, 0, 0, 0); - calendar.set(calendar.MILLISECOND, 0); - - // Copy all measurements from input parameter to member variable and insert - // an extra "null" entry when the year changes. - Date yearStart = calendar.getTime(); - for (int i = 0; i < scaleMeasurements.size(); ++i) { - final ScaleMeasurement measurement = scaleMeasurements.get(i); - - if (measurement.getDateTime().before(yearStart)) { - this.scaleMeasurements.add(null); - - Calendar newCalendar = Calendar.getInstance(); - newCalendar.setTime(measurement.getDateTime()); - calendar.set(Calendar.YEAR, newCalendar.get(Calendar.YEAR)); - yearStart = calendar.getTime(); - } - - this.scaleMeasurements.add(measurement); - } - - notifyDataSetChanged(); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - LinearLayout row = new LinearLayout(getContext()); - row.setLayoutParams(new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT)); - - final int screenSize = getResources().getConfiguration() - .screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; - final boolean isSmallScreen = - screenSize != Configuration.SCREENLAYOUT_SIZE_XLARGE - && screenSize != Configuration.SCREENLAYOUT_SIZE_LARGE; - - final int count = viewType == VIEW_TYPE_YEAR ? 1 : visibleMeasurements.size(); - for (int i = 0; i < count; ++i) { - TextView column = new TextView(getContext()); - column.setLayoutParams(new LinearLayout.LayoutParams( - 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1)); - - if (viewType == VIEW_TYPE_MEASUREMENT) { - column.setMinLines(2); - column.setGravity(Gravity.CENTER_HORIZONTAL); - - if (isSmallScreen) { - column.setTextSize(COMPLEX_UNIT_DIP, 9); - } - } - else { - column.setPadding(0, 10, 0, 10); - column.setGravity(Gravity.CENTER); - column.setTextSize(COMPLEX_UNIT_DIP, 16); - } - - row.addView(column); - } - - return new ViewHolder(row); - } - - @Override - public void onBindViewHolder(ViewHolder holder, int position) { - LinearLayout row = holder.measurementView; - - final ScaleMeasurement measurement = scaleMeasurements.get(position); - if (measurement == null) { - ScaleMeasurement nextMeasurement = scaleMeasurements.get(position + 1); - Calendar calendar = Calendar.getInstance(); - calendar.setTime(nextMeasurement.getDateTime()); - - TextView column = (TextView) row.getChildAt(0); - column.setText(String.format("%d", calendar.get(Calendar.YEAR))); - return; - } - - ScaleMeasurement prevMeasurement = null; - if (position + 1 < scaleMeasurements.size()) { - prevMeasurement = scaleMeasurements.get(position + 1); - if (prevMeasurement == null) { - prevMeasurement = scaleMeasurements.get(position + 2); - } - } - - // Fill view with data - for (int i = 0; i < visibleMeasurements.size(); ++i) { - final MeasurementView view = visibleMeasurements.get(i); - view.loadFrom(measurement, prevMeasurement); - - SpannableStringBuilder string = new SpannableStringBuilder(); - string.append(view.getValueAsString(false)); - view.appendDiffValue(string, true); - - TextView column = (TextView) row.getChildAt(i); - column.setText(string); - } - - row.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - TableFragmentDirections.ActionNavTableToNavDataentry action = TableFragmentDirections.actionNavTableToNavDataentry(); - action.setMeasurementId(measurement.getId()); - action.setMode(MeasurementEntryFragment.DATA_ENTRY_MODE.VIEW); - Navigation.findNavController(getActivity(), R.id.nav_host_fragment).navigate(action); - } - }); - } - - @Override - public int getItemCount() { - return scaleMeasurements == null ? 0 : scaleMeasurements.size(); - } - - @Override - public int getItemViewType(int position) { - return scaleMeasurements.get(position) != null ? VIEW_TYPE_MEASUREMENT : VIEW_TYPE_YEAR; - } - } } diff --git a/android_app/app/src/main/res/layout/fragment_table.xml b/android_app/app/src/main/res/layout/fragment_table.xml index d21ccced..19bce22f 100644 --- a/android_app/app/src/main/res/layout/fragment_table.xml +++ b/android_app/app/src/main/res/layout/fragment_table.xml @@ -1,24 +1,27 @@ - + android:layout_height="match_parent"> - - - - - - + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:shtv_cellPadding="10dp" + app:shtv_dividerColor="?android:colorBackground" + app:shtv_dividerThickness="1dp" + app:shtv_headerCellFillColor="?android:colorBackground" + app:shtv_contentCellFillColor="?android:colorBackground" + app:shtv_is2DScrollEnabled="true" + app:shtv_isDisplayLeftHeadersVertically="false" + app:shtv_isWrapHeightOfEachRow="true" + app:shtv_isWrapWidthOfEachColumn="true" + app:shtv_textHeaderColor="?android:colorForeground" + app:shtv_textHeaderSize="14dp" + app:shtv_textLabelColor="?android:colorForeground" + app:shtv_textLabelSize="14dp" /> + diff --git a/android_app/app/src/main/res/values/styles.xml b/android_app/app/src/main/res/values/styles.xml index 57015550..fd89d2fe 100644 --- a/android_app/app/src/main/res/values/styles.xml +++ b/android_app/app/src/main/res/values/styles.xml @@ -37,4 +37,26 @@ @android:string/ok @android:string/cancel + + + + + + + + + + + + + + + + + + + + + + From 4f152ba7ec66a1d6bd0b098b91dda80b8f183e96 Mon Sep 17 00:00:00 2001 From: oliexdev Date: Sun, 29 Jan 2023 17:53:46 +0100 Subject: [PATCH 8/8] - some optimization to speed the table view calculation - added progress bar to table view --- .../health/openscale/gui/MainActivity.java | 30 +++++++++++ .../gui/measurement/FloatMeasurementView.java | 53 +++++++++++-------- .../gui/measurement/MeasurementView.java | 3 +- .../gui/overview/OverviewAdapter.java | 3 ++ .../gui/table/StickyHeaderTableView.java | 23 ++++---- .../openscale/gui/table/TableFragment.java | 34 +++++++----- .../src/main/res/layout/fragment_table.xml | 13 ++++- 7 files changed, 110 insertions(+), 49 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java index d5ef1ffc..3f010b70 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java @@ -85,8 +85,10 @@ import com.health.openscale.gui.preferences.UserSettingsFragment; import com.health.openscale.gui.slides.AppIntroActivity; import java.io.File; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; @@ -1069,4 +1071,32 @@ public class MainActivity extends AppCompatActivity connectToBluetooth(); } }); + + // Generate random dummy measurements - ONLY FOR TESTING PURPOSE + private void generateDummyMeasurements(int measurementCount) { + for (int i=0; i 0.0f) { - color = (value > getScaleUser().getGoalWeight()) ? Color.RED : Color.GREEN; - } else if (diff < 0.0f) { - color = (value < getScaleUser().getGoalWeight()) ? Color.RED : Color.GREEN; + // skip evaluation to speed the calculation up (e.g. not needed for table view) + if (isEvalOn) { + // change color depending on if you are going towards or away from your weight goal + if (this instanceof WeightMeasurementView) { + if (diff > 0.0f) { + color = (value > getScaleUser().getGoalWeight()) ? Color.RED : Color.GREEN; + } else if (diff < 0.0f) { + color = (value < getScaleUser().getGoalWeight()) ? Color.RED : Color.GREEN; + } } - } - final float evalValue = maybeConvertToOriginalValue(value); + final float evalValue = maybeConvertToOriginalValue(value); - EvaluationSheet evalSheet = new EvaluationSheet(getScaleUser(), dateTime); - evaluationResult = evaluateSheet(evalSheet, evalValue); + EvaluationSheet evalSheet = new EvaluationSheet(getScaleUser(), dateTime); + evaluationResult = evaluateSheet(evalSheet, evalValue); - if (evaluationResult != null) { - switch (evaluationResult.eval_state) { - case LOW: - color = (diff > 0.0f) ? Color.GREEN : Color.RED; - break; - case HIGH: - color = (diff < 0.0f) ? Color.GREEN : Color.RED; - break; - case NORMAL: - color = Color.GREEN; - break; + if (evaluationResult != null) { + switch (evaluationResult.eval_state) { + case LOW: + color = (diff > 0.0f) ? Color.GREEN : Color.RED; + break; + case HIGH: + color = (diff < 0.0f) ? Color.GREEN : Color.RED; + break; + case NORMAL: + color = Color.GREEN; + break; + } } } @@ -490,6 +493,12 @@ public abstract class FloatMeasurementView extends MeasurementView { Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } + + @Override + public void appendDiffValue(final SpannableStringBuilder text, boolean newLine) { + appendDiffValue(text, newLine, true); + } + @Override protected boolean isEditable() { if (useAutoValue()) { diff --git a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java index 942bfd4b..c4d972b5 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/measurement/MeasurementView.java @@ -286,7 +286,8 @@ public abstract class MeasurementView extends TableLayout { public CharSequence getName() { return nameView.getText(); } public abstract String getValueAsString(boolean withUnit); - public void appendDiffValue(SpannableStringBuilder builder, boolean newLine) { } + public void appendDiffValue(final SpannableStringBuilder builder, boolean newLine, boolean isEvalOn) { } + public void appendDiffValue(final SpannableStringBuilder builder, boolean newLine) { } public Drawable getIcon() { return iconView.getDrawable(); } public int getIconResource() { return iconId; } public void setBackgroundIconColor(int color) { diff --git a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java index 98543a61..6b0a4f49 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/overview/OverviewAdapter.java @@ -85,6 +85,9 @@ class OverviewAdapter extends RecyclerView.Adapter { @Override public void onBindViewHolder(@NonNull OverviewAdapter.ViewHolder holder, int position) { + holder.measurementHighlightViews.removeAllViews(); + holder.measurementViews.removeAllViews(); + ScaleMeasurement scaleMeasurement = scaleMeasurementList.get(position); ScaleMeasurement prevScaleMeasurement; diff --git a/android_app/app/src/main/java/com/health/openscale/gui/table/StickyHeaderTableView.java b/android_app/app/src/main/java/com/health/openscale/gui/table/StickyHeaderTableView.java index 22326791..10c6aecb 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/table/StickyHeaderTableView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/table/StickyHeaderTableView.java @@ -36,7 +36,6 @@ import com.health.openscale.gui.utils.ColorUtil; */ public class StickyHeaderTableView extends View implements NestedScrollingChild { - private final Paint paintStrokeRect = new Paint(); private final Paint paintHeaderCellFillRect = new Paint(); private final Paint paintContentCellFillRect = new Paint(); @@ -45,6 +44,9 @@ public class StickyHeaderTableView extends View implements NestedScrollingChild private final TextPaint paintHeaderText = new TextPaint(); private final Rect textRectBounds = new Rect(); + + private int maxMeasure = 0; + /** * Visible rect size of view which is displayed on screen */ @@ -483,11 +485,16 @@ public class StickyHeaderTableView extends View implements NestedScrollingChild * Required for onMeasure() method */ private void updateMaxWidthHeightOfCell() { + // call only once otherwise it is very cpu time consuming + if (maxMeasure > 0) { + return; + } + maxMeasure++; maxWidthOfCell = 0; maxHeightOfCell = 0; - maxHeightSparseIntArray = new SparseIntArray(); - maxWidthSparseIntArray = new SparseIntArray(); + maxHeightSparseIntArray.clear(); + maxWidthSparseIntArray.clear(); final int doubleCellPadding = cellPadding + cellPadding; @@ -542,7 +549,6 @@ public class StickyHeaderTableView extends View implements NestedScrollingChild if (maxHeightSparseIntArray.get(i, 0) < textRectBounds.height()) { maxHeightSparseIntArray.put(i, textRectBounds.height()); } - } else if (j == 0) { // Left headers cells if (data[i][j] instanceof String) { @@ -557,10 +563,6 @@ public class StickyHeaderTableView extends View implements NestedScrollingChild } } paintHeaderText.getTextBounds(str, 0, str.length(), textRectBounds); - StaticLayout staticLayout = StaticLayout.Builder.obtain(str, 0, str.length(), paintHeaderText, textRectBounds.width()).build(); - - //textRectBounds.right = 50; - textRectBounds.bottom = staticLayout.getHeight(); } else if (data[i][j] instanceof Drawable) { Drawable icon = (Drawable) data[i][j]; textRectBounds.set(0,0,icon.getIntrinsicWidth(), icon.getIntrinsicHeight() / 2); @@ -613,9 +615,6 @@ public class StickyHeaderTableView extends View implements NestedScrollingChild } } paintLabelText.getTextBounds(str, 0, str.length(), textRectBounds); - StaticLayout staticLayout = StaticLayout.Builder.obtain(str, 0, str.length(), paintLabelText, textRectBounds.width()).build(); - - textRectBounds.bottom = staticLayout.getHeight(); } else if (data[i][j] instanceof Drawable) { Drawable icon = (Drawable) data[i][j]; textRectBounds.set(0,0,icon.getIntrinsicWidth(), icon.getIntrinsicHeight() / 2); @@ -659,10 +658,12 @@ public class StickyHeaderTableView extends View implements NestedScrollingChild @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); + if (data == null) { return; } + int cellLeftX; int cellTopY = scrolledRect.top; int cellRightX; diff --git a/android_app/app/src/main/java/com/health/openscale/gui/table/TableFragment.java b/android_app/app/src/main/java/com/health/openscale/gui/table/TableFragment.java index b7727475..7a8ac115 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/table/TableFragment.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/table/TableFragment.java @@ -22,6 +22,7 @@ import android.text.SpannableStringBuilder; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ProgressBar; import androidx.activity.OnBackPressedCallback; import androidx.fragment.app.Fragment; @@ -45,11 +46,16 @@ import java.util.List; public class TableFragment extends Fragment { private View tableView; + private ProgressBar progressBar; private StickyHeaderTableView tableDataView; private List measurementViews; private List scaleMeasurementList; private ArrayList iconList; + private final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT); + private final DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT); + private final DateFormat dayFormat = new SimpleDateFormat("EE"); + private final SpannableStringBuilder contentFormat = new SpannableStringBuilder(); public TableFragment() { @@ -59,7 +65,9 @@ public class TableFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { tableView = inflater.inflate(R.layout.fragment_table, container, false); + progressBar = tableView.findViewById(R.id.progressBarTable); tableDataView = tableView.findViewById(R.id.tableDataView); + progressBar.setVisibility(View.VISIBLE); tableDataView.setOnTableCellClickListener(new StickyHeaderTableView.OnTableCellClickListener() { @Override @@ -83,7 +91,7 @@ public class TableFragment extends Fragment { continue; } - // measurementView.setUpdateViews(false); + measurementView.setUpdateViews(false); measurementView.getIcon().setColorFilter(measurementView.getColor(), PorterDuff.Mode.SRC_ATOP); iconList.add(measurementView.getIcon()); @@ -105,7 +113,6 @@ public class TableFragment extends Fragment { requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), onBackPressedCallback); - return tableView; } @@ -120,9 +127,8 @@ public class TableFragment extends Fragment { tableData[0][j] = iconList.get(j); } - int i = 0; - for (ScaleMeasurement scaleMeasurement : scaleMeasurementList) { - int j=0; + for (int i = 0; i + +