1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-16 21:54:05 +02:00

Merge pull request #915 from oliexdev/RecyclerView

Refactoring overview and table fragment
This commit is contained in:
OliE
2023-01-29 17:58:39 +01:00
committed by GitHub
23 changed files with 2236 additions and 492 deletions

View File

@@ -86,8 +86,10 @@ import com.health.openscale.gui.preferences.UserSettingsFragment;
import com.health.openscale.gui.slides.AppIntroActivity; import com.health.openscale.gui.slides.AppIntroActivity;
import java.io.File; import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -1070,4 +1072,32 @@ public class MainActivity extends AppCompatActivity
connectToBluetooth(); connectToBluetooth();
} }
}); });
// Generate random dummy measurements - ONLY FOR TESTING PURPOSE
private void generateDummyMeasurements(int measurementCount) {
for (int i=0; i<measurementCount; i++) {
ScaleMeasurement scaleMeasurement = new ScaleMeasurement();
SimpleDateFormat dfDateTime = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss",Locale.getDefault());
int year = randBetween(2005, 2023);// Here you can set Range of years you need
int month = randBetween(0, 11);
int hour = randBetween(9, 22); //Hours will be displayed in between 9 to 22
int min = randBetween(0, 59);
int sec = randBetween(0, 59);
GregorianCalendar gc = new GregorianCalendar(year, month, 1);
int day = randBetween(1, gc.getActualMaximum(gc.DAY_OF_MONTH));
gc.set(year, month, day, hour, min,sec);
scaleMeasurement.setDateTime(gc.getTime());
scaleMeasurement.setWeight(randBetween(30, 140));
OpenScale.getInstance().addScaleMeasurement(scaleMeasurement, true);
}
}
private static int randBetween(int start, int end) {
return start + (int)Math.round(Math.random() * (end - start));
}
} }

View File

@@ -55,6 +55,10 @@ public class ChartActionBarView extends HorizontalScrollView {
} }
private void init() { private void init() {
if (isInEditMode()) {
return;
}
actionBarView = new LinearLayout(getContext()); actionBarView = new LinearLayout(getContext());
actionBarView.setOrientation(LinearLayout.HORIZONTAL); actionBarView.setOrientation(LinearLayout.HORIZONTAL);
actionBarView.setBackgroundColor(ColorUtil.COLOR_BLACK); actionBarView.setBackgroundColor(ColorUtil.COLOR_BLACK);

View File

@@ -16,6 +16,8 @@
package com.health.openscale.gui.measurement; package com.health.openscale.gui.measurement;
import static java.time.temporal.ChronoUnit.DAYS;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Color; import android.graphics.Color;
@@ -53,8 +55,6 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Stack; import java.util.Stack;
import static java.time.temporal.ChronoUnit.DAYS;
public class ChartMeasurementView extends LineChart { public class ChartMeasurementView extends LineChart {
public enum ViewMode { public enum ViewMode {
DAY_OF_MONTH, DAY_OF_MONTH,
@@ -195,6 +195,10 @@ public class ChartMeasurementView extends LineChart {
} }
private void initChart() { private void initChart() {
if (isInEditMode()) {
return;
}
prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
openScale = OpenScale.getInstance(); openScale = OpenScale.getInstance();
measurementViews = MeasurementView.getMeasurementList(getContext(), MeasurementView.DateTimeOrder.NONE); measurementViews = MeasurementView.getMeasurementList(getContext(), MeasurementView.DateTimeOrder.NONE);

View File

@@ -23,6 +23,7 @@ import android.widget.EditText;
import com.health.openscale.R; import com.health.openscale.R;
import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.gui.utils.ColorUtil;
public class CommentMeasurementView extends MeasurementView { public class CommentMeasurementView extends MeasurementView {
// Don't change key value, it may be stored persistent in preferences // 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); state.putString(getKey(), comment);
} }
@Override
public int getColor() { return ColorUtil.COLOR_GRAY; };
@Override @Override
public String getValueAsString(boolean withUnit) { public String getValueAsString(boolean withUnit) {
return comment; return comment;

View File

@@ -23,6 +23,7 @@ import android.widget.DatePicker;
import com.health.openscale.R; import com.health.openscale.R;
import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.gui.utils.ColorUtil;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.Calendar; import java.util.Calendar;
@@ -88,6 +89,9 @@ public class DateMeasurementView extends MeasurementView {
state.putLong(getKey(), date.getTime()); state.putLong(getKey(), date.getTime());
} }
@Override
public int getColor() { return ColorUtil.COLOR_GRAY; };
@Override @Override
public String getValueAsString(boolean withUnit) { public String getValueAsString(boolean withUnit) {
return dateFormat.format(date); return dateFormat.format(date);

View File

@@ -426,7 +426,7 @@ public abstract class FloatMeasurementView extends MeasurementView {
} }
@Override @Override
public void appendDiffValue(SpannableStringBuilder text, boolean newLine) { public void appendDiffValue(final SpannableStringBuilder text, boolean newLine, boolean isEvalOn) {
if (previousValue < 0.0f) { if (previousValue < 0.0f) {
return; return;
} }
@@ -446,31 +446,34 @@ public abstract class FloatMeasurementView extends MeasurementView {
color = Color.GRAY; color = Color.GRAY;
} }
// change color depending on if you are going towards or away from your weight goal // skip evaluation to speed the calculation up (e.g. not needed for table view)
if (this instanceof WeightMeasurementView) { if (isEvalOn) {
if (diff> 0.0f) { // change color depending on if you are going towards or away from your weight goal
color = (value > getScaleUser().getGoalWeight()) ? Color.RED : Color.GREEN; if (this instanceof WeightMeasurementView) {
} else if (diff < 0.0f) { if (diff > 0.0f) {
color = (value < getScaleUser().getGoalWeight()) ? Color.RED : Color.GREEN; 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); EvaluationSheet evalSheet = new EvaluationSheet(getScaleUser(), dateTime);
evaluationResult = evaluateSheet(evalSheet, evalValue); evaluationResult = evaluateSheet(evalSheet, evalValue);
if (evaluationResult != null) { if (evaluationResult != null) {
switch (evaluationResult.eval_state) { switch (evaluationResult.eval_state) {
case LOW: case LOW:
color = (diff > 0.0f) ? Color.GREEN : Color.RED; color = (diff > 0.0f) ? Color.GREEN : Color.RED;
break; break;
case HIGH: case HIGH:
color = (diff < 0.0f) ? Color.GREEN : Color.RED; color = (diff < 0.0f) ? Color.GREEN : Color.RED;
break; break;
case NORMAL: case NORMAL:
color = Color.GREEN; color = Color.GREEN;
break; break;
}
} }
} }
@@ -490,6 +493,12 @@ public abstract class FloatMeasurementView extends MeasurementView {
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
@Override
public void appendDiffValue(final SpannableStringBuilder text, boolean newLine) {
appendDiffValue(text, newLine, true);
}
@Override @Override
protected boolean isEditable() { protected boolean isEditable() {
if (useAutoValue()) { if (useAutoValue()) {
@@ -539,9 +548,6 @@ public abstract class FloatMeasurementView extends MeasurementView {
return ""; return "";
} }
@Override
public boolean hasExtraPreferences() { return true; }
private class ListPreferenceWithNeutralButton extends ListPreference { private class ListPreferenceWithNeutralButton extends ListPreference {
ListPreferenceWithNeutralButton(Context context) { ListPreferenceWithNeutralButton(Context context) {
super(context); super(context);
@@ -568,6 +574,7 @@ public abstract class FloatMeasurementView extends MeasurementView {
@Override @Override
public void prepareExtraPreferencesScreen(PreferenceScreen screen) { public void prepareExtraPreferencesScreen(PreferenceScreen screen) {
super.prepareExtraPreferencesScreen(screen);
MeasurementViewSettings settings = getSettings(); MeasurementViewSettings settings = getSettings();
CheckBoxPreference rightAxis = new CheckBoxPreference(screen.getContext()); CheckBoxPreference rightAxis = new CheckBoxPreference(screen.getContext());

View File

@@ -15,6 +15,11 @@
*/ */
package com.health.openscale.gui.measurement; 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.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@@ -40,6 +45,7 @@ import android.widget.TableRow;
import android.widget.TextView; import android.widget.TextView;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.preference.CheckBoxPreference;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
@@ -53,11 +59,6 @@ import com.health.openscale.gui.utils.ColorUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 abstract class MeasurementView extends TableLayout {
public enum MeasurementViewMode {VIEW, EDIT, ADD, STATISTIC} public enum MeasurementViewMode {VIEW, EDIT, ADD, STATISTIC}
@@ -207,7 +208,7 @@ public abstract class MeasurementView extends TableLayout {
iconView.setImageResource(iconId); iconView.setImageResource(iconId);
iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
iconView.setPadding(25,25,25,25); iconView.setPadding(15,15,15,15);
iconView.setColorFilter(ColorUtil.COLOR_BLACK); iconView.setColorFilter(ColorUtil.COLOR_BLACK);
iconView.setBackground(iconViewBackground); iconView.setBackground(iconViewBackground);
@@ -285,7 +286,8 @@ public abstract class MeasurementView extends TableLayout {
public CharSequence getName() { return nameView.getText(); } public CharSequence getName() { return nameView.getText(); }
public abstract String getValueAsString(boolean withUnit); 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 Drawable getIcon() { return iconView.getDrawable(); }
public int getIconResource() { return iconId; } public int getIconResource() { return iconId; }
public void setBackgroundIconColor(int color) { public void setBackgroundIconColor(int color) {
@@ -357,6 +359,8 @@ public abstract class MeasurementView extends TableLayout {
return background.getColor(); return background.getColor();
} }
abstract public int getColor();
protected void showEvaluatorRow(boolean show) { protected void showEvaluatorRow(boolean show) {
if (show) { if (show) {
evaluatorRow.setVisibility(View.VISIBLE); evaluatorRow.setVisibility(View.VISIBLE);
@@ -423,8 +427,16 @@ public abstract class MeasurementView extends TableLayout {
} }
public String getPreferenceSummary() { return ""; } public String getPreferenceSummary() { return ""; }
public boolean hasExtraPreferences() { return false; } public void prepareExtraPreferencesScreen(PreferenceScreen screen) {
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 View getInputView();
protected abstract boolean validateAndSetInput(View view); protected abstract boolean validateAndSetInput(View view);

View File

@@ -27,6 +27,7 @@ public class MeasurementViewSettings {
private final String key; private final String key;
private static final String PREFERENCE_SUFFIX_ENABLE = "Enable"; 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_IN_OVERVIEW_GRAPH = "InOverviewGraph";
private static final String PREFERENCE_SUFFIX_ON_RIGHT_AXIS = "OnRightAxis"; private static final String PREFERENCE_SUFFIX_ON_RIGHT_AXIS = "OnRightAxis";
private static final String PREFERENCE_SUFFIX_IN_GRAPH = "InGraph"; private static final String PREFERENCE_SUFFIX_IN_GRAPH = "InGraph";
@@ -116,6 +117,26 @@ public class MeasurementViewSettings {
return isEnabledIgnoringDependencies() && areDependenciesEnabled(); 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() { public String getInOverviewGraphKey() {
return getPreferenceKey(PREFERENCE_SUFFIX_IN_OVERVIEW_GRAPH); return getPreferenceKey(PREFERENCE_SUFFIX_IN_OVERVIEW_GRAPH);
} }

View File

@@ -22,6 +22,7 @@ import android.widget.TimePicker;
import com.health.openscale.R; import com.health.openscale.R;
import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.gui.utils.ColorUtil;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.Calendar; import java.util.Calendar;
@@ -89,6 +90,9 @@ public class TimeMeasurementView extends MeasurementView {
state.putLong(getKey(), time.getTime()); state.putLong(getKey(), time.getTime());
} }
@Override
public int getColor() { return ColorUtil.COLOR_GRAY; };
@Override @Override
public String getValueAsString(boolean withUnit) { public String getValueAsString(boolean withUnit) {
return timeFormat.format(time); return timeFormat.format(time);

View File

@@ -25,6 +25,7 @@ import com.health.openscale.R;
import com.health.openscale.core.OpenScale; import com.health.openscale.core.OpenScale;
import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser; import com.health.openscale.core.datatypes.ScaleUser;
import com.health.openscale.gui.utils.ColorUtil;
import java.util.ArrayList; import java.util.ArrayList;
@@ -80,6 +81,9 @@ public class UserMeasurementView extends MeasurementView {
state.putInt(getKey(), userId); state.putInt(getKey(), userId);
} }
@Override
public int getColor() { return ColorUtil.COLOR_GRAY; };
@Override @Override
public String getValueAsString(boolean withUnit) { public String getValueAsString(boolean withUnit) {
return openScale.getScaleUser(userId).getUserName(); return openScale.getScaleUser(userId).getUserName();

View File

@@ -0,0 +1,196 @@
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;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.RecyclerView;
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;
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.text.SimpleDateFormat;
import java.util.List;
class OverviewAdapter extends RecyclerView.Adapter<OverviewAdapter.ViewHolder> {
private Activity activity;
private List<ScaleMeasurement> scaleMeasurementList;
public OverviewAdapter(Activity activity, List<ScaleMeasurement> scaleMeasurementList) {
this.activity = activity;
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);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull OverviewAdapter.ViewHolder holder, int position) {
holder.measurementHighlightViews.removeAllViews();
holder.measurementViews.removeAllViews();
ScaleMeasurement scaleMeasurement = scaleMeasurementList.get(position);
ScaleMeasurement prevScaleMeasurement;
// 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.showEntry.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);
}
});
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) {
TransitionManager.beginDelayedTransition(holder.measurementViews, new AutoTransition());
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));
}
}
});
holder.dateView.setText(DateFormat.getDateInstance(DateFormat.MEDIUM).format(scaleMeasurement.getDateTime()) +
" (" + new SimpleDateFormat("EE").format(scaleMeasurement.getDateTime()) + ") "+
DateFormat.getTimeInstance(DateFormat.SHORT).format(scaleMeasurement.getDateTime()));
List<MeasurementView> measurementViewList = MeasurementView.getMeasurementList(activity, MeasurementView.DateTimeOrder.LAST);
for (MeasurementView measurementView : measurementViewList) {
if (measurementView instanceof DateMeasurementView || measurementView instanceof TimeMeasurementView || measurementView instanceof UserMeasurementView) {
measurementView.setVisible(false);
}
else if (measurementView.isVisible()) {
measurementView.loadFrom(scaleMeasurement, prevScaleMeasurement);
if (measurementView.getSettings().isSticky()) {
holder.measurementHighlightViews.addView(measurementView);
} else{
holder.measurementViews.addView(measurementView);
}
}
}
}
@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;
ImageView showEntry;
ImageView editEntry;
ImageView deleteEntry;
TableLayout measurementHighlightViews;
ImageView expandMeasurementView;
TableLayout measurementViews;
public ViewHolder(@NonNull View itemView) {
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);
measurementViews.setVisibility(View.GONE);
}
}
}

View File

@@ -15,12 +15,14 @@
*/ */
package com.health.openscale.gui.overview; package com.health.openscale.gui.overview;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Color; import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; 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.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@@ -30,14 +32,15 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TableLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer; 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.data.Entry;
import com.github.mikephil.charting.highlight.Highlight; import com.github.mikephil.charting.highlight.Highlight;
@@ -46,13 +49,14 @@ import com.health.openscale.R;
import com.health.openscale.core.OpenScale; import com.health.openscale.core.OpenScale;
import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser; 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.ChartActionBarView;
import com.health.openscale.gui.measurement.ChartMeasurementView; import com.health.openscale.gui.measurement.ChartMeasurementView;
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.utils.ColorUtil;
import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.List; import java.util.List;
public class OverviewFragment extends Fragment { public class OverviewFragment extends Fragment {
@@ -60,8 +64,8 @@ public class OverviewFragment extends Fragment {
private TextView txtTitleUser; private TextView txtTitleUser;
private List<MeasurementView> lastMeasurementViews; private RecyclerView recyclerView;
private OverviewAdapter overviewAdapter;
private ChartMeasurementView chartView; private ChartMeasurementView chartView;
private ChartActionBarView chartActionBarView; private ChartActionBarView chartActionBarView;
@@ -69,9 +73,9 @@ public class OverviewFragment extends Fragment {
private PopupMenu rangePopupMenu; private PopupMenu rangePopupMenu;
private ImageView showEntry; private TextView differenceWeightView;
private ImageView editEntry; private TextView initialWeightView;
private ImageView deleteEntry; private TextView goalWeightView;
private ScaleUser currentScaleUser; private ScaleUser currentScaleUser;
@@ -79,6 +83,7 @@ public class OverviewFragment extends Fragment {
private SharedPreferences prefs; private SharedPreferences prefs;
private List<ScaleMeasurement> scaleMeasurementList;
private ScaleMeasurement markedMeasurement; private ScaleMeasurement markedMeasurement;
@Override @Override
@@ -87,12 +92,20 @@ public class OverviewFragment extends Fragment {
prefs = PreferenceManager.getDefaultSharedPreferences(overviewView.getContext()); 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);
chartView = overviewView.findViewById(R.id.chartView); chartView = overviewView.findViewById(R.id.chartView);
chartView.setOnChartValueSelectedListener(new onChartSelectedListener()); chartView.setOnChartValueSelectedListener(new onChartSelectedListener());
chartView.setProgressBar(overviewView.findViewById(R.id.progressBar)); chartView.setProgressBar(overviewView.findViewById(R.id.progressBar));
chartView.setIsInGraphKey(false); 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 = overviewView.findViewById(R.id.chartActionBar);
chartActionBarView.setIsInGraphKey(false); chartActionBarView.setIsInGraphKey(false);
@@ -178,14 +191,12 @@ public class OverviewFragment extends Fragment {
chartActionBarView.setVisibility(View.GONE); chartActionBarView.setVisibility(View.GONE);
} }
lastMeasurementViews = MeasurementView.getMeasurementList( recyclerView = overviewView.findViewById(R.id.recyclerView);
getContext(), MeasurementView.DateTimeOrder.LAST); LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
layoutManager.setInitialPrefetchItemCount(5);
TableLayout tableOverviewLayout = overviewView.findViewById(R.id.tableLayoutMeasurements); layoutManager.setReverseLayout(true);
layoutManager.setStackFromEnd(true);
for (MeasurementView measurement : lastMeasurementViews) { recyclerView.setLayoutManager(layoutManager);
tableOverviewLayout.addView(measurement);
}
spinUserAdapter = new ArrayAdapter<>(overviewView.getContext(), R.layout.spinner_item, new ArrayList<String>()); spinUserAdapter = new ArrayAdapter<>(overviewView.getContext(), R.layout.spinner_item, new ArrayList<String>());
spinUser.setAdapter(spinUserAdapter); spinUser.setAdapter(spinUserAdapter);
@@ -198,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); chartView.animateY(700);
OpenScale.getInstance().getScaleMeasurementsLiveData().observe(getViewLifecycleOwner(), new Observer<List<ScaleMeasurement>>() { OpenScale.getInstance().getScaleMeasurementsLiveData().observe(getViewLifecycleOwner(), new Observer<List<ScaleMeasurement>>() {
@@ -253,14 +231,12 @@ public class OverviewFragment extends Fragment {
} }
public void updateOnView(List<ScaleMeasurement> scaleMeasurementList) { public void updateOnView(List<ScaleMeasurement> scaleMeasurementList) {
if (scaleMeasurementList.isEmpty()) { this.scaleMeasurementList = scaleMeasurementList;
markedMeasurement = new ScaleMeasurement();
} else { overviewAdapter = new OverviewAdapter(getActivity(), scaleMeasurementList);
markedMeasurement = scaleMeasurementList.get(0); recyclerView.setAdapter(overviewAdapter);
}
updateUserSelection(); updateUserSelection();
updateMesurementViews(markedMeasurement);
chartView.updateMeasurementList(scaleMeasurementList); chartView.updateMeasurementList(scaleMeasurementList);
updateChartView(); updateChartView();
} }
@@ -270,17 +246,7 @@ public class OverviewFragment extends Fragment {
chartView.setViewRange(selectedRangeMode); 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() { private void updateUserSelection() {
currentScaleUser = OpenScale.getInstance().getSelectedScaleUser(); currentScaleUser = OpenScale.getInstance().getSelectedScaleUser();
spinUserAdapter.clear(); spinUserAdapter.clear();
@@ -300,8 +266,75 @@ public class OverviewFragment extends Fragment {
// Hide user selector when there is only one user // Hide user selector when there is only one user
int visibility = spinUserAdapter.getCount() < 2 ? View.GONE : View.VISIBLE; int visibility = spinUserAdapter.getCount() < 2 ? View.GONE : View.VISIBLE;
txtTitleUser.setVisibility(visibility);
spinUser.setVisibility(visibility); spinUser.setVisibility(visibility);
WeightMeasurementView weightMeasurementView = new WeightMeasurementView(getContext());
ScaleMeasurement initialWeightMeasurement = OpenScale.getInstance().getLastScaleMeasurement();
if (initialWeightMeasurement == null) {
initialWeightMeasurement = new ScaleMeasurement();
}
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 { private class onChartSelectedListener implements OnChartValueSelectedListener {
@@ -313,26 +346,15 @@ public class OverviewFragment extends Fragment {
markedMeasurement = (ScaleMeasurement)extraData[0]; markedMeasurement = (ScaleMeasurement)extraData[0];
//MeasurementView measurementView = (MeasurementView)extraData[1]; //MeasurementView measurementView = (MeasurementView)extraData[1];
showEntry.setEnabled(true); if (scaleMeasurementList.contains(markedMeasurement)) {
editEntry.setEnabled(true); TransitionManager.beginDelayedTransition(recyclerView, new ChangeScroll());
deleteEntry.setEnabled(true); recyclerView.scrollToPosition(scaleMeasurementList.indexOf(markedMeasurement));
}
showEntry.setColorFilter(ColorUtil.COLOR_BLUE);
editEntry.setColorFilter(ColorUtil.COLOR_GREEN);
deleteEntry.setColorFilter(ColorUtil.COLOR_RED);
updateMesurementViews(markedMeasurement);
} }
@Override @Override
public void onNothingSelected() { public void onNothingSelected() {
showEntry.setEnabled(false); // empty
editEntry.setEnabled(false);
deleteEntry.setEnabled(false);
showEntry.setColorFilter(ColorUtil.COLOR_GRAY);
editEntry.setColorFilter(ColorUtil.COLOR_GRAY);
deleteEntry.setColorFilter(ColorUtil.COLOR_GRAY);
} }
} }
@@ -358,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);
}
} }

View File

@@ -245,13 +245,6 @@ public class MeasurementPreferences extends PreferenceFragmentCompat {
public boolean onSingleTapUp(MotionEvent e) { public boolean onSingleTapUp(MotionEvent e) {
boundView.setPressed(false); boundView.setPressed(false);
if (!measurement.hasExtraPreferences()) {
if (switchView.getVisibility() == View.VISIBLE) {
switchView.toggle();
}
return true;
}
// Must be enabled to show extra preferences screen // Must be enabled to show extra preferences screen
if (!measurement.getSettings().isEnabled()) { if (!measurement.getSettings().isEnabled()) {
return true; return true;

View File

@@ -15,50 +15,47 @@
*/ */
package com.health.openscale.gui.table; 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.os.Bundle;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ProgressBar;
import android.widget.LinearLayout;
import android.widget.TableRow;
import android.widget.TextView;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.navigation.Navigation; 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.R;
import com.health.openscale.core.OpenScale; import com.health.openscale.core.OpenScale;
import com.health.openscale.core.datatypes.ScaleMeasurement; 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.MeasurementEntryFragment;
import com.health.openscale.gui.measurement.MeasurementView; 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.measurement.UserMeasurementView;
import com.health.openscale.gui.utils.ColorUtil;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List; import java.util.List;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
public class TableFragment extends Fragment { public class TableFragment extends Fragment {
private View tableView; private View tableView;
private LinearLayout tableHeaderView;
private RecyclerView recyclerView; private ProgressBar progressBar;
private MeasurementsAdapter adapter; private StickyHeaderTableView tableDataView;
private LinearLayoutManager layoutManager;
private List<MeasurementView> measurementViews; private List<MeasurementView> measurementViews;
private List<ScaleMeasurement> scaleMeasurementList;
private ArrayList<Drawable> 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() { public TableFragment() {
@@ -68,25 +65,36 @@ public class TableFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
tableView = inflater.inflate(R.layout.fragment_table, container, false); tableView = inflater.inflate(R.layout.fragment_table, container, false);
tableHeaderView = tableView.findViewById(R.id.tableHeaderView); progressBar = tableView.findViewById(R.id.progressBarTable);
recyclerView = tableView.findViewById(R.id.tableDataView); tableDataView = tableView.findViewById(R.id.tableDataView);
progressBar.setVisibility(View.VISIBLE);
recyclerView.setHasFixedSize(true); tableDataView.setOnTableCellClickListener(new StickyHeaderTableView.OnTableCellClickListener() {
@Override
layoutManager = new LinearLayoutManager(getContext()); public void onTableCellClicked(int rowPosition, int columnPosition) {
recyclerView.setLayoutManager(layoutManager); if (rowPosition > 0) {
TableFragmentDirections.ActionNavTableToNavDataentry action = TableFragmentDirections.actionNavTableToNavDataentry();
recyclerView.addItemDecoration(new DividerItemDecoration( action.setMeasurementId(scaleMeasurementList.get(rowPosition-1).getId());
recyclerView.getContext(), layoutManager.getOrientation())); action.setMode(MeasurementEntryFragment.DATA_ENTRY_MODE.VIEW);
Navigation.findNavController(getActivity(), R.id.nav_host_fragment).navigate(action);
adapter = new MeasurementsAdapter(); }
recyclerView.setAdapter(adapter); }
});
measurementViews = MeasurementView.getMeasurementList( measurementViews = MeasurementView.getMeasurementList(
getContext(), MeasurementView.DateTimeOrder.FIRST); getContext(), MeasurementView.DateTimeOrder.FIRST);
for (MeasurementView measurement : measurementViews) { iconList = new ArrayList<>();
measurement.setUpdateViews(false);
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<List<ScaleMeasurement>>() { OpenScale.getInstance().getScaleMeasurementsLiveData().observe(getViewLifecycleOwner(), new Observer<List<ScaleMeasurement>>() {
@@ -105,183 +113,57 @@ public class TableFragment extends Fragment {
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), onBackPressedCallback); requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return tableView; return tableView;
} }
public void updateOnView(List<ScaleMeasurement> scaleMeasurementList) public void updateOnView(List<ScaleMeasurement> scaleMeasurementList)
{ {
tableHeaderView.removeAllViews(); this.scaleMeasurementList = scaleMeasurementList;
final int iconHeight = pxImageDp(20); Object[][] tableData = new Object[scaleMeasurementList.size()+1][iconList.size()];
ArrayList<MeasurementView> visibleMeasurements = new ArrayList<>();
for (MeasurementView measurement : measurementViews) { // add header icons to the first table data row
if (!measurement.isVisible() || measurement instanceof UserMeasurementView) { for (int j=0; j<iconList.size(); j++) {
continue; tableData[0][j] = iconList.get(j);
}
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);
} }
adapter.setMeasurements(visibleMeasurements, scaleMeasurementList); for (int i = 0; i<scaleMeasurementList.size(); i++ ) {
ScaleMeasurement scaleMeasurement = scaleMeasurementList.get(i);
ScaleMeasurement prevScaleMeasurement = null;
if ((i+1) < scaleMeasurementList.size()) {
prevScaleMeasurement = scaleMeasurementList.get(i+1);
}
int j=0;
for (MeasurementView measurementView : measurementViews) {
if (!measurementView.isVisible() || measurementView instanceof UserMeasurementView || measurementView instanceof TimeMeasurementView) {
continue;
}
if (measurementView instanceof DateMeasurementView) {
String strDateTime = (dateFormat.format(scaleMeasurement.getDateTime()) +
" (" + dayFormat.format(scaleMeasurement.getDateTime()) + ")\n"+
timeFormat.format(scaleMeasurement.getDateTime()));
tableData[i+1][j] = strDateTime;
} else {
measurementView.loadFrom(scaleMeasurement, prevScaleMeasurement);
contentFormat.clear();
contentFormat.append(measurementView.getValueAsString(false));
measurementView.appendDiffValue(contentFormat, true, false);
tableData[i+1][j] = contentFormat.toString();
}
j++;
}
}
tableDataView.setData(tableData);
progressBar.setVisibility(View.GONE);
} }
private int pxImageDp(float dp) {
return (int)(dp * getResources().getDisplayMetrics().density + 0.5f);
}
private class MeasurementsAdapter extends RecyclerView.Adapter<MeasurementsAdapter.ViewHolder> {
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<MeasurementView> visibleMeasurements;
private List<ScaleMeasurement> scaleMeasurements;
public void setMeasurements(List<MeasurementView> visibleMeasurements,
List<ScaleMeasurement> 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;
}
}
} }

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
</vector>

View File

@@ -1,113 +1,94 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_alignParentBottom="true" android:layout_width="match_parent"
android:layout_alignParentLeft="true" android:layout_height="match_parent">
android:layout_alignParentStart="true"
android:orientation="vertical"
android:weightSum="100">
<Spinner
<ScrollView android:id="@+id/spinUser"
android:id="@+id/scrollView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="100"> android:spinnerMode="dialog"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout <ImageView
android:layout_width="match_parent" android:id="@+id/rangeOptionMenu"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:orientation="vertical"> android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
android:scaleType="centerInside"
android:translationZ="10dp"
app:layout_constraintBottom_toBottomOf="@+id/chartView"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_options" />
<LinearLayout <com.health.openscale.gui.measurement.ChartActionBarView
android:layout_width="match_parent" android:id="@+id/chartActionBar"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:background="?attr/colorPrimaryDark" android:layout_height="wrap_content"
android:orientation="horizontal"> android:background="?attr/colorPrimary"
android:fillViewport="true"
app:layout_constraintTop_toTopOf="parent" />
<TextView <com.health.openscale.gui.measurement.ChartMeasurementView
android:id="@+id/txtTitleUser" android:id="@+id/chartView"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="200dp"
android:text="@string/label_title_user" android:layout_gravity="center"
android:textColor="@android:color/white" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/spinUser"></com.health.openscale.gui.measurement.ChartMeasurementView>
<Spinner <TextView
android:id="@+id/spinUser" android:id="@+id/initialWeightView"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
</LinearLayout> android:gravity="center"
android:text="TextView"
app:layout_constraintEnd_toStartOf="@+id/differenceWeightView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chartView" />
<LinearLayout <TextView
android:layout_width="match_parent" android:id="@+id/differenceWeightView"
android:layout_height="wrap_content" android:layout_width="0dp"
android:background="?attr/colorPrimaryDark" android:layout_height="wrap_content"
android:gravity="right" android:gravity="center"
android:orientation="horizontal" android:text="TextView"
android:padding="10dp"> app:layout_constraintEnd_toStartOf="@+id/goalWeightView"
app:layout_constraintStart_toEndOf="@+id/initialWeightView"
app:layout_constraintTop_toTopOf="@+id/initialWeightView" />
<ImageView <TextView
android:id="@+id/showEntry" android:id="@+id/goalWeightView"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginRight="10dp" android:gravity="center"
android:scaleType="centerInside" android:text="TextView"
android:tint="#d3d3d3" app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_show" /> app:layout_constraintStart_toEndOf="@+id/differenceWeightView"
app:layout_constraintTop_toTopOf="@+id/differenceWeightView" />
<ImageView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/editEntry" android:id="@+id/recyclerView"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_marginRight="10dp" android:layout_marginTop="8dp"
android:scaleType="centerInside" app:layout_constraintBottom_toBottomOf="parent"
android:tint="#d3d3d3" app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_editable" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/differenceWeightView" />
<ImageView
android:id="@+id/deleteEntry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:scaleType="centerInside"
android:tint="#d3d3d3"
app:srcCompat="@drawable/ic_delete" />
<ImageView
android:id="@+id/rangeOptionMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerInside"
android:tint="@android:color/white"
app:srcCompat="@drawable/ic_options" />
</LinearLayout>
<com.health.openscale.gui.measurement.ChartActionBarView
android:id="@+id/chartActionBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:fillViewport="true"/>
<com.health.openscale.gui.measurement.ChartMeasurementView
android:id="@+id/chartView"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_gravity="center" />
<TableLayout
android:id="@+id/tableLayoutMeasurements"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
<ProgressBar <ProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_anchor="@id/chartView" app:layout_constraintBottom_toBottomOf="@+id/chartView"
app:layout_anchorGravity="center" app:layout_constraintEnd_toEndOf="parent"
/> app:layout_constraintStart_toStartOf="parent"
</androidx.coordinatorlayout.widget.CoordinatorLayout> app:layout_constraintTop_toBottomOf="@+id/chartActionBar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,24 +1,38 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:orientation="vertical">
<LinearLayout <com.health.openscale.gui.table.StickyHeaderTableView
android:id="@+id/tableHeaderView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="5dp"
android:paddingTop="5dp" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="?android:attr/listDivider" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tableDataView" android:id="@+id/tableDataView"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:scrollbars="vertical" /> app:layout_constraintBottom_toBottomOf="parent"
</LinearLayout> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shtv_cellPadding="16dp"
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" />
<ProgressBar
android:id="@+id/progressBarTable"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout >

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:foreground="?android:attr/selectableItemBackground"
app:cardCornerRadius="10dp"
app:contentPadding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/dateView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="16sp"
android:textStyle="normal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/showEntry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:scaleType="centerInside"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_show"
app:tint="#33B5E5" />
<ImageView
android:id="@+id/editEntry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:scaleType="centerInside"
app:layout_constraintEnd_toStartOf="@+id/showEntry"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_editable"
app:tint="#99CC00" />
<ImageView
android:id="@+id/deleteEntry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:scaleType="centerInside"
app:layout_constraintEnd_toStartOf="@+id/editEntry"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_delete"
app:tint="#FF4444" />
<TableLayout
android:id="@+id/measurementHighlightViews"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/dateView"></TableLayout>
<ImageView
android:id="@+id/expandMoreView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/measurementHighlightViews"
app:srcCompat="@drawable/ic_expand_more"
app:tint="?attr/colorControlNormal" />
<TableLayout
android:id="@+id/measurementViews"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="5dp"
app:layout_constraintTop_toBottomOf="@id/expandMoreView">
</TableLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@@ -2,9 +2,7 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="?attr/spinnerDropDownItemStyle" style="?attr/spinnerDropDownItemStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/dropdownListPreferredItemHeight" android:layout_height="match_parent"
android:background="?attr/colorPrimaryDark" android:padding="8dp"
android:ellipsize="marquee" android:ellipsize="marquee"
android:gravity="center" android:gravity="center" />
android:singleLine="true"
android:textColor="@android:color/white" />

View File

@@ -197,6 +197,7 @@
<string name="label_set_default_order">Set default order</string> <string name="label_set_default_order">Set default order</string>
<string name="label_export_overwrite">Overwrite previous export \"%s\"?</string> <string name="label_export_overwrite">Overwrite previous export \"%s\"?</string>
<string name="label_is_on_right_axis">Is on right axis</string> <string name="label_is_on_right_axis">Is on right axis</string>
<string name="label_is_sticky">Is sticky</string>
<string name="label_measurement_in_percent">Measurement in %</string> <string name="label_measurement_in_percent">Measurement in %</string>
<string name="label_estimate_measurement">Estimate measurement</string> <string name="label_estimate_measurement">Estimate measurement</string>
<string name="label_estimation_formula">Estimation formula</string> <string name="label_estimation_formula">Estimation formula</string>

View File

@@ -37,4 +37,26 @@
<item name="positiveButtonText">@android:string/ok</item> <item name="positiveButtonText">@android:string/ok</item>
<item name="negativeButtonText">@android:string/cancel</item> <item name="negativeButtonText">@android:string/cancel</item>
</style> </style>
<declare-styleable name="StickyHeaderTableView">
<attr name="shtv_textLabelColor" format="color" />
<attr name="shtv_textLabelSize" format="dimension" />
<attr name="shtv_textHeaderColor" format="color" />
<attr name="shtv_textHeaderSize" format="dimension" />
<attr name="shtv_dividerColor" format="dimension" />
<attr name="shtv_dividerThickness" format="dimension" />
<attr name="shtv_contentCellFillColor" format="color" />
<attr name="shtv_headerCellFillColor" format="color" />
<attr name="shtv_cellPadding" format="dimension" />
<attr name="shtv_isDisplayLeftHeadersVertically" format="boolean" />
<attr name="shtv_isWrapHeightOfEachRow" format="boolean" />
<attr name="shtv_isWrapWidthOfEachColumn" format="boolean" />
<attr name="shtv_is2DScrollEnabled" format="boolean" />
</declare-styleable>
</resources> </resources>