Number format preference

This commit is contained in:
serso 2016-04-28 12:18:35 +02:00
parent 9e4a798c1b
commit 5a9bcdede7
15 changed files with 477 additions and 53 deletions

View File

@ -56,7 +56,11 @@
<activity
android:name=".preferences.PreferencesActivity"
android:label="@string/cpp_settings" />
android:label="@string/cpp_settings">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<activity
android:name=".preferences.PreferencesActivity$Dialog"

View File

@ -25,7 +25,11 @@ package org.solovyev.android.calculator;
import android.app.Activity;
import android.app.Application;
import android.app.Dialog;
import android.content.*;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
@ -48,14 +52,17 @@ import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import org.solovyev.android.Check;
import org.solovyev.android.calculator.floating.FloatingCalculatorService;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public final class App {
public static final String TAG = "C++";
@ -276,6 +283,11 @@ public final class App {
processViewsOfType0(view, viewClass, viewProcessor);
}
@Nonnull
public static <T> ArrayAdapter<T> makeSimpleSpinnerAdapter(@NonNull Context context) {
return new ArrayAdapter<>(context, R.layout.support_simple_spinner_dropdown_item);
}
public interface ViewProcessor<V> {
void process(@Nonnull V view);
}

View File

@ -23,15 +23,11 @@
package org.solovyev.android.calculator;
import android.content.SharedPreferences;
import android.support.annotation.StringRes;
import android.text.TextUtils;
import com.squareup.otto.Bus;
import jscl.AngleUnit;
import jscl.JsclMathEngine;
import jscl.MathEngine;
import jscl.NumeralBase;
import jscl.math.operator.Operator;
import jscl.text.Identifier;
import jscl.text.Parser;
import org.solovyev.android.Check;
import org.solovyev.android.calculator.functions.FunctionsRegistry;
import org.solovyev.android.calculator.operators.OperatorsRegistry;
@ -43,15 +39,25 @@ import org.solovyev.android.prefs.StringPreference;
import org.solovyev.common.text.EnumMapper;
import org.solovyev.common.text.NumberMapper;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import jscl.AngleUnit;
import jscl.JsclMathEngine;
import jscl.MathEngine;
import jscl.NumeralBase;
import jscl.math.operator.Operator;
import jscl.text.Identifier;
import jscl.text.Parser;
import midpcalc.Real;
@Singleton
public class Engine implements SharedPreferences.OnSharedPreferenceChangeListener {
@ -231,8 +237,24 @@ public class Engine implements SharedPreferences.OnSharedPreferenceChangeListene
}
}
public enum Notation {
simple(Real.NumberFormat.FSE_NONE, R.string.cpp_number_format_simple),
eng(Real.NumberFormat.FSE_ENG, R.string.cpp_number_format_eng),
sci(Real.NumberFormat.FSE_SCI, R.string.cpp_number_format_sci);
public final int id;
@StringRes
public final int name;
Notation(int id, @StringRes int name) {
this.id = id;
this.name = name;
}
}
public static class ChangedEvent {
static final ChangedEvent INSTANCE = new ChangedEvent();
private ChangedEvent() {
}
}
@ -264,6 +286,7 @@ public class Engine implements SharedPreferences.OnSharedPreferenceChangeListene
public static final StringPreference<Integer> precision = StringPreference.ofTypedValue("engine.output.precision", "5", NumberMapper.of(Integer.class));
public static final BooleanPreference scientificNotation = BooleanPreference.of("engine.output.scientificNotation", false);
public static final BooleanPreference round = BooleanPreference.of("engine.output.round", true);
public static final StringPreference<Notation> notation = StringPreference.ofEnum("engine.output.notation", Notation.simple, Notation.class);
}
}
}

View File

