/*
* Copyright 2016 Google Inc.
*
* 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 com.google.android.libraries.remixer;
import com.google.android.libraries.remixer.serialization.StoredVariable;
import java.util.Locale;
/**
* An integer value that can be adjusted.
*
* <p>It also checks that values are always in the range specified by [minValue,maxValue].
*
* <p><b>This class is not thread-safe and should only be used from the main thread.</b>
*/
public class RangeVariable extends Variable<Float> {
private static final String INVALID_RANGE_ERROR_FORMAT =
"Invalid range for Variable %s min: %f, max: %f";
private static final String NEGATIVE_STEPPING_ERROR_FORMAT =
"Stepping must be > 0, Variable %s has increment %f";
private static final String STEP_INCREMENT_INVALID_FOR_RANGE_ERROR_FORMAT =
"Variable %s: incorrect increment, can't get to %s %f from minValue %f using"
+ " increment %f";
private static final String NEW_VALUE_OUT_OF_BOUNDS_ERROR_FORMAT =
"%f is out of bounds for Variable %s: min: %f, max: %f";
private final float minValue;
private final float maxValue;
private final float increment;
/**
* Constructor that checks correctness of the range, validates {@code initialValue} and runs
* {@code callback}.
*
* @param title The name of this variable to be displayed in the UI.
* @param key The key to store in SharedPreferences.
* @param initialValue The initial value for this variable.
* @param minValue The minimum value for this variable.
* @param maxValue The maximum value for this variable.
* @param increment A value that defines each step. Must be a positive integer. So if you have
* {@code minValue = 0 && maxValue = 12 && increment = 4}, only 0, 4, 8, 12 are possible
* values.
* @param context the object which created this variable, should be an activity.
* @param callback A callback to run when successfully initialized and when the value changes. Can
* be null.
* @param layoutId A layout id that renders this control on screen.
* @throws IllegalArgumentException {@code minValue > maxValue} or {@code increment < 1} or {@code
* (maxValue - minValue) % increment != 0} which means the current increment setting can't
* possibly get from minValue to maxValue.
*/
private RangeVariable(
String title,
String key,
float initialValue,
float minValue,
float maxValue,
float increment,
Object context,
Callback<Float> callback,
int layoutId) {
super(title, key, initialValue, context, callback, layoutId, DataType.NUMBER);
this.minValue = minValue;
this.maxValue = maxValue;
this.increment = increment;
checkRange();
checkStepIncrement();
}
private void checkRange() {
if (minValue > maxValue) {
throw new IllegalArgumentException(
String.format(
Locale.getDefault(),
INVALID_RANGE_ERROR_FORMAT,
getTitle(),
minValue,
maxValue));
}
}
private void checkStepIncrement() {
if (increment <= 0) {
throw new IllegalArgumentException(
String.format(
Locale.getDefault(),
NEGATIVE_STEPPING_ERROR_FORMAT,
getTitle(),
increment));
}
checkValueAndStep(maxValue, "maxValue");
}
private void checkValueAndStep(float value, String valueName) {
if ((value - minValue) % increment != 0) {
throw new IllegalArgumentException(
String.format(
Locale.getDefault(),
STEP_INCREMENT_INVALID_FOR_RANGE_ERROR_FORMAT,
getTitle(),
valueName,
value,
minValue,
increment));
}
}
@Override
protected void checkValue(Float newValue) {
if (newValue < minValue || newValue > maxValue) {
throw new IllegalArgumentException(
String.format(
Locale.getDefault(),
NEW_VALUE_OUT_OF_BOUNDS_ERROR_FORMAT,
newValue,
getTitle(),
minValue,
maxValue));
}
checkValueAndStep(newValue, "newValue");
}
public float getMinValue() {
return minValue;
}
public float getMaxValue() {
return maxValue;
}
public float getIncrement() {
return increment;
}
/**
* Gets the serializable constraints string for this variable.
*/
public String getSerializableConstraints() {
return StoredVariable.RANGE_VARIABLE_CONSTRAINT;
}
/**
* Convenience builder for RangeVariable, the number of arguments for the constructor is too
* large.
*
* <p>This builder assumes a few things for your convenience:
* <ul>
* <li>If the initial value is not set, minValue will be used as the initial value.
* <li>If the increment is not set, 1 will be used.
* <li>If the layout id is not set, the default layout will be used.
* <li>If the title is not set, the key will be used as title
* </ul>
*
* <p>On the other hand: key, minValue, maxValue, dataType, and context are mandatory. If any of
* these are missing or the settings are incorrect according to the logic of {@link RangeVariable}
* an {@link IllegalArgumentException} will be thrown.
*/
public static class Builder extends BaseVariableBuilder<RangeVariable, Float> {
private Float minValue;
private Float maxValue;
private float increment = 1;
public Builder() {
setDataType(DataType.NUMBER);
}
public Builder setMinValue(float minValue) {
this.minValue = minValue;
return this;
}
public Builder setMaxValue(float maxValue) {
this.maxValue = maxValue;
return this;
}
public Builder setIncrement(float increment) {
this.increment = increment;
return this;
}
/**
* Returns a new RangeVariable created with the configuration stored in this builder instance.
*
* @throws IllegalArgumentException If key, minValue or maxValue are missing, or if these
* settings are incorrect for {@link RangeVariable}
*/
public RangeVariable build() {
if (minValue == null || maxValue == null) {
throw new IllegalArgumentException("minValue and maxValue must not be null");
}
if (initialValue == null) {
initialValue = minValue;
}
checkBaseFields();
RangeVariable variable = new RangeVariable(
title, key, initialValue, minValue, maxValue, increment, context, callback,
layoutId);
variable.init();
return variable;
}
}
}