From 1f5838ce16f0db081054a9b975d4da1f2f83f85f Mon Sep 17 00:00:00 2001 From: Ivan Murashov Date: Mon, 26 Mar 2018 00:33:24 +0300 Subject: [PATCH] Autoscroll when keyboard open on purchase screen --- ios/Runner.xcodeproj/project.pbxproj | 10 ++-- lib/base/base_state.dart | 81 ---------------------------- lib/screens/purchase.dart | 48 +++++++++++++++-- lib/screens/registration.dart | 6 +-- 4 files changed, 49 insertions(+), 96 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 59fe18b..1c960d9 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -193,7 +193,6 @@ 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; }; 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 = ""; }; - 323C7CAF205A43FC0051BB7F /* Dinect-INT.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Dinect-INT.entitlements"; sourceTree = ""; }; 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 = ""; }; 32DA147B1FBC3DCE008F0388 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; @@ -343,7 +342,6 @@ 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( - 323C7CAF205A43FC0051BB7F /* Dinect-INT.entitlements */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, @@ -733,7 +731,7 @@ ); inputPaths = ( "${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"; outputPaths = ( @@ -794,7 +792,7 @@ ); inputPaths = ( "${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"; outputPaths = ( @@ -812,7 +810,7 @@ ); inputPaths = ( "${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"; outputPaths = ( @@ -1288,7 +1286,6 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Checker-INT"; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = "Dinect-INT.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; @@ -1322,7 +1319,6 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Checker-INT"; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = "Dinect-INT.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; diff --git a/lib/base/base_state.dart b/lib/base/base_state.dart index 33573c7..a3ab40f 100644 --- a/lib/base/base_state.dart +++ b/lib/base/base_state.dart @@ -318,84 +318,3 @@ abstract class BaseState extends State { return new Container(margin: margin, height: buttonHeight, child: new Row(children: [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 { - @override - void initState() { - super.initState(); - widget.focusNode.addListener(_ensureVisible); - } - - @override - void dispose() { - super.dispose(); - widget.focusNode.removeListener(_ensureVisible); - } - - Future _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; -} diff --git a/lib/screens/purchase.dart b/lib/screens/purchase.dart index 8c82118..53f48cb 100644 --- a/lib/screens/purchase.dart +++ b/lib/screens/purchase.dart @@ -45,6 +45,8 @@ class PurchaseScreenState extends BaseState { void initState() { loading = true; requestAsyncData(user); + buildFocusNode(); + scrollController = new ScrollController(); super.initState(); } @@ -54,12 +56,14 @@ class PurchaseScreenState extends BaseState { } bool purchaseInProgress = false; + ScrollController scrollController; Map user; String card = ''; String loyalityType = ''; String loyalty = ''; String bonus = ''; List coupons = []; + ListView listView; @override Widget getScreenContent() { @@ -119,8 +123,8 @@ class PurchaseScreenState extends BaseState { ) )); - return new Container(child: new ListView(reverse: true, - children: [new Column(children: widgetList)].reversed.toList())); + listView = new ListView(children: widgetList, controller: scrollController); + return listView; } getBonusInputField() { @@ -130,6 +134,7 @@ class PurchaseScreenState extends BaseState { hintText: (app != 'crypto') ? StringsLocalization.bonusHint() : StringsLocalization.joysHint(), hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0) ), + focusNode: bonusFocusNode, controller: bonusController, onSubmitted: (String text) { setState(() { @@ -206,13 +211,13 @@ class PurchaseScreenState extends BaseState { hintText: getHintString(), hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)), controller: controller, + focusNode: sumFocusNode, onSubmitted: (String text) { setState(() { controller.text = _parseSum(text); }); }, - textAlign: TextAlign.center, - autofocus: true); + textAlign: TextAlign.center); } requestAsyncData(Map user) async { @@ -436,4 +441,39 @@ class PurchaseScreenState extends BaseState { 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); + } + + }); + }); + } } diff --git a/lib/screens/registration.dart b/lib/screens/registration.dart index f7a3705..300c177 100644 --- a/lib/screens/registration.dart +++ b/lib/screens/registration.dart @@ -57,15 +57,13 @@ class RegistrationScreenState extends BaseState { @override getTextWidget() { - return new EnsureVisibleWhenFocused( - focusNode: _focusNode, - child: new TextField( + return new TextField( focusNode: _focusNode, keyboardType: TextInputType.number, decoration: new InputDecoration.collapsed( hintText: getHintString(), hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)), - onChanged: (text) => handleUserInput(text))); + onChanged: (text) => handleUserInput(text)); } /// Возвращает кнопку регистрации.