Autoscroll when keyboard open on purchase screen

This commit is contained in:
Ivan Murashov
2018-03-26 00:33:24 +03:00
parent b6abdac78b
commit 1f5838ce16
4 changed files with 49 additions and 96 deletions

View File

@@ -193,7 +193,6 @@
28B464359F9DDCC3EF756D7D /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 28B464359F9DDCC3EF756D7D /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
31F92512BEA3153C65AA90AD /* Pods-Runner.release-dinect.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-dinect.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-dinect.xcconfig"; sourceTree = "<group>"; }; 31F92512BEA3153C65AA90AD /* Pods-Runner.release-dinect.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-dinect.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-dinect.xcconfig"; sourceTree = "<group>"; };
323C7CAF205A43FC0051BB7F /* Dinect-INT.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Dinect-INT.entitlements"; sourceTree = "<group>"; };
328A58AF205F68270039EA5A /* Dinect.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Dinect.app; sourceTree = BUILT_PRODUCTS_DIR; }; 328A58AF205F68270039EA5A /* Dinect.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Dinect.app; sourceTree = BUILT_PRODUCTS_DIR; };
328A58B0205F68270039EA5A /* Runner copy2-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Runner copy2-Info.plist"; path = "/Users/dinect/projects/checker/ios/Runner copy2-Info.plist"; sourceTree = "<absolute>"; }; 328A58B0205F68270039EA5A /* Runner copy2-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Runner copy2-Info.plist"; path = "/Users/dinect/projects/checker/ios/Runner copy2-Info.plist"; sourceTree = "<absolute>"; };
32DA147B1FBC3DCE008F0388 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; }; 32DA147B1FBC3DCE008F0388 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
@@ -343,7 +342,6 @@
97C146E51CF9000F007C117D = { 97C146E51CF9000F007C117D = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
323C7CAF205A43FC0051BB7F /* Dinect-INT.entitlements */,
9740EEB11CF90186004384FC /* Flutter */, 9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
@@ -733,7 +731,7 @@
); );
inputPaths = ( inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../../../flutter/bin/cache/artifacts/engine/ios-release/Flutter.framework", "${PODS_ROOT}/../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
@@ -794,7 +792,7 @@
); );
inputPaths = ( inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Crypto/Pods-Crypto-frameworks.sh", "${SRCROOT}/Pods/Target Support Files/Pods-Crypto/Pods-Crypto-frameworks.sh",
"${PODS_ROOT}/../../../flutter/bin/cache/artifacts/engine/ios-release/Flutter.framework", "${PODS_ROOT}/../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
@@ -812,7 +810,7 @@
); );
inputPaths = ( inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Dinect/Pods-Dinect-frameworks.sh", "${SRCROOT}/Pods/Target Support Files/Pods-Dinect/Pods-Dinect-frameworks.sh",
"${PODS_ROOT}/../../../flutter/bin/cache/artifacts/engine/ios-release/Flutter.framework", "${PODS_ROOT}/../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
@@ -1288,7 +1286,6 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Checker-INT"; ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Checker-INT";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Dinect-INT.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@@ -1322,7 +1319,6 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Checker-INT"; ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Checker-INT";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Dinect-INT.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;

View File

@@ -318,84 +318,3 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
return new Container(margin: margin, height: buttonHeight, child: new Row(children: <Widget>[new Expanded(child: widget)])); return new Container(margin: margin, height: buttonHeight, child: new Row(children: <Widget>[new Expanded(child: widget)]));
} }
} }
class EnsureVisibleWhenFocused extends StatefulWidget {
const EnsureVisibleWhenFocused({
Key key,
@required this.child,
@required this.focusNode,
this.curve: Curves.ease,
this.duration: const Duration(milliseconds: 100),
}) : super(key: key);
/// The node we will monitor to determine if the child is focused
final FocusNode focusNode;
/// The child widget that we are wrapping
final Widget child;
/// The curve we will use to scroll ourselves into view.
///
/// Defaults to Curves.ease.
final Curve curve;
/// The duration we will use to scroll ourselves into view
///
/// Defaults to 100 milliseconds.
final Duration duration;
EnsureVisibleWhenFocusedState createState() => new EnsureVisibleWhenFocusedState();
}
class EnsureVisibleWhenFocusedState extends State<EnsureVisibleWhenFocused> {
@override
void initState() {
super.initState();
widget.focusNode.addListener(_ensureVisible);
}
@override
void dispose() {
super.dispose();
widget.focusNode.removeListener(_ensureVisible);
}
Future<Null> _ensureVisible() async {
// Wait for the keyboard to come into view
// TODO: position doesn't seem to notify listeners when metrics change,
// perhaps a NotificationListener around the scrollable could avoid
// the need insert a delay here.
await new Future.delayed(const Duration(milliseconds: 100));
if (!widget.focusNode.hasFocus)
return;
final RenderObject object = context.findRenderObject();
final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
assert(viewport != null);
ScrollableState scrollableState = Scrollable.of(context);
assert(scrollableState != null);
ScrollPosition position = scrollableState.position;
double alignment;
if (position.pixels > viewport.getOffsetToReveal(object, 0.0)) {
// Move down to the top of the viewport
alignment = 0.0;
} else if (position.pixels < viewport.getOffsetToReveal(object, 1.0)) {
// Move up to the bottom of the viewport
alignment = 1.0;
} else {
// No scrolling is necessary to reveal the child
return;
}
position.ensureVisible(
object,
alignment: alignment,
duration: widget.duration,
curve: widget.curve,
);
}
Widget build(BuildContext context) => widget.child;
}

