Skip to content

Don't show unexpected decimals for range slider value #6699

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down
Loading