@ -21,21 +21,38 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.*;
import butterknife.Bind;
import butterknife.ButterKnife;
import org.solovyev.android.calculator.*;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import org.solovyev.android.calculator.App;
import org.solovyev.android.calculator.AppComponent;
import org.solovyev.android.calculator.AppModule;
import org.solovyev.android.calculator.BaseDialogFragment;
import org.solovyev.android.calculator.Clipboard;
import org.solovyev.android.calculator.Editor;
import org.solovyev.android.calculator.Keyboard;
import org.solovyev.android.calculator.R;
import org.solovyev.android.calculator.keyboard.FloatingKeyboard;
import org.solovyev.android.calculator.keyboard.FloatingKeyboardWindow;
import org.solovyev.android.calculator.keyboard.FloatingNumberKeyboard;
import org.solovyev.android.calculator.math.MathUtils;
import org.solovyev.android.calculator.text.NaturalComparator;
import org.solovyev.android.calculator.view.EditTextCompat;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.Comparator;
import static org.solovyev.android.calculator.UiPreferences.Converter.*;
import butterknife.Bind;
import butterknife.ButterKnife;
import static org.solovyev.android.calculator.UiPreferences.Converter.lastDimension;
import static org.solovyev.android.calculator.UiPreferences.Converter.lastUnitsFrom;
import static org.solovyev.android.calculator.UiPreferences.Converter.lastUnitsTo;
public class ConverterFragment extends BaseDialogFragment
implements AdapterView.OnItemSelectedListener, View.OnFocusChangeListener, TextView.OnEditorActionListener, View.OnClickListener, TextWatcher {
@ -44,7 +61,6 @@ public class ConverterFragment extends BaseDialogFragment
private static final String STATE_SELECTION_FROM = "selection.from";
private static final String STATE_SELECTION_TO = "selection.to";
private static final String EXTRA_VALUE = "value";
private static final NamedItemComparator COMPARATOR = new NamedItemComparator();
public static final int NONE = -1;
@NonNull
@ -96,11 +112,6 @@ public class ConverterFragment extends BaseDialogFragment
App.showDialog(fragment, "converter", activity.getSupportFragmentManager());
}
@Nonnull
private static <T> ArrayAdapter<T> makeAdapter(@NonNull Context context) {
return new ArrayAdapter<>(context, R.layout.support_simple_spinner_dropdown_item);
}
@NonNull
@Override
public AlertDialog onCreateDialog(@Nullable Bundle savedInstanceState) {
@ -130,13 +141,13 @@ public class ConverterFragment extends BaseDialogFragment
final View view = inflater.inflate(R.layout.cpp_unit_converter, null);
ButterKnife.bind(this, view);
dimensionsAdapter = makeAdapter(context);
dimensionsAdapter = App.makeSimpleSpinnerAdapter(context);
for (ConvertibleDimension dimension : UnitDimension.values()) {
dimensionsAdapter.add(dimension.named(context));
}
dimensionsAdapter.add(NumeralBaseDimension.get().named(context));
adapterFrom = makeAdapter(context);
adapterTo = makeAdapter(context);
adapterFrom = App.makeSimpleSpinnerAdapter(context);
adapterTo = App.makeSimpleSpinnerAdapter(context);
dimensionsSpinner.setAdapter(dimensionsAdapter);
spinnerFrom.setAdapter(adapterFrom);
@ -235,7 +246,7 @@ public class ConverterFragment extends BaseDialogFragment
for (Convertible unit : dimension.getUnits()) {
adapterFrom.add(unit.named(getActivity()));
}
adapterFrom.sort(COMPARATOR);
adapterFrom.sort(NaturalComparator.INSTANCE);
adapterFrom.setNotifyOnChange(true);
adapterFrom.notifyDataSetChanged();
if (pendingFromSelection != NONE) {
@ -261,7 +272,7 @@ public class ConverterFragment extends BaseDialogFragment
adapterTo.add(unit.named(getActivity()));
}
}
adapterTo.sort(COMPARATOR);
adapterTo.sort(NaturalComparator.INSTANCE);
adapterTo.setNotifyOnChange(true);
adapterTo.notifyDataSetChanged();
if (selectedUnit != null && !except.equals(selectedUnit)) {
@ -431,13 +442,6 @@ public class ConverterFragment extends BaseDialogFragment
super.dismiss();
}
private static class NamedItemComparator implements Comparator<Named<Convertible>> {
@Override
public int compare(Named<Convertible> lhs, Named<Convertible> rhs) {
return lhs.toString().compareTo(rhs.toString());
}
}
private class KeyboardUser implements FloatingKeyboard.User {
@NonNull
@Override

View File

@ -0,0 +1,100 @@
package org.solovyev.android.calculator.preferences;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.DialogPreference;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.SeekBar;
import android.widget.Spinner;
import org.solovyev.android.calculator.App;
import org.solovyev.android.calculator.Engine;
import org.solovyev.android.calculator.R;
import org.solovyev.android.calculator.text.NaturalComparator;
import butterknife.Bind;
import butterknife.ButterKnife;
public class NumberFormatPreference extends DialogPreference {
@Bind(R.id.nf_notation_spinner)
Spinner notationSpinner;
@Bind(R.id.nf_precision_seekbar)
SeekBar precisionSeekBar;
{
setPersistent(false);
setDialogLayoutResource(R.layout.preference_number_format);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public NumberFormatPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public NumberFormatPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public NumberFormatPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public NumberFormatPreference(Context context) {
super(context);
}
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
ButterKnife.bind(this, view);
final SharedPreferences preferences = getSharedPreferences();
precisionSeekBar.setMax(15);
precisionSeekBar.setProgress(Math.max(0, Math.min(15, Engine.Preferences.Output.precision.getPreference(preferences))));
final ArrayAdapter<NotationItem> adapter = makeNumberFormatAdapter();
notationSpinner.setAdapter(adapter);
notationSpinner.setSelection(indexOf(adapter, Engine.Preferences.Output.notation.getPreference(preferences)));
}
private int indexOf(ArrayAdapter<NotationItem> adapter, Engine.Notation notation) {
for (int i = 0; i < adapter.getCount(); i++) {
if (adapter.getItem(i).notation == notation) {
return i;
}
}
return -1;
}
@NonNull
private ArrayAdapter<NotationItem> makeNumberFormatAdapter() {
final ArrayAdapter<NotationItem> adapter = App.makeSimpleSpinnerAdapter(getContext());
for (Engine.Notation format : Engine.Notation.values()) {
adapter.add(new NotationItem(format));
}
adapter.sort(NaturalComparator.INSTANCE);
return adapter;
}
private final class NotationItem {
@NonNull
final Engine.Notation notation;
@NonNull
final String name;
private NotationItem(@NonNull Engine.Notation notation) {
this.notation = notation;
this.name = getContext().getString(notation.name);
}
@Override
public String toString() {
return name;
}
}
}

View File

@ -11,6 +11,7 @@ import android.support.v4.app.FragmentActivity;
import android.util.SparseArray;
import android.view.View;
import android.widget.ListView;
import org.solovyev.android.calculator.AdView;
import org.solovyev.android.calculator.Engine;
import org.solovyev.android.calculator.Preferences;
@ -24,11 +25,12 @@ import org.solovyev.android.checkout.ProductTypes;
import org.solovyev.android.checkout.RequestListener;
import org.solovyev.android.wizard.Wizards;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.List;
import static org.solovyev.android.calculator.App.cast;
import static org.solovyev.android.calculator.wizard.CalculatorWizards.DEFAULT_WIZARD_FLOW;

View File

@ -0,0 +1,12 @@
package org.solovyev.android.calculator.text;
import java.util.Comparator;
public class NaturalComparator implements Comparator<Object> {
public static final NaturalComparator INSTANCE = new NaturalComparator();
@Override
public int compare(Object lhs, Object rhs) {
return lhs.toString().compareTo(rhs.toString());
}
}

View File

@ -0,0 +1,224 @@
package org.solovyev.android.views;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
import android.view.animation.DecelerateInterpolator;
import android.widget.SeekBar;
import org.solovyev.android.Check;
import org.solovyev.android.calculator.R;
/**
* SeekBar for discrete values with a label displayed underneath the active tick
*/
public class DiscreteSeekBar extends SeekBar {
// Duration of how quick the SeekBar thumb should snap to its destination value
private static final int THUMB_SNAP_DURATION_TIME = 100;
private final Paint mPaint = new Paint();
private ObjectAnimator mObjectAnimator;
private OnChangeListener mOnChangeListener;
private int mCurrentTick = 0;
private CharSequence[] mTickLabels;
private ColorStateList mLabelColor;
public DiscreteSeekBar(Context context) {
super(context);
init(context, null, 0);
}
public DiscreteSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public DiscreteSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.DiscreteSeekBar, defStyle, 0);
mTickLabels = a.getTextArray(R.styleable.DiscreteSeekBar_values);
final int labelsSize = a.getDimensionPixelSize(R.styleable.DiscreteSeekBar_labelsSize, 0);
final ColorStateList labelsColor = a.getColorStateList(R.styleable.DiscreteSeekBar_labelsColor);
a.recycle();
Check.isNotNull(mTickLabels);
Check.isTrue(mTickLabels.length > 0);
Check.isTrue(labelsSize > 0);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextSize(labelsSize);
if (labelsColor != null) {
setLabelColor(labelsColor);
} else {
mPaint.setColor(Color.BLACK);
}
// Extend the bottom padding to include tick label height (including descent in order to not
// clip glyphs that extends below the baseline).
Paint.FontMetricsInt fi = mPaint.getFontMetricsInt();
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
getPaddingBottom() + labelsSize + fi.descent);
super.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
cancelAnimator();
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void cancelAnimator() {
if (mObjectAnimator != null) {
mObjectAnimator.cancel();
}
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mCurrentTick = getClosestTick(seekBar.getProgress());
final int endProgress = getProgressForTick(mCurrentTick);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
startAnimator(seekBar, endProgress);
} else {
seekBar.setProgress(endProgress);
}
if (mOnChangeListener != null) {
mOnChangeListener.onValueChanged(mCurrentTick);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void startAnimator(SeekBar seekBar, int endProgress) {
mObjectAnimator = ObjectAnimator.ofInt(
seekBar, "progress", seekBar.getProgress(), endProgress);
mObjectAnimator.setInterpolator(new DecelerateInterpolator());
mObjectAnimator.setDuration(THUMB_SNAP_DURATION_TIME);
mObjectAnimator.start();
}
});
}
private int getClosestTick(int progress) {
float normalizedValue = (float) progress / getMax();
return Math.round(normalizedValue * getMaxTick());
}
private int getProgressForTick(int tick) {
return (getMax() / getMaxTick()) * tick;
}
@Override
public void setOnSeekBarChangeListener(OnSeekBarChangeListener seekBarChangeListener) {
// It doesn't make sense to expose the interface for listening to intermediate changes.
Check.isTrue(false);
}
/**
* Get the largest tick value the SeekBar can represent
*
* @return maximum tick value
*/
public int getMaxTick() {
return mTickLabels.length - 1;
}
/**
* Set listener for observing value changes
*
* @param onChangeListener listener that should receive updates
*/
public void setOnChangeListener(OnChangeListener onChangeListener) {
mOnChangeListener = onChangeListener;
}
/**
* Set tick value
*
* @param tickValue tick value in range [0, maxTick]
*/
public void setTickValue(int tickValue) {
Check.isTrue(tickValue >= 0);
Check.isTrue(tickValue <= getMaxTick());
mCurrentTick = tickValue;
setProgress(getProgressForTick(mCurrentTick));
}
public void setLabelColor(int color) {
mLabelColor = ColorStateList.valueOf(color);
updateLabelColor();
}
public void setLabelColor(ColorStateList colors) {
mLabelColor = colors;
updateLabelColor();
}
private void updateLabelColor() {
int color = mLabelColor.getColorForState(getDrawableState(), Color.BLACK);
if (color != mPaint.getColor()) {
mPaint.setColor(color);
invalidate();
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
updateLabelColor();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
final float sliderWidth = getWidth() - getPaddingRight() - getPaddingLeft();
final float sliderStepSize = sliderWidth / getMaxTick();
int closestTick = getClosestTick(getProgress());
String text = mTickLabels[closestTick].toString();
final float startOffset = getPaddingLeft();
final float tickLabelWidth = mPaint.measureText(text);
final float tickPos = sliderStepSize * closestTick;
final float labelOffset;
// First step description text should be anchored with its left edge just
// below the slider start tick. The last step description should be anchored
// to the right just under the end tick. Tick labels in between are centered below
// each tick.
if (closestTick == 0) {
labelOffset = startOffset;
} else if (closestTick == getMaxTick()) {
labelOffset = startOffset + sliderWidth - tickLabelWidth;
} else {
labelOffset = startOffset + tickPos - tickLabelWidth / 2;
}
// Text position is drawn from bottom left, with bottom at the font baseline. We need to
// offset by the descent to cover e.g 'g' that extends below the baseline.
final Paint.FontMetricsInt m = mPaint.getFontMetricsInt();
final int lowestPosForFullGlyphCoverage = getHeight() - m.descent;
canvas.drawText(text, labelOffset, lowestPosForFullGlyphCoverage, mPaint);
}
/**
* Listener for observing tick changes
*/
public interface OnChangeListener {
void onValueChanged(int selectedTick);
}
}

