Commit 279c6149 authored by Jason Simmons's avatar Jason Simmons

Merge pull request #1747 from jason-simmons/input_field_clear

Change the text/selection value API of the input field
parents c27dc641 52c55344
......@@ -51,10 +51,10 @@ class MealFragment extends StatefulComponent {
}
class MealFragmentState extends State<MealFragment> {
String _description = "";
InputValue _description = InputValue.empty;
void _handleSave() {
config.onCreated(new Meal(when: new DateTime.now(), description: _description));
config.onCreated(new Meal(when: new DateTime.now(), description: _description.text));
Navigator.pop(context);
}
......@@ -75,7 +75,7 @@ class MealFragmentState extends State<MealFragment> {
);
}
void _handleDescriptionChanged(String description) {
void _handleDescriptionChanged(InputValue description) {
setState(() {
_description = description;
});
......
......@@ -63,13 +63,13 @@ class MeasurementFragment extends StatefulComponent {
}
class MeasurementFragmentState extends State<MeasurementFragment> {
String _weight = "";
InputValue _weight = InputValue.empty;
DateTime _when = new DateTime.now();
void _handleSave() {
double parsedWeight;
try {
parsedWeight = double.parse(_weight);
parsedWeight = double.parse(_weight.text);
} on FormatException catch(e) {
print("Exception $e");
Scaffold.of(context).showSnackBar(new SnackBar(
......@@ -97,7 +97,7 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
);
}
void _handleWeightChanged(String weight) {
void _handleWeightChanged(InputValue weight) {
setState(() {
_weight = weight;
});
......
......@@ -4,6 +4,64 @@
part of fitness;
class _SettingsDialog extends StatefulComponent {
_SettingsDialogState createState() => new _SettingsDialogState();
}
class _SettingsDialogState extends State<_SettingsDialog> {
final GlobalKey weightGoalKey = new GlobalKey();
InputValue _goalWeight = InputValue.empty;
void _handleGoalWeightChanged(InputValue goalWeight) {
setState(() {
_goalWeight = goalWeight;
});
}
void _handleGoalWeightSubmitted(InputValue goalWeight) {
_goalWeight = goalWeight;
_handleSavePressed();
}
void _handleSavePressed() {
double goalWeight;
try {
goalWeight = double.parse(_goalWeight.text);
} on FormatException {
goalWeight = 0.0;
}
Navigator.pop(context, goalWeight);
}
Widget build(BuildContext context) {
return new Dialog(
title: new Text("Goal Weight"),
content: new Input(
key: weightGoalKey,
value: _goalWeight,
autofocus: true,
hintText: 'Goal weight in lbs',
keyboardType: KeyboardType.number,
onChanged: _handleGoalWeightChanged,
onSubmitted: _handleGoalWeightSubmitted
),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
}
),
new FlatButton(
child: new Text('SAVE'),
onPressed: _handleSavePressed
),
]
);
}
}
typedef void SettingsUpdater({
BackupMode backup,
double goalWeight
......@@ -36,52 +94,10 @@ class SettingsFragmentState extends State<SettingsFragment> {
return "${config.userData.goalWeight}";
}
static final GlobalKey weightGoalKey = new GlobalKey();
double _goalWeight;
void _handleGoalWeightChanged(String goalWeight) {
// TODO(jackson): Looking for null characters to detect enter key is a hack
if (goalWeight.endsWith("\u{0}")) {
Navigator.pop(context, double.parse(goalWeight.replaceAll("\u{0}", "")));
} else {
setState(() {
try {
_goalWeight = double.parse(goalWeight);
} on FormatException {
_goalWeight = 0.0;
}
});
}
}
Future _handleGoalWeightPressed() async {
double goalWeight = await showDialog(
context: context,
child: new Dialog(
title: new Text("Goal Weight"),
content: new Input(
key: weightGoalKey,
autofocus: true,
hintText: 'Goal weight in lbs',
keyboardType: KeyboardType.number,
onChanged: _handleGoalWeightChanged
),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
}
),
new FlatButton(
child: new Text('SAVE'),
onPressed: () {
Navigator.pop(context, _goalWeight);
}
),
]
)
child: new _SettingsDialog()
);
config.updater(goalWeight: goalWeight);
}
......
......@@ -33,14 +33,14 @@ class StockHomeState extends State<StockHome> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
bool _isSearching = false;
String _searchQuery;
InputValue _searchQuery = InputValue.empty;
void _handleSearchBegin() {
ModalRoute.of(context).addLocalHistoryEntry(new LocalHistoryEntry(
onRemove: () {
setState(() {
_isSearching = false;
_searchQuery = null;
_searchQuery = InputValue.empty;
});
}
));
......@@ -53,7 +53,7 @@ class StockHomeState extends State<StockHome> {
Navigator.pop(context);
}
void _handleSearchQueryChanged(String query) {
void _handleSearchQueryChanged(InputValue query) {
setState(() {
_searchQuery = query;
});
......@@ -197,9 +197,9 @@ class StockHomeState extends State<StockHome> {
}
Iterable<Stock> _filterBySearchQuery(Iterable<Stock> stocks) {
if (_searchQuery == null)
if (_searchQuery.text.isEmpty)
return stocks;
RegExp regexp = new RegExp(_searchQuery, caseSensitive: false);
RegExp regexp = new RegExp(_searchQuery.text, caseSensitive: false);
return stocks.where((Stock stock) => stock.symbol.contains(regexp));
}
......@@ -254,6 +254,7 @@ class StockHomeState extends State<StockHome> {
tooltip: 'Back'
),
center: new Input(
value: _searchQuery,
key: searchFieldKey,
autofocus: true,
hintText: 'Search stocks',
......
......@@ -7,12 +7,12 @@ import 'package:flutter/rendering.dart' show debugDumpRenderTree;
class CardModel {
CardModel(this.value, this.height) {
label = "Item $value";
inputValue = new InputValue(text: "Item $value");
}
int value;
double height;
int get color => ((value % 9) + 1) * 100;
String label;
InputValue inputValue;
Key get key => new ObjectKey(this);
}
......@@ -305,9 +305,11 @@ class CardCollectionState extends State<CardCollection> {
new Center(
child: new Input(
key: new GlobalObjectKey(cardModel),
initialValue: cardModel.label,
onChanged: (String value) {
cardModel.label = value;
value: cardModel.inputValue,
onChanged: (InputValue value) {
setState(() {
cardModel.inputValue = value;
});
}
)
)
......@@ -317,7 +319,7 @@ class CardCollectionState extends State<CardCollection> {
),
child: new Column(
children: <Widget>[
new Text(cardModel.label)
new Text(cardModel.inputValue.text)
],
alignItems: FlexAlignItems.stretch,
justifyContent: FlexJustifyContent.center
......
......@@ -4,7 +4,6 @@
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:sky_services/editing/editing.mojom.dart' as mojom;
import 'colors.dart';
import 'debug.dart';
......@@ -17,8 +16,7 @@ export 'package:sky_services/editing/editing.mojom.dart' show KeyboardType;
class Input extends StatefulComponent {
Input({
GlobalKey key,
this.initialValue: '',
this.initialSelection,
this.value: InputValue.empty,
this.keyboardType: KeyboardType.text,
this.icon,
this.labelText,
......@@ -34,11 +32,8 @@ class Input extends StatefulComponent {
assert(key != null);
}
/// The initial editable text for the input field.
final String initialValue;
/// The initial selection for this input field.
final TextSelection initialSelection;
/// The text of the input field.
final InputValue value;
/// The type of keyboard to use for editing the text.
final KeyboardType keyboardType;
......@@ -68,10 +63,10 @@ class Input extends StatefulComponent {
final bool autofocus;
/// Called when the text being edited changes.
final ValueChanged<String> onChanged;
final ValueChanged<InputValue> onChanged;
/// Called when the user indicates that they are done editing the text in the field.
final ValueChanged<String> onSubmitted;
final ValueChanged<InputValue> onSubmitted;
_InputState createState() => new _InputState();
}
......@@ -80,89 +75,13 @@ const Duration _kTransitionDuration = const Duration(milliseconds: 200);
const Curve _kTransitionCurve = Curves.ease;
class _InputState extends State<Input> {
String _value;
EditableString _editableString;
KeyboardHandle _keyboardHandle;
// Used by tests.
EditableString get editableValue => _editableString;
void initState() {
super.initState();
_value = config.initialValue;
_editableString = new EditableString(
text: _value,
selection: config.initialSelection,
onUpdated: _handleTextUpdated,
onSubmitted: _handleTextSubmitted
);
}
void dispose() {
if (_isAttachedToKeyboard)
_keyboardHandle.release();
super.dispose();
}
bool get _isAttachedToKeyboard => _keyboardHandle != null && _keyboardHandle.attached;
void _attachOrDetachKeyboard(bool focused) {
if (focused && !_isAttachedToKeyboard) {
_keyboardHandle = keyboard.attach(_editableString.createStub(),
new mojom.KeyboardConfiguration()
..type = config.keyboardType);
_keyboardHandle.setEditingState(_editableString.editingState);
_keyboardHandle.show();
} else if (!focused && _isAttachedToKeyboard) {
_keyboardHandle.release();
_keyboardHandle = null;
_editableString.didDetachKeyboard();
}
}
void _requestKeyboard() {
if (Focus.at(context)) {
assert(_isAttachedToKeyboard);
_keyboardHandle.show();
} else {
Focus.moveTo(config.key);
// we'll get told to rebuild and we'll take care of the keyboard then
}
}
void _handleTextUpdated() {
if (_value != _editableString.text) {
setState(() {
_value = _editableString.text;
});
if (config.onChanged != null)
config.onChanged(_value);
}
}
void _handleTextSubmitted() {
Focus.clear(context);
if (config.onSubmitted != null)
config.onSubmitted(_value);
}
void _handleSelectionChanged(TextSelection selection) {
if (_isAttachedToKeyboard) {
_editableString.setSelection(selection);
_keyboardHandle.setEditingState(_editableString.editingState);
} else {
_editableString.setSelection(selection);
_requestKeyboard();
}
}
GlobalKey<RawInputLineState> _rawInputLineKey = new GlobalKey<RawInputLineState>();
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
ThemeData themeData = Theme.of(context);
bool focused = Focus.at(context, autofocus: config.autofocus);
_attachOrDetachKeyboard(focused);
TextStyle textStyle = config.style ?? themeData.text.subhead;
Color focusHighlightColor = themeData.accentColor;
if (themeData.primarySwatch != null)
......@@ -171,7 +90,7 @@ class _InputState extends State<Input> {
List<Widget> stackChildren = <Widget>[];
bool hasInlineLabel = config.labelText != null && !focused && !_value.isNotEmpty;
bool hasInlineLabel = config.labelText != null && !focused && !config.value.text.isNotEmpty;
if (config.labelText != null) {
TextStyle labelStyle = hasInlineLabel ?
......@@ -194,7 +113,7 @@ class _InputState extends State<Input> {
topPadding += topPaddingIncrement;
}
if (config.hintText != null && _value.isEmpty && !hasInlineLabel) {
if (config.hintText != null && config.value.text.isEmpty && !hasInlineLabel) {
TextStyle hintStyle = themeData.text.subhead.copyWith(color: themeData.hintColor);
stackChildren.add(new Positioned(
left: 0.0,
......@@ -234,14 +153,17 @@ class _InputState extends State<Input> {
)
)
),
child: new RawEditableLine(
value: _editableString,
focused: focused,
child: new RawInputLine(
key: _rawInputLineKey,
value: config.value,
focusKey: config.key,
style: textStyle,
hideText: config.hideText,
cursorColor: cursorColor,
selectionColor: cursorColor,
onSelectionChanged: _handleSelectionChanged
keyboardType: config.keyboardType,
onChanged: config.onChanged,
onSubmitted: config.onSubmitted
)
));
......@@ -278,7 +200,7 @@ class _InputState extends State<Input> {
return new GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _requestKeyboard,
onTap: () => _rawInputLineKey.currentState?.requestKeyboard(),
child: new Padding(
padding: const EdgeDims.symmetric(horizontal: 16.0),
child: child
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show TextAffinity, TextPosition;
import 'dart:ui' show hashValues, TextAffinity, TextPosition;
export 'dart:ui' show TextAffinity, TextPosition;
......@@ -50,6 +50,22 @@ class TextRange {
assert(isNormalized);
return text.substring(start, end);
}
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! TextRange)
return false;
TextRange typedOther = other;
return typedOther.start == start
&& typedOther.end == end;
}
int get hashCode => hashValues(
start.hashCode,
end.hashCode
);
}
/// A range of text that represents a selection.
......@@ -122,4 +138,23 @@ class TextSelection extends TextRange {
String toString() {
return '$runtimeType(baseOffset: $baseOffset, extentOffset: $extentOffset, affinity: $affinity, isDirectional: $isDirectional)';
}
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! TextSelection)
return false;
TextSelection typedOther = other;
return typedOther.baseOffset == baseOffset
&& typedOther.extentOffset == extentOffset
&& typedOther.affinity == affinity
&& typedOther.isDirectional == isDirectional;
}
int get hashCode => hashValues(
baseOffset.hashCode,
extentOffset.hashCode,
affinity.hashCode,
isDirectional.hashCode
);
}
......@@ -30,15 +30,16 @@ void main() {
test('Editable text has consistent size', () {
testWidgets((WidgetTester tester) {
GlobalKey inputKey = new GlobalKey();
String inputValue;
InputValue inputValue = InputValue.empty;
Widget builder() {
return new Center(
child: new Material(
child: new Input(
value: inputValue,
key: inputKey,
hintText: 'Placeholder',
onChanged: (String value) { inputValue = value; }
onChanged: (InputValue value) { inputValue = value; }
)
)
);
......@@ -58,7 +59,7 @@ void main() {
..composingExtent = testValue.length);
// Check that the onChanged event handler fired.
expect(inputValue, equals(testValue));
expect(inputValue.text, equals(testValue));
tester.pumpWidget(builder());
}
......@@ -88,7 +89,7 @@ void main() {
tester.pumpWidget(builder());
RawEditableTextState editableText = tester.findStateOfType(RawEditableTextState);
RawInputLineState editableText = tester.findStateOfType(RawInputLineState);
// Check that the cursor visibility toggles after each blink interval.
void checkCursorToggle() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment