From b2eb11380f92063880479b11077027e5529a9d2c Mon Sep 17 00:00:00 2001 From: Erik Johansson Date: Tue, 20 Feb 2018 23:35:35 +0100 Subject: [PATCH] Make it possible to change order of measurment views By long pressing a view in the data entry activity, the view can be dragged up or down to change the order. The order is then saved and reflected in overview, table and data entry fragments/activity. This fixes part of #81 (and #164). --- .../health/openscale/gui/MainActivity.java | 29 +++-- .../gui/activities/DataEntryActivity.java | 104 ++++++++++++++++++ .../openscale/gui/views/MeasurementView.java | 95 +++++++++++----- 3 files changed, 184 insertions(+), 44 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 b3579b30..197c5c0e 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 @@ -59,6 +59,7 @@ import com.health.openscale.gui.fragments.OverviewFragment; import com.health.openscale.gui.fragments.StatisticsFragment; import com.health.openscale.gui.fragments.TableFragment; import com.health.openscale.gui.utils.PermissionHelper; +import com.health.openscale.gui.views.MeasurementView; import java.lang.reflect.Field; @@ -78,6 +79,8 @@ public class MainActivity extends AppCompatActivity private BottomNavigationView navBottomDrawer; private ActionBarDrawerToggle drawerToggle; + private boolean settingsActivityRunning = false; + @Override protected void onCreate(Bundle savedInstanceState) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); @@ -90,6 +93,9 @@ public class MainActivity extends AppCompatActivity super.onCreate(savedInstanceState); + PreferenceManager.getDefaultSharedPreferences(this) + .registerOnSharedPreferenceChangeListener(this); + CaocConfig.Builder.create() .trackActivities(true) .apply(); @@ -183,33 +189,24 @@ public class MainActivity extends AppCompatActivity } } - private void registerOnSharedPreferenceChangeListener() { - PreferenceManager.getDefaultSharedPreferences(this) - .registerOnSharedPreferenceChangeListener(this); - } - - private void unregisterOnSharedPreferenceChangeListener() { - PreferenceManager.getDefaultSharedPreferences(this) - .unregisterOnSharedPreferenceChangeListener(this); - } - @Override public void onResume() { super.onResume(); - // Stop listening when returning from settings - unregisterOnSharedPreferenceChangeListener(); + settingsActivityRunning = false; } @Override public void onDestroy() { - // Clean up when shutting down - unregisterOnSharedPreferenceChangeListener(); + PreferenceManager.getDefaultSharedPreferences(this) + .unregisterOnSharedPreferenceChangeListener(this); super.onDestroy(); } @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { - recreate(); + if (settingsActivityRunning || key == MeasurementView.PREF_MEASUREMENT_ORDER) { + recreate(); + } } private void positiveFeedbackDialog() { @@ -307,10 +304,10 @@ public class MainActivity extends AppCompatActivity prefs.edit().putInt("lastFragmentId", menuItemId).commit(); break; case R.id.nav_settings: - registerOnSharedPreferenceChangeListener(); Intent settingsIntent = new Intent(this, SettingsActivity.class); settingsIntent.putExtra(SettingsActivity.EXTRA_TINT_COLOR, navDrawer.getItemTextColor().getDefaultColor()); startActivity(settingsIntent); + settingsActivityRunning = true; drawerLayout.closeDrawers(); return; case R.id.nav_help: diff --git a/android_app/app/src/main/java/com/health/openscale/gui/activities/DataEntryActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/activities/DataEntryActivity.java index 521f37b2..8ceb9d62 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/activities/DataEntryActivity.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/activities/DataEntryActivity.java @@ -20,15 +20,18 @@ import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.graphics.Color; +import android.graphics.Point; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; +import android.view.DragEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; @@ -127,6 +130,16 @@ public class DataEntryActivity extends AppCompatActivity { : MeasurementView.MeasurementViewMode.VIEW; for (MeasurementView measurement : dataEntryMeasurements) { measurement.setEditMode(mode); + + // Date and time can not be reordered (as they can be both first and last) + if (measurement instanceof DateMeasurementView || measurement instanceof TimeMeasurementView) { + continue; + } + + onLongClickListener longClickListener = new onLongClickListener(); + measurement.setOnTouchListener(longClickListener); + measurement.setOnLongClickListener(longClickListener); + measurement.setOnDragListener(new onDragListener()); } updateOnView(); @@ -456,4 +469,95 @@ public class DataEntryActivity extends AppCompatActivity { } } } + + private class onLongClickListener implements View.OnTouchListener, View.OnLongClickListener { + float x = 0; + float y = 0; + + @Override + public boolean onTouch(View view, MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + // Save x and y so that the drag shadow can have the touch point set to where + // the user did the touch (and not in the center of the view). + x = event.getX(); + y = event.getY(); + } + return false; + } + + @Override + public boolean onLongClick(View view) { + return view.startDrag(null, new dragShadowBuilder(view), view, 0); + } + + private class dragShadowBuilder extends View.DragShadowBuilder { + public dragShadowBuilder(View view) { + super(view); + } + + @Override + public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) { + super.onProvideShadowMetrics(outShadowSize, outShadowTouchPoint); + outShadowTouchPoint.set(Math.round(x), Math.round(y)); + } + } + } + + private class onDragListener implements View.OnDragListener { + Drawable background = null; + // background may be set to null, thus the extra boolean + boolean hasBackground = false; + + @Override + public boolean onDrag(View view, DragEvent event) { + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_STARTED: + if (view == event.getLocalState() && !hasBackground) { + background = view.getBackground(); + hasBackground = true; + view.setBackgroundColor(Color.GRAY); + } + break; + case DragEvent.ACTION_DRAG_LOCATION: + // Ignore + break; + case DragEvent.ACTION_DRAG_ENTERED: + if (view != event.getLocalState()) { + background = view.getBackground(); + hasBackground = true; + view.setBackgroundColor(Color.LTGRAY); + } + break; + case DragEvent.ACTION_DRAG_EXITED: + if (view != event.getLocalState() && hasBackground) { + view.setBackground(background); + background = null; + hasBackground = false; + } + break; + case DragEvent.ACTION_DROP: + View draggedView = (View) event.getLocalState(); + TableLayout table = (TableLayout) draggedView.getParent(); + final int draggedIndex = table.indexOfChild(draggedView); + final int targetIndex = table.indexOfChild(view); + if (draggedIndex != targetIndex) { + // A view that is moved down is placed after the target view, + // and a view that is moved up is placed before the target view. + table.removeView(draggedView); + table.addView(draggedView, targetIndex); + MeasurementView.saveMeasurementViewsOrder(table); + } + break; + case DragEvent.ACTION_DRAG_ENDED: + if (hasBackground) { + // Restore background + view.setBackground(background); + background = null; + hasBackground = false; + } + break; + } + return true; + } + } } diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java index 39b6b600..e274b234 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java @@ -25,6 +25,7 @@ import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.content.ContextCompat; import android.text.SpannableStringBuilder; +import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -58,6 +59,8 @@ import static com.health.openscale.gui.views.MeasurementView.MeasurementViewMode public abstract class MeasurementView extends TableLayout { public enum MeasurementViewMode {VIEW, EDIT, ADD, STATISTIC} + public static String PREF_MEASUREMENT_ORDER = "measurementOrder"; + private TableRow measurementRow; private ImageView iconView; private TextView nameView; @@ -84,38 +87,76 @@ public abstract class MeasurementView extends TableLayout { public enum DateTimeOrder { FIRST, LAST, NONE } - public static final List getMeasurementList(Context context, DateTimeOrder order) { - final List measurementViews = new ArrayList<>(); - - if (order == DateTimeOrder.FIRST) { - measurementViews.add(new DateMeasurementView(context)); - measurementViews.add(new TimeMeasurementView(context)); - } - measurementViews.add(new WeightMeasurementView(context)); - measurementViews.add(new BMIMeasurementView(context)); - measurementViews.add(new WaterMeasurementView(context)); - measurementViews.add(new MuscleMeasurementView(context)); - measurementViews.add(new LBWMeasurementView(context)); - measurementViews.add(new FatMeasurementView(context)); - measurementViews.add(new BoneMeasurementView(context)); - measurementViews.add(new WaistMeasurementView(context)); - measurementViews.add(new WHtRMeasurementView(context)); - measurementViews.add(new HipMeasurementView(context)); - measurementViews.add(new WHRMeasurementView(context)); - measurementViews.add(new BMRMeasurementView(context)); - measurementViews.add(new CommentMeasurementView(context)); - if (order == DateTimeOrder.LAST) { - measurementViews.add(new DateMeasurementView(context)); - measurementViews.add(new TimeMeasurementView(context)); + public static final List getMeasurementList(Context context, DateTimeOrder dateTimeOrder) { + final List sorted = new ArrayList<>(); + if (dateTimeOrder == DateTimeOrder.FIRST) { + sorted.add(new DateMeasurementView(context)); + sorted.add(new TimeMeasurementView(context)); } SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - for (MeasurementView measurement : measurementViews) { + { + final List unsorted = new ArrayList<>(); + + unsorted.add(new WeightMeasurementView(context)); + unsorted.add(new BMIMeasurementView(context)); + unsorted.add(new WaterMeasurementView(context)); + unsorted.add(new MuscleMeasurementView(context)); + unsorted.add(new LBWMeasurementView(context)); + unsorted.add(new FatMeasurementView(context)); + unsorted.add(new BoneMeasurementView(context)); + unsorted.add(new WaistMeasurementView(context)); + unsorted.add(new WHtRMeasurementView(context)); + unsorted.add(new HipMeasurementView(context)); + unsorted.add(new WHRMeasurementView(context)); + unsorted.add(new BMRMeasurementView(context)); + unsorted.add(new CommentMeasurementView(context)); + + // Get sort order + final String[] sortOrder = TextUtils.split( + prefs.getString(PREF_MEASUREMENT_ORDER, ""), ","); + + // Move views from unsorted to sorted in the correct order + for (String key : sortOrder) { + for (MeasurementView measurement : unsorted) { + if (key.equals(measurement.getKey())) { + sorted.add(measurement); + unsorted.remove(measurement); + break; + } + } + } + + // Any new views end up at the end + sorted.addAll(unsorted); + } + + if (dateTimeOrder == DateTimeOrder.LAST) { + sorted.add(new DateMeasurementView(context)); + sorted.add(new TimeMeasurementView(context)); + } + + for (MeasurementView measurement : sorted) { measurement.updatePreferences(prefs); } - return measurementViews; + return sorted; + } + + public static void saveMeasurementViewsOrder(TableLayout tableLayout) { + ArrayList order = new ArrayList<>(); + for (int i = 0; i < tableLayout.getChildCount(); ++i) { + MeasurementView view = (MeasurementView) tableLayout.getChildAt(i); + if (view instanceof DateMeasurementView || view instanceof TimeMeasurementView) { + continue; + } + order.add(view.getKey()); + } + PreferenceManager.getDefaultSharedPreferences(tableLayout.getContext()) + .edit() + .putString(PREF_MEASUREMENT_ORDER, TextUtils.join(",", order)) + .commit(); } private void initView(Context context) { @@ -180,9 +221,7 @@ public abstract class MeasurementView extends TableLayout { evaluatorView.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 0.99f)); spaceAfterEvaluatorView.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 0.01f)); - onClickListenerEvaluation onClickListener = new onClickListenerEvaluation(); - measurementRow.setOnClickListener(onClickListener); - evaluatorRow.setOnClickListener(onClickListener); + setOnClickListener(new onClickListenerEvaluation()); } protected LinearLayout getIncDecLayout() {