View File

@ -59,11 +59,9 @@
<TextView
android:id="@+id/fn_linewidth_label"
style="@style/TextAppearance.AppCompat.Caption"
style="@style/CppLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:text="@string/cpp_plot_function_line_width"
tools:ignore="RtlSymmetry"/>
@ -74,11 +72,9 @@
<TextView
android:id="@+id/fn_color_label"
style="@style/TextAppearance.AppCompat.Caption"
style="@style/CppLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:text="@string/cpp_plot_function_line_color"
tools:ignore="RtlSymmetry"/>

View File

@ -0,0 +1,34 @@
<?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="match_parent"
android:orientation="vertical"
android:padding="@dimen/cpp_dialog_spacing">
<TextView
style="@style/CppLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Notation"/>
<Spinner
android:id="@+id/nf_notation_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
style="@style/CppLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Precision"/>
<org.solovyev.android.views.DiscreteSeekBar
android:id="@+id/nf_precision_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:labelsColor="?android:attr/textColorSecondary"
app:labelsSize="12sp"
app:values="@array/cpp_prefs_precisions"/>
</LinearLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DiscreteSeekBar">
<attr name="values" format="reference"/>
<attr name="labelsSize" format="dimension|reference"/>
<attr name="labelsColor" format="color|reference"/>
</declare-styleable>
</resources>

