diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeBaseWidget.java b/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeBaseWidget.java new file mode 100644 index 00000000000..8722cabec2d --- /dev/null +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeBaseWidget.java @@ -0,0 +1,112 @@ +/* + * Copyright 2017 Nafundi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.odk.collect.android.widgets.range; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.google.android.material.slider.Slider; + +import org.javarosa.core.model.data.DecimalData; +import org.javarosa.core.model.data.IAnswerData; +import org.javarosa.core.model.data.IntegerData; +import org.javarosa.form.api.FormEntryPrompt; +import org.odk.collect.android.formentry.questions.QuestionDetails; +import org.odk.collect.android.views.TrackingTouchSlider; +import org.odk.collect.android.widgets.QuestionWidget; +import org.odk.collect.android.widgets.utilities.RangeWidgetUtils; + +import java.math.BigDecimal; + +@SuppressLint("ViewConstructor") +public class RangeBaseWidget extends QuestionWidget implements Slider.OnChangeListener { + private final boolean isIntegerType; + TrackingTouchSlider slider; + TextView currentValue; + + protected RangeBaseWidget(Context context, + QuestionDetails prompt, + Dependencies dependencies, + boolean isIntegerType) { + super(context, dependencies, prompt); + this.isIntegerType = isIntegerType; + render(); + } + + @Override + protected View onCreateAnswerView(Context context, FormEntryPrompt prompt, int answerFontSize) { + RangeWidgetUtils.RangeWidgetLayoutElements layoutElements = RangeWidgetUtils.setUpLayoutElements(context, prompt); + slider = layoutElements.getSlider(); + currentValue = layoutElements.getCurrentValue(); + + BigDecimal set = RangeWidgetUtils.setUpSlider(prompt, slider, isIntegerType); + setUpActualValueLabel(set == null ? null : set.floatValue()); + + if (slider.isEnabled()) { + slider.setListener(this); + } + return layoutElements.getAnswerView(); + } + + @Override + public IAnswerData getAnswer() { + String stringAnswer = currentValue.getText().toString(); + return stringAnswer.isEmpty() ? null + : isIntegerType ? new IntegerData(Integer.parseInt(stringAnswer)) + : new DecimalData(Double.parseDouble(stringAnswer)); + } + + @Override + public void setOnLongClickListener(OnLongClickListener l) { + } + + @Override + public boolean shouldSuppressFlingGesture() { + return slider.isTrackingTouch(); + } + + @Override + public void clearAnswer() { + setUpActualValueLabel(null); + widgetValueChanged(); + } + + @SuppressLint("RestrictedApi") + @Override + public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) { + if (fromUser) { + setUpActualValueLabel(RangeWidgetUtils.getActualValue(getFormEntryPrompt(), value)); + widgetValueChanged(); + } + } + + private void setUpActualValueLabel(Float actualValue) { + if (actualValue != null) { + currentValue.setText( + isIntegerType ? String.valueOf(actualValue.intValue()) + : String.valueOf(actualValue) + ); + } else { + currentValue.setText(""); + slider.reset(); + } + } +} diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeDecimalWidget.java b/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeDecimalWidget.java index fa2c7e589d6..1007d53389d 100644 --- a/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeDecimalWidget.java +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeDecimalWidget.java @@ -18,84 +18,14 @@ import android.annotation.SuppressLint; import android.content.Context; -import android.view.View; -import android.widget.TextView; -import androidx.annotation.NonNull; - -import com.google.android.material.slider.Slider; - -import org.javarosa.core.model.data.DecimalData; -import org.javarosa.core.model.data.IAnswerData; -import org.javarosa.form.api.FormEntryPrompt; import org.odk.collect.android.formentry.questions.QuestionDetails; -import org.odk.collect.android.views.TrackingTouchSlider; -import org.odk.collect.android.widgets.QuestionWidget; -import org.odk.collect.android.widgets.utilities.RangeWidgetUtils; - -import java.math.BigDecimal; @SuppressLint("ViewConstructor") -public class RangeDecimalWidget extends QuestionWidget implements Slider.OnChangeListener { - TrackingTouchSlider slider; - TextView currentValue; - - public RangeDecimalWidget(Context context, QuestionDetails prompt, Dependencies dependencies) { - super(context, dependencies, prompt); - render(); - } - - @Override - protected View onCreateAnswerView(Context context, FormEntryPrompt prompt, int answerFontSize) { - RangeWidgetUtils.RangeWidgetLayoutElements layoutElements = RangeWidgetUtils.setUpLayoutElements(context, prompt); - slider = layoutElements.getSlider(); - currentValue = layoutElements.getCurrentValue(); - - setUpActualValueLabel(RangeWidgetUtils.setUpSlider(prompt, slider, false)); - - if (slider.isEnabled()) { - slider.setListener(this); - } - return layoutElements.getAnswerView(); - } - - @Override - public IAnswerData getAnswer() { - String stringAnswer = currentValue.getText().toString(); - return stringAnswer.isEmpty() ? null : new DecimalData(Double.parseDouble(stringAnswer)); - } - - @Override - public void setOnLongClickListener(OnLongClickListener l) { - } - - @Override - public boolean shouldSuppressFlingGesture() { - return slider.isTrackingTouch(); - } - - @Override - public void clearAnswer() { - setUpActualValueLabel(null); - widgetValueChanged(); - } - - @SuppressLint("RestrictedApi") - @Override - public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) { - if (fromUser) { - BigDecimal actualValue = RangeWidgetUtils.getActualValue(getFormEntryPrompt(), value); - setUpActualValueLabel(actualValue); - widgetValueChanged(); - } - } - - private void setUpActualValueLabel(BigDecimal actualValue) { - if (actualValue != null) { - currentValue.setText(String.valueOf(actualValue.doubleValue())); - } else { - currentValue.setText(""); - slider.reset(); - } +public class RangeDecimalWidget extends RangeBaseWidget { + public RangeDecimalWidget(Context context, + QuestionDetails prompt, + Dependencies dependencies) { + super(context, prompt, dependencies, false); } } diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeIntegerWidget.java b/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeIntegerWidget.java index 56d0ebdd30b..22c10f80778 100644 --- a/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeIntegerWidget.java +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/range/RangeIntegerWidget.java @@ -18,84 +18,14 @@ import android.annotation.SuppressLint; import android.content.Context; -import android.view.View; -import android.widget.TextView; -import androidx.annotation.NonNull; - -import com.google.android.material.slider.Slider; - -import org.javarosa.core.model.data.IAnswerData; -import org.javarosa.core.model.data.IntegerData; -import org.javarosa.form.api.FormEntryPrompt; import org.odk.collect.android.formentry.questions.QuestionDetails; -import org.odk.collect.android.views.TrackingTouchSlider; -import org.odk.collect.android.widgets.QuestionWidget; -import org.odk.collect.android.widgets.utilities.RangeWidgetUtils; - -import java.math.BigDecimal; @SuppressLint("ViewConstructor") -public class RangeIntegerWidget extends QuestionWidget implements Slider.OnChangeListener { - TrackingTouchSlider slider; - TextView currentValue; - - public RangeIntegerWidget(Context context, QuestionDetails prompt, Dependencies dependencies) { - super(context, dependencies, prompt); - render(); - } - - @Override - protected View onCreateAnswerView(Context context, FormEntryPrompt prompt, int answerFontSize) { - RangeWidgetUtils.RangeWidgetLayoutElements layoutElements = RangeWidgetUtils.setUpLayoutElements(context, prompt); - slider = layoutElements.getSlider(); - currentValue = layoutElements.getCurrentValue(); - - setUpActualValueLabel(RangeWidgetUtils.setUpSlider(prompt, slider, true)); - - if (slider.isEnabled()) { - slider.setListener(this); - } - return layoutElements.getAnswerView(); - } - - @Override - public IAnswerData getAnswer() { - String stringAnswer = currentValue.getText().toString(); - return stringAnswer.isEmpty() ? null : new IntegerData(Integer.parseInt(stringAnswer)); - } - - @Override - public void setOnLongClickListener(OnLongClickListener l) { - } - - @Override - public boolean shouldSuppressFlingGesture() { - return slider.isTrackingTouch(); - } - - @Override - public void clearAnswer() { - setUpActualValueLabel(null); - widgetValueChanged(); - } - - @SuppressLint("RestrictedApi") - @Override - public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) { - if (fromUser) { - BigDecimal actualValue = RangeWidgetUtils.getActualValue(getFormEntryPrompt(), value); - setUpActualValueLabel(actualValue); - widgetValueChanged(); - } - } - - private void setUpActualValueLabel(BigDecimal actualValue) { - if (actualValue != null) { - currentValue.setText(String.valueOf(actualValue.intValue())); - } else { - currentValue.setText(""); - slider.reset(); - } +public class RangeIntegerWidget extends RangeBaseWidget { + public RangeIntegerWidget(Context context, + QuestionDetails prompt, + Dependencies dependencies) { + super(context, prompt, dependencies, true); } } diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/RangeWidgetUtils.java b/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/RangeWidgetUtils.java index 0c879a4b842..0aed130c546 100644 --- a/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/RangeWidgetUtils.java +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/RangeWidgetUtils.java @@ -15,8 +15,8 @@ import org.odk.collect.android.databinding.RangeWidgetHorizontalBinding; import org.odk.collect.android.databinding.RangeWidgetVerticalBinding; import org.odk.collect.android.fragments.dialogs.NumberPickerDialog; -import org.odk.collect.androidshared.ui.ToastUtils; import org.odk.collect.android.views.TrackingTouchSlider; +import org.odk.collect.androidshared.ui.ToastUtils; import java.math.BigDecimal; @@ -167,17 +167,16 @@ public static void setUpRangePickerWidget(Context context, RangePickerWidgetAnsw } } - public static BigDecimal getActualValue(FormEntryPrompt prompt, float value) { + public static Float getActualValue(FormEntryPrompt prompt, float value) { RangeQuestion rangeQuestion = (RangeQuestion) prompt.getQuestion(); - BigDecimal rangeStart = rangeQuestion.getRangeStart(); - BigDecimal rangeEnd = rangeQuestion.getRangeEnd(); - BigDecimal actualValue = BigDecimal.valueOf(value); + Float rangeStart = rangeQuestion.getRangeStart().floatValue(); + Float rangeEnd = rangeQuestion.getRangeEnd().floatValue(); if (rangeEnd.compareTo(rangeStart) < 0) { - actualValue = rangeEnd.add(rangeStart).subtract(actualValue); + value = rangeEnd + rangeStart - value; } - return actualValue; + return value; } public static void showNumberPickerDialog(FragmentActivity activity, String[] displayedValuesForNumberPicker, int id, int progress) { diff --git a/collect_app/src/test/java/org/odk/collect/android/widgets/range/RangeDecimalWidgetTest.java b/collect_app/src/test/java/org/odk/collect/android/widgets/range/RangeDecimalWidgetTest.java index 60aec302bff..7e6a4257c7c 100644 --- a/collect_app/src/test/java/org/odk/collect/android/widgets/range/RangeDecimalWidgetTest.java +++ b/collect_app/src/test/java/org/odk/collect/android/widgets/range/RangeDecimalWidgetTest.java @@ -1,5 +1,19 @@ package org.odk.collect.android.widgets.range; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.mockValueChangedListener; +import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.promptWithQuestionDefAndAnswer; +import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.promptWithReadOnlyAndQuestionDef; +import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.widgetDependencies; +import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.widgetTestActivity; + import android.view.View; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -17,20 +31,6 @@ import java.math.BigDecimal; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.mockValueChangedListener; -import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.promptWithQuestionDefAndAnswer; -import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.promptWithReadOnlyAndQuestionDef; -import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.widgetDependencies; -import static org.odk.collect.android.widgets.support.QuestionWidgetHelpers.widgetTestActivity; - @RunWith(AndroidJUnit4.class) public class RangeDecimalWidgetTest { private static final String NO_TICKS_APPEARANCE = "no-ticks"; @@ -42,7 +42,7 @@ public void setup() { rangeQuestion = mock(RangeQuestion.class); when(rangeQuestion.getRangeStart()).thenReturn(BigDecimal.valueOf(1.5)); when(rangeQuestion.getRangeEnd()).thenReturn(BigDecimal.valueOf(5.5)); - when(rangeQuestion.getRangeStep()).thenReturn(BigDecimal.valueOf(0.5)); + when(rangeQuestion.getRangeStep()).thenReturn(BigDecimal.valueOf(0.1)); } @Test @@ -215,6 +215,14 @@ public void changingSliderValueToAnyOtherThanTheMinOne_setsTheValueCorrectly() { assertThat(widget.currentValue.getText(), equalTo("5.5")); } + @Test + public void changingSliderValueToAnIntermediate_setsTheValueCorrectly() { + RangeDecimalWidget widget = createWidget(promptWithQuestionDefAndAnswer(rangeQuestion, null)); + + SliderExtKt.clickOnFractionalValue(widget.slider, 0.3f); + assertThat(widget.currentValue.getText(), equalTo("2.3")); + } + private RangeDecimalWidget createWidget(FormEntryPrompt prompt) { RangeDecimalWidget widget = new RangeDecimalWidget(widgetTestActivity(), new QuestionDetails(prompt), widgetDependencies()); widget.slider.layout(0, 0, 100, 10); diff --git a/test-shared/src/main/java/org/odk/collect/testshared/SliderExt.kt b/test-shared/src/main/java/org/odk/collect/testshared/SliderExt.kt index 1fb7790f4f9..1dde7bfc85a 100644 --- a/test-shared/src/main/java/org/odk/collect/testshared/SliderExt.kt +++ b/test-shared/src/main/java/org/odk/collect/testshared/SliderExt.kt @@ -11,6 +11,10 @@ fun Slider.clickOnMaxValue() { clickOnPosition(width.toFloat()) } +fun Slider.clickOnFractionalValue(fraction: Float) { + clickOnPosition(width.toFloat() * fraction) +} + fun Slider.clickOnPosition(xPosition: Float) { val currentTime = System.currentTimeMillis()