View File

@@ -45,6 +45,8 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
void initState() { void initState() {
loading = true; loading = true;
requestAsyncData(user); requestAsyncData(user);
buildFocusNode();
scrollController = new ScrollController();
super.initState(); super.initState();
} }
@@ -54,12 +56,14 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
} }
bool purchaseInProgress = false; bool purchaseInProgress = false;
ScrollController scrollController;
Map user; Map user;
String card = ''; String card = '';
String loyalityType = ''; String loyalityType = '';
String loyalty = ''; String loyalty = '';
String bonus = ''; String bonus = '';
List<Map> coupons = []; List<Map> coupons = [];
ListView listView;
@override @override
Widget getScreenContent() { Widget getScreenContent() {
@@ -119,8 +123,8 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
) )
)); ));
return new Container(child: new ListView(reverse: true, listView = new ListView(children: widgetList, controller: scrollController);
children: <Widget>[new Column(children: widgetList)].reversed.toList())); return listView;
} }
getBonusInputField() { getBonusInputField() {
@@ -130,6 +134,7 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
hintText: (app != 'crypto') ? StringsLocalization.bonusHint() : StringsLocalization.joysHint(), hintText: (app != 'crypto') ? StringsLocalization.bonusHint() : StringsLocalization.joysHint(),
hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0) hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)
), ),
focusNode: bonusFocusNode,
controller: bonusController, controller: bonusController,
onSubmitted: (String text) { onSubmitted: (String text) {
setState(() { setState(() {
@@ -206,13 +211,13 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
hintText: getHintString(), hintText: getHintString(),
hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)), hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)),
controller: controller, controller: controller,
focusNode: sumFocusNode,
onSubmitted: (String text) { onSubmitted: (String text) {
setState(() { setState(() {
controller.text = _parseSum(text); controller.text = _parseSum(text);
}); });
}, },
textAlign: TextAlign.center, textAlign: TextAlign.center);
autofocus: true);
} }
requestAsyncData(Map user) async { requestAsyncData(Map user) async {
@@ -436,4 +441,39 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
Navigator.of(context).pop(token); Navigator.of(context).pop(token);
}); });
} }
FocusNode bonusFocusNode = new FocusNode();
FocusNode sumFocusNode = new FocusNode();
// TODO: Удалить дублирующийся код.
void buildFocusNode() {
sumFocusNode.addListener(() {
setState(() {
if (sumFocusNode.hasFocus && bonusFocusNode.hasFocus) {
bonusFocusNode.unfocus();
}
if (sumFocusNode.hasFocus) {
scrollController.animateTo(100.0, duration: new Duration(seconds: 1), curve: Curves.ease);
}
});
});
bonusFocusNode.addListener(() {
setState(() {
if (bonusFocusNode.hasFocus && sumFocusNode.hasFocus) {
sumFocusNode.unfocus();
}
if (bonusFocusNode.hasFocus) {
scrollController.animateTo(120.0, duration: new Duration(seconds: 1), curve: Curves.ease);
}
});
});
}
} }

View File

@@ -57,15 +57,13 @@ class RegistrationScreenState extends BaseState<RegistrationScreen> {
@override @override
getTextWidget() { getTextWidget() {
return new EnsureVisibleWhenFocused( return new TextField(
focusNode: _focusNode,
child: new TextField(
focusNode: _focusNode, focusNode: _focusNode,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: new InputDecoration.collapsed( decoration: new InputDecoration.collapsed(
hintText: getHintString(), hintText: getHintString(),
hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)), hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)),
onChanged: (text) => handleUserInput(text))); onChanged: (text) => handleUserInput(text));
} }
/// Возвращает кнопку регистрации. /// Возвращает кнопку регистрации.