View File

@ -319,6 +319,11 @@
<item name="android:visibility">gone</item>
</style>
<style name="CppLabel" parent="TextAppearance.AppCompat.Caption">
<item name="android:paddingLeft">4dp</item>
<item name="android:paddingRight">4dp</item>
</style>
<dimen name="list_item_text_size">16sp</dimen>
<dimen name="list_item_text_size_small">14sp</dimen>
</resources>

View File

@ -34,6 +34,5 @@
<item>13</item>
<item>14</item>
<item>15</item>
<item>16</item>
</string-array>
</resources>

View File

@ -124,4 +124,7 @@
<string name="cpp_angles">Angles</string>
<string name="cpp_radix">Radix</string>
<string name="cpp_numeral_system">Numeral system</string>
<string name="cpp_number_format_simple">Simple</string>
<string name="cpp_number_format_eng">Engineering</string>
<string name="cpp_number_format_sci">Scientific</string>
</resources>

View File

@ -24,11 +24,9 @@
<PreferenceScreen xmlns:a="http://schemas.android.com/apk/res/android">
<android.preference.CheckBoxPreference
a:defaultValue="true"
a:key="engine.output.round"
a:summary="@string/c_calc_round_result_summary"
a:title="@string/c_calc_round_result_title" />
<org.solovyev.android.calculator.preferences.NumberFormatPreference
a:key="engine.output.numberFormat"
a:title="Number format" />
<ListPreference
a:entries="@array/cpp_prefs_precisions"