├── .github
└── workflows
│ ├── master.yml
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example
├── .gitignore
├── .metadata
├── README.md
├── android
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── example
│ │ │ │ └── MainActivity.java
│ │ │ └── res
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ └── styles.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── ios
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── Runner
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── main.m
├── lib
│ ├── content.dart
│ ├── custom_input.dart
│ ├── main.dart
│ ├── sample.dart
│ ├── sample2.dart
│ ├── sample3.dart
│ ├── sample4.dart
│ └── sample5.dart
├── pubspec.yaml
└── test
│ └── widget_test.dart
├── ios
└── Flutter
│ └── flutter_export_environment.sh
├── lib
├── external
│ ├── keyboard_avoider
│ │ ├── bottom_area_avoider.dart
│ │ └── keyboard_avoider.dart
│ └── platform_check
│ │ ├── platform_check.dart
│ │ ├── platform_io.dart
│ │ └── platform_web.dart
├── keyboard_actions.dart
├── keyboard_actions_config.dart
├── keyboard_actions_item.dart
└── keyboard_custom.dart
└── pubspec.yaml
/.github/workflows/master.yml:
--------------------------------------------------------------------------------
1 | name: Package Publish dry-run
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 |
7 | jobs:
8 |
9 | dry-run:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: 'Checkout'
13 | uses: actions/checkout@main
14 | - name: 'Dry-run'
15 | uses: k-paxian/dart-package-publisher@master
16 | with:
17 | accessToken: ${{ secrets.OAUTH_ACCESS_TOKEN }}
18 | refreshToken: ${{ secrets.OAUTH_REFRESH_TOKEN }}
19 | dryRunOnly: true
20 | flutter: true
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Package Publish
2 |
3 | on:
4 | push:
5 | branches: [ release ]
6 |
7 | jobs:
8 | check_version:
9 | name: "Check Version Tag"
10 | runs-on: ubuntu-20.04
11 | steps:
12 | -
13 | uses: actions/checkout@v2
14 | -
15 | continue-on-error: true
16 | env:
17 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
18 | id: previoustag
19 | name: "Get Latest Version"
20 | uses: WyriHaximus/github-action-get-previous-tag@master
21 | -
22 | name: "Print Latest Version"
23 | run: "echo ${{ steps.previoustag.outputs.tag }}"
24 | -
25 | id: config
26 | name: "Get New Version"
27 | uses: CumulusDS/get-yaml-paths-action@v0.1.0
28 | with:
29 | file: pubspec.yaml
30 | version_name: version
31 | -
32 | name: "Print New Version"
33 | run: "echo ${{ steps.config.outputs.version_name }}"
34 | -
35 | if: "steps.config.outputs.version_name == steps.previoustag.outputs.tag"
36 | name: "Compare Version"
37 | run: |
38 | echo 'The version from your pubspec.yaml is the same as Release, Please update the version'
39 | exit 1
40 | -
41 | run: "echo ${{ steps.config.outputs.version_name }} > version.txt\n"
42 | shell: bash
43 | -
44 | name: "Upload New Version"
45 | uses: actions/upload-artifact@v1
46 | with:
47 | name: home
48 | path: version.txt
49 | publish:
50 | needs:
51 | - check_version
52 | runs-on: ubuntu-20.04
53 | steps:
54 | -
55 | name: Checkout
56 | uses: actions/checkout@v1
57 | -
58 | name: "Publish Package"
59 | uses: ilteoood/actions-flutter-pub-publisher@master
60 | with:
61 | credential: "${{secrets.CREDENTIAL_JSON}}"
62 | dry_run: false
63 | flutter_package: true
64 | skip_test: true
65 | tag:
66 | name: "Tag Version"
67 | needs:
68 | - publish
69 | runs-on: ubuntu-20.04
70 | steps:
71 | -
72 | name: "Download New Version"
73 | uses: actions/download-artifact@v1
74 | with:
75 | name: home
76 | -
77 | env:
78 | ACTIONS_ALLOW_UNSECURE_COMMANDS: "true"
79 | name: "Set and Tag the new version"
80 | run: "echo \"::set-env name=RELEASE_VERSION::$(cat home/version.txt)\""
81 | shell: bash
82 | -
83 | uses: tvdias/github-tagger@v0.0.2
84 | with:
85 | repo-token: "${{ secrets.GITHUB_TOKEN }}"
86 | tag: "${{env.RELEASE_VERSION}}"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Taken from https://github.com/flutter/plugins/blob/master/.gitignore
2 |
3 | # OS and editor files
4 | .DS_Store
5 | .atom/
6 | .idea/
7 | .vscode/
8 |
9 | # Generated dart files
10 | .packages
11 | .pub/
12 | .dart_tool/
13 | pubspec.lock
14 |
15 | # Generated iOS files
16 | Podfile
17 | Podfile.lock
18 | Pods/
19 | .symlinks/
20 | **/Flutter/App.framework/
21 | **/Flutter/Flutter.framework/
22 | **/Flutter/Generated.xcconfig
23 | **/Flutter/flutter_assets/
24 | ServiceDefinitions.json
25 | xcuserdata/
26 |
27 | # Android tools and settings
28 | local.properties
29 | keystore.properties
30 | .gradle/
31 | gradlew
32 | gradlew.bat
33 | gradle-wrapper.jar
34 | *.iml
35 |
36 | # Flutter generated files
37 | GeneratedPluginRegistrant.h
38 | GeneratedPluginRegistrant.m
39 | GeneratedPluginRegistrant.java
40 | build/
41 | example/ios/Flutter/Flutter.podspec
42 | example/ios/Flutter/.last_build_id
43 | .fvm/flutter_sdk
44 | .fvm/fvm_config.json
45 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [4.2.0]
2 | * removed unfocusing for android on keyboard change (bug). Thanks `raphire08`.
3 | * minor lints for flutter 3.7.3.
4 |
5 | ## [4.1.0]
6 | * Dismiss the bar when the android back button is pressed (bug). Thanks `monster555`.
7 | ## [4.0.1]
8 | * Adds `keyboardBarElevation` to `KeyboardActionsConfig` to change the current mandatory 20 elevation. Thanks `Rooa94`.
9 |
10 | ## [4.0.0]
11 | * Update to Flutter 3.0 (Use only if you are using Flutter 3 >=). Thanks `ashim-kr-saha` and `Roaa94`.
12 |
13 | ## [3.4.6 - 3.4.7]
14 | * Bug #173 fixed. Thanks `Arenukvern`.
15 | * Bug #172 fixed. Thanks `BenjaminFarquhar`.
16 |
17 | ## [3.4.5]
18 | * `keepFocusOnTappingNode` was added. Thanks `alex-min`.
19 | * `toolbarAlignment` was added in `KeyboardActionsItem`. Thanks `f-person` (Arshak Aghakaryan).
20 |
21 | ## [3.4.4]
22 | * `defaultDoneWidget` was added in `KeyboardActionsConfig`. Thanks `peter-gy`.
23 |
24 | ## [3.4.2 - 3.4.3]
25 | * Fixed a bug that was displaying the bar at the bottom (hide behind the keyboard).
26 |
27 | ## [3.4.1]
28 | * `tapOutsideBehavior` was added in order to dismiss and allow the hitTest for background components. Thanks `crizant`.
29 |
30 | ## [3.4.0]
31 | * Null safety migration. Thanks TheManuz
32 |
33 | ## [3.3.1 - 3.3.1+1]
34 |
35 | * Fixed issue #115 when running Flutter 1.22.
36 | * Fixed issue #106
37 | * Fixed issue #121
38 |
39 | ## [3.3.0+1]
40 |
41 | * Some bugs fixed: #99, #100, #101.
42 | * `disableScroll` was added in `KeyboardActions`.
43 |
44 |
45 | ## [3.3.0]
46 |
47 | * `KeyboardAction` was renamed to `KeyboardActionsItem` to avoid mess with the main widget `KeyboardActions`.
48 | * Support Web compilation.
49 |
50 | ## [3.2.1+1]
51 |
52 | * Bug fixed when Custom Keyboard has an area above footer builder. Thanks @lzhuor
53 |
54 | ## [3.2.1]
55 |
56 | * Exposed `overscroll` property in `KeyboardActions` in case you want an extra scroll below your focused input. (default: 12).
57 |
58 | ## [3.2.0] BREAKING CHANGE
59 |
60 | * `displayArrows` property was added in `KeyboardAction`.
61 | * `closeWidget` and `displayCloseWidget` were removed. Now you can add multiple toolbar buttons using `toolbarButtons` property from `KeyboardAction` (check the sample updated).
62 | * Set `displayDoneButton` to false if you don't want the DONE button by default.
63 |
64 | ## [3.1.3]
65 |
66 | * Now you can change the size of the arrow buttons using the Theme. (Check the sample.dart file from the example folder to get more info)
67 | * `keyboardSeparatorColor` was added in `KeyboardActionsConfig` to change the color of the line separator between keyboard and content.
68 |
69 | ## [3.1.2]
70 |
71 | * fixed issue with `keyboardActionsPlatform`.
72 |
73 | ## [3.1.1]
74 |
75 | * added `tapOutsideToDismiss` property inside `KeyboardActions` in case you want to press outside the keyboard to dismiss it.
76 |
77 | ## [3.1.0] BREAKING CHANGE
78 |
79 | * API improved
80 | * `FormKeyboardActions` was renamed to `KeyboardActions`.
81 | * `KeyboardCustomInput` was added to help you to create custom keyboards in an easy way.
82 | * added `enabled` property inside `KeyboardActions` in case you don't want to use `KeyboardActions` widget (tablets for example).
83 | * added `displayActionBar` property inside `KeyboardAction` in case you want to display/hide the keyboard bar (E.g: if you use footerBuilder and add your own done button inside that)
84 | * added `isDialog` property inside `KeyboardActions`.
85 | * Material color is transparent to avoid issues with the parent container.
86 |
87 |
88 |
89 | ## [3.0.0] BREAKING CHANGE
90 |
91 | * Restore the old API with some bug fixing
92 |
93 | ## [2.1.2+2]
94 |
95 | * Keyboard dismissed when press back on Android.
96 |
97 | ## [2.1.2+1]
98 |
99 | * Fixed issue when using `CupertinoPageScaffold`.
100 |
101 | ## [2.1.2]
102 |
103 | * Now you can use the `IconTheme.of(context).color` and `Theme.of(context).disabledColor` to set the colors of the arrow icons (up/down).
104 |
105 | ## [2.1.0 - 2.1.1+1]
106 |
107 | * Custom footer widget below keyboard bar
108 | * Now you can add your custom keyboard!!
109 | * Thanks @jayjwarrick again for the contribution
110 |
111 | ## [2.0.1]
112 |
113 | * Disable next & previous buttons when there is none
114 |
115 | ## [2.0.0] ** Breaking change **
116 |
117 | * Now `KeyboardActions` works on Dialogs
118 | * Add KeyboardActionsConfig to make parameters easily swappable
119 | * Add `FormKeyboardActions.setKeyboardActions` and `FormKeyboardActions` to allow changing the config from anywhere in the child widget tree. (Check the sample)
120 | * Thanks @jayjwarrick for the contribution
121 |
122 | ## [1.0.4]
123 |
124 | * Added `enabled` attribute for KeyboardAction to skip the prev/next when the TextField is disabled
125 |
126 | ## [1.0.3]
127 |
128 | * Fixed android issue when return from background
129 |
130 | ## [1.0.0 - 1.0.2]
131 |
132 | * First release.
133 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Diego Velásquez López
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Keyboard Actions
2 |
3 | [](https://pub.dartlang.org/packages/keyboard_actions)
4 |
5 | Add features to the Android / iOS keyboard in a simple way.
6 |
7 | Because the keyboard that Android / iOS offers us specifically when we are in numeric mode, does not bring the button to hide the keyboard.
8 | This causes a lot of inconvenience for users, so this package allows adding functionality to the existing keyboard.
9 |
10 |
11 |
12 |
13 |
14 |
15 | ## Features
16 |
17 | - Done button for the keyboard (You can customize the button).
18 | - Move up/down between your Textfields (You can hide for set `nextFocus: false`).
19 | - Keyboard Bar customization.
20 | - Custom footer widget below keyboard bar
21 | - Create your own Keyboard in an easy way
22 | - You can use it for Android, iOS or both platforms.
23 | - Compatible with Dialog.
24 |
25 | Example of the custom footer:
26 |
27 |
28 |
29 | For more fun, use that widget as a custom keyboard with your custom input:
30 |
31 |
32 |
33 |
34 | Even more fun:
35 |
36 | [Watch the video](https://thumbs.gfycat.com/NimbleGraveDarwinsfox-mobile.mp4)
37 |
38 | ## Getting started
39 |
40 | You should ensure that you add the dependency in your flutter project.
41 | ```yaml
42 | dependencies:
43 | keyboard_actions: "^4.1.0"
44 | ```
45 |
46 | You should then run `flutter packages upgrade` or update your packages in IntelliJ.
47 |
48 | ## Example Project
49 |
50 | There is an example project in the `example` folder where you can get more information. Check it out. Otherwise, keep reading to get up and running.
51 |
52 | ## Usage
53 |
54 | ```dart
55 | import 'package:flutter/material.dart';
56 | import 'package:keyboard_actions/keyboard_actions.dart';
57 |
58 |
59 | class Content extends StatefulWidget {
60 | const Content({
61 | Key key,
62 | }) : super(key: key);
63 |
64 | @override
65 | _ContentState createState() => _ContentState();
66 | }
67 |
68 | class _ContentState extends State {
69 | final FocusNode _nodeText1 = FocusNode();
70 | final FocusNode _nodeText2 = FocusNode();
71 | final FocusNode _nodeText3 = FocusNode();
72 | final FocusNode _nodeText4 = FocusNode();
73 | final FocusNode _nodeText5 = FocusNode();
74 | final FocusNode _nodeText6 = FocusNode();
75 |
76 | /// Creates the [KeyboardActionsConfig] to hook up the fields
77 | /// and their focus nodes to our [FormKeyboardActions].
78 | KeyboardActionsConfig _buildConfig(BuildContext context) {
79 | return KeyboardActionsConfig(
80 | keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
81 | keyboardBarColor: Colors.grey[200],
82 | nextFocus: true,
83 | actions: [
84 | KeyboardActionsItem(
85 | focusNode: _nodeText1,
86 | ),
87 | KeyboardActionsItem(focusNode: _nodeText2, toolbarButtons: [
88 | (node) {
89 | return GestureDetector(
90 | onTap: () => node.unfocus(),
91 | child: Padding(
92 | padding: EdgeInsets.all(8.0),
93 | child: Icon(Icons.close),
94 | ),
95 | );
96 | }
97 | ]),
98 | KeyboardActionsItem(
99 | focusNode: _nodeText3,
100 | onTapAction: () {
101 | showDialog(
102 | context: context,
103 | builder: (context) {
104 | return AlertDialog(
105 | content: Text("Custom Action"),
106 | actions: [
107 | FlatButton(
108 | child: Text("OK"),
109 | onPressed: () => Navigator.of(context).pop(),
110 | )
111 | ],
112 | );
113 | });
114 | },
115 | ),
116 | KeyboardActionsItem(
117 | focusNode: _nodeText4,
118 | displayCloseWidget: false,
119 | ),
120 | KeyboardActionsItem(
121 | focusNode: _nodeText5,
122 | toolbarButtons: [
123 | //button 1
124 | (node) {
125 | return GestureDetector(
126 | onTap: () => node.unfocus(),
127 | child: Container(
128 | color: Colors.white,
129 | padding: EdgeInsets.all(8.0),
130 | child: Text(
131 | "CLOSE",
132 | style: TextStyle(color: Colors.black),
133 | ),
134 | ),
135 | );
136 | },
137 | //button 2
138 | (node) {
139 | return GestureDetector(
140 | onTap: () => node.unfocus(),
141 | child: Container(
142 | color: Colors.black,
143 | padding: EdgeInsets.all(8.0),
144 | child: Text(
145 | "DONE",
146 | style: TextStyle(color: Colors.white),
147 | ),
148 | ),
149 | );
150 | }
151 | ],
152 | ),
153 | KeyboardActionsItem(
154 | focusNode: _nodeText6,
155 | footerBuilder: (_) => PreferredSize(
156 | child: SizedBox(
157 | height: 40,
158 | child: Center(
159 | child: Text('Custom Footer'),
160 | )),
161 | preferredSize: Size.fromHeight(40)),
162 | ),
163 | ],
164 | );
165 | }
166 |
167 | @override
168 | Widget build(BuildContext context) {
169 | return KeyboardActions(
170 | config: _buildConfig(context),
171 | child: Center(
172 | child: Padding(
173 | padding: const EdgeInsets.all(15.0),
174 | child: Column(
175 | crossAxisAlignment: CrossAxisAlignment.stretch,
176 | children: [
177 | TextField(
178 | keyboardType: TextInputType.number,
179 | focusNode: _nodeText1,
180 | decoration: InputDecoration(
181 | hintText: "Input Number",
182 | ),
183 | ),
184 | TextField(
185 | keyboardType: TextInputType.text,
186 | focusNode: _nodeText2,
187 | decoration: InputDecoration(
188 | hintText: "Input Text with Custom Done Button",
189 | ),
190 | ),
191 | TextField(
192 | keyboardType: TextInputType.number,
193 | focusNode: _nodeText3,
194 | decoration: InputDecoration(
195 | hintText: "Input Number with Custom Action",
196 | ),
197 | ),
198 | TextField(
199 | keyboardType: TextInputType.text,
200 | focusNode: _nodeText4,
201 | decoration: InputDecoration(
202 | hintText: "Input Text without Done button",
203 | ),
204 | ),
205 | TextField(
206 | keyboardType: TextInputType.number,
207 | focusNode: _nodeText5,
208 | decoration: InputDecoration(
209 | hintText: "Input Number with Toolbar Buttons",
210 | ),
211 | ),
212 | TextField(
213 | keyboardType: TextInputType.number,
214 | focusNode: _nodeText6,
215 | decoration: InputDecoration(
216 | hintText: "Input Number with Custom Footer",
217 | ),
218 | ),
219 | ],
220 | ),
221 | ),
222 | ),
223 | );
224 | }
225 | }
226 |
227 | ```
228 |
229 | ## Using Custom Keyboard
230 |
231 | ```dart
232 | import 'package:flutter/material.dart';
233 | import 'package:keyboard_actions/keyboard_actions.dart';
234 |
235 | class Content extends StatelessWidget {
236 | final FocusNode _nodeText7 = FocusNode();
237 | final FocusNode _nodeText8 = FocusNode();
238 | //This is only for custom keyboards
239 | final custom1Notifier = ValueNotifier("0");
240 | final custom2Notifier = ValueNotifier(Colors.blue);
241 |
242 | /// Creates the [KeyboardActionsConfig] to hook up the fields
243 | /// and their focus nodes to our [FormKeyboardActions].
244 | KeyboardActionsConfig _buildConfig(BuildContext context) {
245 | return KeyboardActionsConfig(
246 | keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
247 | keyboardBarColor: Colors.grey[200],
248 | nextFocus: true,
249 | actions: [
250 | KeyboardActionsItem(
251 | focusNode: _nodeText7,
252 | footerBuilder: (_) => CounterKeyboard(
253 | notifier: custom1Notifier,
254 | ),
255 | ),
256 | KeyboardActionsItem(
257 | focusNode: _nodeText8,
258 | footerBuilder: (_) => ColorPickerKeyboard(
259 | notifier: custom2Notifier,
260 | ),
261 | ),
262 | ],
263 | );
264 | }
265 |
266 | @override
267 | Widget build(BuildContext context) {
268 | return KeyboardActions(
269 | config: _buildConfig(context),
270 | child: Center(
271 | child: Container(
272 | padding: const EdgeInsets.all(15.0),
273 | child: Column(
274 | crossAxisAlignment: CrossAxisAlignment.stretch,
275 | children: [
276 | KeyboardCustomInput(
277 | focusNode: _nodeText7,
278 | height: 65,
279 | notifier: custom1Notifier,
280 | builder: (context, val, hasFocus) {
281 | return Container(
282 | alignment: Alignment.center,
283 | color: hasFocus ? Colors.grey[300] : Colors.white,
284 | child: Text(
285 | val,
286 | style:
287 | TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
288 | ),
289 | );
290 | },
291 | ),
292 | KeyboardCustomInput(
293 | focusNode: _nodeText8,
294 | height: 65,
295 | notifier: custom2Notifier,
296 | builder: (context, val, hasFocus) {
297 | return Container(
298 | width: double.maxFinite,
299 | color: val ?? Colors.transparent,
300 | );
301 | },
302 | ),
303 | ],
304 | ),
305 | ),
306 | ),
307 | );
308 | }
309 | }
310 |
311 |
312 | /// A quick example "keyboard" widget for picking a color.
313 | class ColorPickerKeyboard extends StatelessWidget
314 | with KeyboardCustomPanelMixin
315 | implements PreferredSizeWidget {
316 | final ValueNotifier notifier;
317 | static const double _kKeyboardHeight = 200;
318 |
319 | ColorPickerKeyboard({Key key, this.notifier}) : super(key: key);
320 |
321 | @override
322 | Widget build(BuildContext context) {
323 | final double rows = 3;
324 | final double screenWidth = MediaQuery.of(context).size.width;
325 | final int colorsCount = Colors.primaries.length;
326 | final int colorsPerRow = (colorsCount / rows).ceil();
327 | final double itemWidth = screenWidth / colorsPerRow;
328 | final double itemHeight = _kKeyboardHeight / rows;
329 |
330 | return Container(
331 | height: _kKeyboardHeight,
332 | child: Wrap(
333 | children: [
334 | for (final color in Colors.primaries)
335 | GestureDetector(
336 | onTap: () {
337 | updateValue(color);
338 | },
339 | child: Container(
340 | color: color,
341 | width: itemWidth,
342 | height: itemHeight,
343 | ),
344 | )
345 | ],
346 | ),
347 | );
348 | }
349 |
350 | @override
351 | Size get preferredSize => Size.fromHeight(_kKeyboardHeight);
352 | }
353 |
354 | /// A quick example "keyboard" widget for counter value.
355 | class CounterKeyboard extends StatelessWidget
356 | with KeyboardCustomPanelMixin
357 | implements PreferredSizeWidget {
358 | final ValueNotifier notifier;
359 |
360 | CounterKeyboard({Key key, this.notifier}) : super(key: key);
361 |
362 | @override
363 | Size get preferredSize => Size.fromHeight(200);
364 |
365 | @override
366 | Widget build(BuildContext context) {
367 | return Container(
368 | height: preferredSize.height,
369 | child: Row(
370 | children: [
371 | Expanded(
372 | child: InkWell(
373 | onTap: () {
374 | int value = int.tryParse(notifier.value) ?? 0;
375 | value--;
376 | updateValue(value.toString());
377 | },
378 | child: FittedBox(
379 | child: Text(
380 | "-",
381 | style: TextStyle(
382 | fontWeight: FontWeight.bold,
383 | ),
384 | ),
385 | ),
386 | ),
387 | ),
388 | Expanded(
389 | child: InkWell(
390 | onTap: () {
391 | int value = int.tryParse(notifier.value) ?? 0;
392 | value++;
393 | updateValue(value.toString());
394 | },
395 | child: FittedBox(
396 | child: Text(
397 | "+",
398 | style: TextStyle(
399 | fontWeight: FontWeight.bold,
400 | ),
401 | ),
402 | ),
403 | ),
404 | ),
405 | ],
406 | ),
407 | );
408 | }
409 | }
410 |
411 |
412 | ```
413 |
414 |
415 |
416 | You can follow me on twitter [@diegoveloper](https://www.twitter.com/diegoveloper)
417 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Visual Studio Code related
20 | .vscode/
21 |
22 | # Flutter/Dart/Pub related
23 | **/doc/api/
24 | .dart_tool/
25 | .flutter-plugins
26 | .packages
27 | .pub-cache/
28 | .pub/
29 | build/
30 |
31 | # Android related
32 | **/android/**/gradle-wrapper.jar
33 | **/android/.gradle
34 | **/android/captures/
35 | **/android/gradlew
36 | **/android/gradlew.bat
37 | **/android/local.properties
38 | **/android/**/GeneratedPluginRegistrant.java
39 |
40 | # iOS/XCode related
41 | **/ios/**/*.mode1v3
42 | **/ios/**/*.mode2v3
43 | **/ios/**/*.moved-aside
44 | **/ios/**/*.pbxuser
45 | **/ios/**/*.perspectivev3
46 | **/ios/**/*sync/
47 | **/ios/**/.sconsign.dblite
48 | **/ios/**/.tags*
49 | **/ios/**/.vagrant/
50 | **/ios/**/DerivedData/
51 | **/ios/**/Icon?
52 | **/ios/**/Pods/
53 | **/ios/**/.symlinks/
54 | **/ios/**/profile
55 | **/ios/**/xcuserdata
56 | **/ios/.generated/
57 | **/ios/Flutter/App.framework
58 | **/ios/Flutter/Flutter.framework
59 | **/ios/Flutter/Generated.xcconfig
60 | **/ios/Flutter/app.flx
61 | **/ios/Flutter/app.zip
62 | **/ios/Flutter/flutter_assets/
63 | **/ios/ServiceDefinitions.json
64 | **/ios/Runner/GeneratedPluginRegistrant.*
65 | **/ios/Flutter/flutter_export_environment.sh
66 |
67 | # Exceptions to above rules.
68 | !**/ios/**/default.mode1v3
69 | !**/ios/**/default.mode2v3
70 | !**/ios/**/default.pbxuser
71 | !**/ios/**/default.perspectivev3
72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
73 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 58c8489fcdb4e4ef6c010117584c9b23d15221aa
8 | channel: dev
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | A new Flutter project.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook)
13 |
14 | For help getting started with Flutter, view our
15 | [online documentation](https://flutter.io/docs), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
26 |
27 | android {
28 | compileSdkVersion 31
29 |
30 | lintOptions {
31 | disable 'InvalidPackage'
32 | }
33 |
34 | defaultConfig {
35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
36 | applicationId "com.example.example"
37 | minSdkVersion 28
38 | targetSdkVersion 31
39 | versionCode flutterVersionCode.toInteger()
40 | versionName flutterVersionName
41 | }
42 |
43 | buildTypes {
44 | release {
45 | // TODO: Add your own signing config for the release build.
46 | // Signing with the debug keys for now, so `flutter run --release` works.
47 | signingConfig signingConfigs.debug
48 | }
49 | }
50 | }
51 |
52 | flutter {
53 | source '../..'
54 | }
55 |
56 | dependencies {
57 | testImplementation 'junit:junit:4.12'
58 | }
59 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
13 |
21 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.example;
2 |
3 | import io.flutter.embedding.android.FlutterActivity;
4 |
5 | public class MainActivity extends FlutterActivity {}
6 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:4.1.0'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 | }
17 | }
18 |
19 | rootProject.buildDir = '../build'
20 | subprojects {
21 | project.buildDir = "${rootProject.buildDir}/${project.name}"
22 | }
23 | subprojects {
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
13 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
14 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
15 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
16 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
17 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
18 | /* End PBXBuildFile section */
19 |
20 | /* Begin PBXCopyFilesBuildPhase section */
21 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
22 | isa = PBXCopyFilesBuildPhase;
23 | buildActionMask = 2147483647;
24 | dstPath = "";
25 | dstSubfolderSpec = 10;
26 | files = (
27 | );
28 | name = "Embed Frameworks";
29 | runOnlyForDeploymentPostprocessing = 0;
30 | };
31 | /* End PBXCopyFilesBuildPhase section */
32 |
33 | /* Begin PBXFileReference section */
34 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
35 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
38 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
39 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
40 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
41 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
42 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
43 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
44 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
45 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
46 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
47 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
48 | /* End PBXFileReference section */
49 |
50 | /* Begin PBXFrameworksBuildPhase section */
51 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
52 | isa = PBXFrameworksBuildPhase;
53 | buildActionMask = 2147483647;
54 | files = (
55 | );
56 | runOnlyForDeploymentPostprocessing = 0;
57 | };
58 | /* End PBXFrameworksBuildPhase section */
59 |
60 | /* Begin PBXGroup section */
61 | 9740EEB11CF90186004384FC /* Flutter */ = {
62 | isa = PBXGroup;
63 | children = (
64 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
65 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
66 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
67 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
68 | );
69 | name = Flutter;
70 | sourceTree = "";
71 | };
72 | 97C146E51CF9000F007C117D = {
73 | isa = PBXGroup;
74 | children = (
75 | 9740EEB11CF90186004384FC /* Flutter */,
76 | 97C146F01CF9000F007C117D /* Runner */,
77 | 97C146EF1CF9000F007C117D /* Products */,
78 | CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
79 | );
80 | sourceTree = "";
81 | };
82 | 97C146EF1CF9000F007C117D /* Products */ = {
83 | isa = PBXGroup;
84 | children = (
85 | 97C146EE1CF9000F007C117D /* Runner.app */,
86 | );
87 | name = Products;
88 | sourceTree = "";
89 | };
90 | 97C146F01CF9000F007C117D /* Runner */ = {
91 | isa = PBXGroup;
92 | children = (
93 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
94 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
95 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
96 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
97 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
98 | 97C147021CF9000F007C117D /* Info.plist */,
99 | 97C146F11CF9000F007C117D /* Supporting Files */,
100 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
101 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
102 | );
103 | path = Runner;
104 | sourceTree = "";
105 | };
106 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
107 | isa = PBXGroup;
108 | children = (
109 | 97C146F21CF9000F007C117D /* main.m */,
110 | );
111 | name = "Supporting Files";
112 | sourceTree = "";
113 | };
114 | /* End PBXGroup section */
115 |
116 | /* Begin PBXNativeTarget section */
117 | 97C146ED1CF9000F007C117D /* Runner */ = {
118 | isa = PBXNativeTarget;
119 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
120 | buildPhases = (
121 | 9740EEB61CF901F6004384FC /* Run Script */,
122 | 97C146EA1CF9000F007C117D /* Sources */,
123 | 97C146EB1CF9000F007C117D /* Frameworks */,
124 | 97C146EC1CF9000F007C117D /* Resources */,
125 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
126 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
127 | );
128 | buildRules = (
129 | );
130 | dependencies = (
131 | );
132 | name = Runner;
133 | productName = Runner;
134 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
135 | productType = "com.apple.product-type.application";
136 | };
137 | /* End PBXNativeTarget section */
138 |
139 | /* Begin PBXProject section */
140 | 97C146E61CF9000F007C117D /* Project object */ = {
141 | isa = PBXProject;
142 | attributes = {
143 | LastUpgradeCheck = 1300;
144 | ORGANIZATIONNAME = "The Chromium Authors";
145 | TargetAttributes = {
146 | 97C146ED1CF9000F007C117D = {
147 | CreatedOnToolsVersion = 7.3.1;
148 | };
149 | };
150 | };
151 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
152 | compatibilityVersion = "Xcode 3.2";
153 | developmentRegion = English;
154 | hasScannedForEncodings = 0;
155 | knownRegions = (
156 | en,
157 | Base,
158 | );
159 | mainGroup = 97C146E51CF9000F007C117D;
160 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
161 | projectDirPath = "";
162 | projectRoot = "";
163 | targets = (
164 | 97C146ED1CF9000F007C117D /* Runner */,
165 | );
166 | };
167 | /* End PBXProject section */
168 |
169 | /* Begin PBXResourcesBuildPhase section */
170 | 97C146EC1CF9000F007C117D /* Resources */ = {
171 | isa = PBXResourcesBuildPhase;
172 | buildActionMask = 2147483647;
173 | files = (
174 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
175 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
176 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
177 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
178 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
179 | );
180 | runOnlyForDeploymentPostprocessing = 0;
181 | };
182 | /* End PBXResourcesBuildPhase section */
183 |
184 | /* Begin PBXShellScriptBuildPhase section */
185 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
186 | isa = PBXShellScriptBuildPhase;
187 | alwaysOutOfDate = 1;
188 | buildActionMask = 2147483647;
189 | files = (
190 | );
191 | inputPaths = (
192 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
193 | );
194 | name = "Thin Binary";
195 | outputPaths = (
196 | );
197 | runOnlyForDeploymentPostprocessing = 0;
198 | shellPath = /bin/sh;
199 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
200 | };
201 | 9740EEB61CF901F6004384FC /* Run Script */ = {
202 | isa = PBXShellScriptBuildPhase;
203 | alwaysOutOfDate = 1;
204 | buildActionMask = 2147483647;
205 | files = (
206 | );
207 | inputPaths = (
208 | );
209 | name = "Run Script";
210 | outputPaths = (
211 | );
212 | runOnlyForDeploymentPostprocessing = 0;
213 | shellPath = /bin/sh;
214 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
215 | };
216 | /* End PBXShellScriptBuildPhase section */
217 |
218 | /* Begin PBXSourcesBuildPhase section */
219 | 97C146EA1CF9000F007C117D /* Sources */ = {
220 | isa = PBXSourcesBuildPhase;
221 | buildActionMask = 2147483647;
222 | files = (
223 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
224 | 97C146F31CF9000F007C117D /* main.m in Sources */,
225 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
226 | );
227 | runOnlyForDeploymentPostprocessing = 0;
228 | };
229 | /* End PBXSourcesBuildPhase section */
230 |
231 | /* Begin PBXVariantGroup section */
232 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
233 | isa = PBXVariantGroup;
234 | children = (
235 | 97C146FB1CF9000F007C117D /* Base */,
236 | );
237 | name = Main.storyboard;
238 | sourceTree = "";
239 | };
240 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
241 | isa = PBXVariantGroup;
242 | children = (
243 | 97C147001CF9000F007C117D /* Base */,
244 | );
245 | name = LaunchScreen.storyboard;
246 | sourceTree = "";
247 | };
248 | /* End PBXVariantGroup section */
249 |
250 | /* Begin XCBuildConfiguration section */
251 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
252 | isa = XCBuildConfiguration;
253 | buildSettings = {
254 | ALWAYS_SEARCH_USER_PATHS = NO;
255 | CLANG_ANALYZER_NONNULL = YES;
256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
257 | CLANG_CXX_LIBRARY = "libc++";
258 | CLANG_ENABLE_MODULES = YES;
259 | CLANG_ENABLE_OBJC_ARC = YES;
260 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
261 | CLANG_WARN_BOOL_CONVERSION = YES;
262 | CLANG_WARN_COMMA = YES;
263 | CLANG_WARN_CONSTANT_CONVERSION = YES;
264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
265 | CLANG_WARN_EMPTY_BODY = YES;
266 | CLANG_WARN_ENUM_CONVERSION = YES;
267 | CLANG_WARN_INFINITE_RECURSION = YES;
268 | CLANG_WARN_INT_CONVERSION = YES;
269 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
270 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
271 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
272 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
273 | CLANG_WARN_STRICT_PROTOTYPES = YES;
274 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
275 | CLANG_WARN_UNREACHABLE_CODE = YES;
276 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
277 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
278 | COPY_PHASE_STRIP = NO;
279 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
280 | ENABLE_NS_ASSERTIONS = NO;
281 | ENABLE_STRICT_OBJC_MSGSEND = YES;
282 | GCC_C_LANGUAGE_STANDARD = gnu99;
283 | GCC_NO_COMMON_BLOCKS = YES;
284 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
285 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
286 | GCC_WARN_UNDECLARED_SELECTOR = YES;
287 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
288 | GCC_WARN_UNUSED_FUNCTION = YES;
289 | GCC_WARN_UNUSED_VARIABLE = YES;
290 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
291 | MTL_ENABLE_DEBUG_INFO = NO;
292 | SDKROOT = iphoneos;
293 | TARGETED_DEVICE_FAMILY = "1,2";
294 | VALIDATE_PRODUCT = YES;
295 | };
296 | name = Profile;
297 | };
298 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
299 | isa = XCBuildConfiguration;
300 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
301 | buildSettings = {
302 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
303 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
304 | DEVELOPMENT_TEAM = S8QB4VV633;
305 | ENABLE_BITCODE = NO;
306 | FRAMEWORK_SEARCH_PATHS = (
307 | "$(inherited)",
308 | "$(PROJECT_DIR)/Flutter",
309 | );
310 | INFOPLIST_FILE = Runner/Info.plist;
311 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
312 | LIBRARY_SEARCH_PATHS = (
313 | "$(inherited)",
314 | "$(PROJECT_DIR)/Flutter",
315 | );
316 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
317 | PRODUCT_NAME = "$(TARGET_NAME)";
318 | VERSIONING_SYSTEM = "apple-generic";
319 | };
320 | name = Profile;
321 | };
322 | 97C147031CF9000F007C117D /* Debug */ = {
323 | isa = XCBuildConfiguration;
324 | buildSettings = {
325 | ALWAYS_SEARCH_USER_PATHS = NO;
326 | CLANG_ANALYZER_NONNULL = YES;
327 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
328 | CLANG_CXX_LIBRARY = "libc++";
329 | CLANG_ENABLE_MODULES = YES;
330 | CLANG_ENABLE_OBJC_ARC = YES;
331 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
332 | CLANG_WARN_BOOL_CONVERSION = YES;
333 | CLANG_WARN_COMMA = YES;
334 | CLANG_WARN_CONSTANT_CONVERSION = YES;
335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
336 | CLANG_WARN_EMPTY_BODY = YES;
337 | CLANG_WARN_ENUM_CONVERSION = YES;
338 | CLANG_WARN_INFINITE_RECURSION = YES;
339 | CLANG_WARN_INT_CONVERSION = YES;
340 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
344 | CLANG_WARN_STRICT_PROTOTYPES = YES;
345 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
346 | CLANG_WARN_UNREACHABLE_CODE = YES;
347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
349 | COPY_PHASE_STRIP = NO;
350 | DEBUG_INFORMATION_FORMAT = dwarf;
351 | ENABLE_STRICT_OBJC_MSGSEND = YES;
352 | ENABLE_TESTABILITY = YES;
353 | GCC_C_LANGUAGE_STANDARD = gnu99;
354 | GCC_DYNAMIC_NO_PIC = NO;
355 | GCC_NO_COMMON_BLOCKS = YES;
356 | GCC_OPTIMIZATION_LEVEL = 0;
357 | GCC_PREPROCESSOR_DEFINITIONS = (
358 | "DEBUG=1",
359 | "$(inherited)",
360 | );
361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
363 | GCC_WARN_UNDECLARED_SELECTOR = YES;
364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
365 | GCC_WARN_UNUSED_FUNCTION = YES;
366 | GCC_WARN_UNUSED_VARIABLE = YES;
367 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
368 | MTL_ENABLE_DEBUG_INFO = YES;
369 | ONLY_ACTIVE_ARCH = YES;
370 | SDKROOT = iphoneos;
371 | TARGETED_DEVICE_FAMILY = "1,2";
372 | };
373 | name = Debug;
374 | };
375 | 97C147041CF9000F007C117D /* Release */ = {
376 | isa = XCBuildConfiguration;
377 | buildSettings = {
378 | ALWAYS_SEARCH_USER_PATHS = NO;
379 | CLANG_ANALYZER_NONNULL = YES;
380 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
381 | CLANG_CXX_LIBRARY = "libc++";
382 | CLANG_ENABLE_MODULES = YES;
383 | CLANG_ENABLE_OBJC_ARC = YES;
384 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
385 | CLANG_WARN_BOOL_CONVERSION = YES;
386 | CLANG_WARN_COMMA = YES;
387 | CLANG_WARN_CONSTANT_CONVERSION = YES;
388 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
389 | CLANG_WARN_EMPTY_BODY = YES;
390 | CLANG_WARN_ENUM_CONVERSION = YES;
391 | CLANG_WARN_INFINITE_RECURSION = YES;
392 | CLANG_WARN_INT_CONVERSION = YES;
393 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
394 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
395 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
396 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
397 | CLANG_WARN_STRICT_PROTOTYPES = YES;
398 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
399 | CLANG_WARN_UNREACHABLE_CODE = YES;
400 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
401 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
402 | COPY_PHASE_STRIP = NO;
403 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
404 | ENABLE_NS_ASSERTIONS = NO;
405 | ENABLE_STRICT_OBJC_MSGSEND = YES;
406 | GCC_C_LANGUAGE_STANDARD = gnu99;
407 | GCC_NO_COMMON_BLOCKS = YES;
408 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
409 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
410 | GCC_WARN_UNDECLARED_SELECTOR = YES;
411 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
412 | GCC_WARN_UNUSED_FUNCTION = YES;
413 | GCC_WARN_UNUSED_VARIABLE = YES;
414 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
415 | MTL_ENABLE_DEBUG_INFO = NO;
416 | SDKROOT = iphoneos;
417 | TARGETED_DEVICE_FAMILY = "1,2";
418 | VALIDATE_PRODUCT = YES;
419 | };
420 | name = Release;
421 | };
422 | 97C147061CF9000F007C117D /* Debug */ = {
423 | isa = XCBuildConfiguration;
424 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
425 | buildSettings = {
426 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
427 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
428 | ENABLE_BITCODE = NO;
429 | FRAMEWORK_SEARCH_PATHS = (
430 | "$(inherited)",
431 | "$(PROJECT_DIR)/Flutter",
432 | );
433 | INFOPLIST_FILE = Runner/Info.plist;
434 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
435 | LIBRARY_SEARCH_PATHS = (
436 | "$(inherited)",
437 | "$(PROJECT_DIR)/Flutter",
438 | );
439 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
440 | PRODUCT_NAME = "$(TARGET_NAME)";
441 | VERSIONING_SYSTEM = "apple-generic";
442 | };
443 | name = Debug;
444 | };
445 | 97C147071CF9000F007C117D /* Release */ = {
446 | isa = XCBuildConfiguration;
447 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
448 | buildSettings = {
449 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
450 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
451 | ENABLE_BITCODE = NO;
452 | FRAMEWORK_SEARCH_PATHS = (
453 | "$(inherited)",
454 | "$(PROJECT_DIR)/Flutter",
455 | );
456 | INFOPLIST_FILE = Runner/Info.plist;
457 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
458 | LIBRARY_SEARCH_PATHS = (
459 | "$(inherited)",
460 | "$(PROJECT_DIR)/Flutter",
461 | );
462 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
463 | PRODUCT_NAME = "$(TARGET_NAME)";
464 | VERSIONING_SYSTEM = "apple-generic";
465 | };
466 | name = Release;
467 | };
468 | /* End XCBuildConfiguration section */
469 |
470 | /* Begin XCConfigurationList section */
471 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
472 | isa = XCConfigurationList;
473 | buildConfigurations = (
474 | 97C147031CF9000F007C117D /* Debug */,
475 | 97C147041CF9000F007C117D /* Release */,
476 | 249021D3217E4FDB00AE95B9 /* Profile */,
477 | );
478 | defaultConfigurationIsVisible = 0;
479 | defaultConfigurationName = Release;
480 | };
481 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
482 | isa = XCConfigurationList;
483 | buildConfigurations = (
484 | 97C147061CF9000F007C117D /* Debug */,
485 | 97C147071CF9000F007C117D /* Release */,
486 | 249021D4217E4FDB00AE95B9 /* Profile */,
487 | );
488 | defaultConfigurationIsVisible = 0;
489 | defaultConfigurationName = Release;
490 | };
491 | /* End XCConfigurationList section */
492 | };
493 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
494 | }
495 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application
7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8 | [GeneratedPluginRegistrant registerWithRegistry:self];
9 | // Override point for customization after application launch.
10 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoveloper/flutter_keyboard_actions/7633812719ba04fae5526e5b564a7703e2668933/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/example/ios/Runner/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char* argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/lib/content.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:keyboard_actions/keyboard_actions.dart';
3 | import 'custom_input.dart';
4 |
5 | //This could be StatelessWidget but it won't work on Dialogs for now until this issue is fixed: https://github.com/flutter/flutter/issues/45839
6 | class Content extends StatefulWidget {
7 | final bool isDialog;
8 |
9 | const Content({Key? key, this.isDialog = false}) : super(key: key);
10 |
11 | @override
12 | _ContentState createState() => _ContentState();
13 | }
14 |
15 | class _ContentState extends State {
16 | final FocusNode _nodeText1 = FocusNode();
17 |
18 | final FocusNode _nodeText2 = FocusNode();
19 |
20 | final FocusNode _nodeText3 = FocusNode();
21 |
22 | final FocusNode _nodeText4 = FocusNode();
23 |
24 | final FocusNode _nodeText5 = FocusNode();
25 |
26 | final FocusNode _nodeText6 = FocusNode();
27 |
28 | final FocusNode _nodeText7 = FocusNode();
29 |
30 | final FocusNode _nodeText8 = FocusNode();
31 |
32 | final FocusNode _nodeText9 = FocusNode();
33 |
34 | final FocusNode _nodeText10 = FocusNode();
35 |
36 | final custom1Notifier = ValueNotifier("0");
37 |
38 | final custom2Notifier = ValueNotifier(Colors.blue);
39 |
40 | final custom3Notifier = ValueNotifier("");
41 |
42 | /// Creates the [KeyboardActionsConfig] to hook up the fields
43 | /// and their focus nodes to our [FormKeyboardActions].
44 | KeyboardActionsConfig _buildConfig(BuildContext context) {
45 | return KeyboardActionsConfig(
46 | keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
47 | keyboardBarColor: Colors.grey[200],
48 | nextFocus: true,
49 | actions: [
50 | KeyboardActionsItem(
51 | focusNode: _nodeText1,
52 | ),
53 | KeyboardActionsItem(focusNode: _nodeText2, toolbarButtons: [
54 | (node) {
55 | return GestureDetector(
56 | onTap: () => node.unfocus(),
57 | child: Padding(
58 | padding: EdgeInsets.all(8.0),
59 | child: Icon(Icons.close),
60 | ),
61 | );
62 | }
63 | ]),
64 | KeyboardActionsItem(
65 | focusNode: _nodeText3,
66 | onTapAction: () async {
67 | await showDialog(
68 | context: context,
69 | builder: (context) {
70 | return AlertDialog(
71 | content: Text("Custom Action"),
72 | actions: [
73 | TextButton(
74 | child: Text("OK"),
75 | onPressed: () => Navigator.of(context).pop(),
76 | )
77 | ],
78 | );
79 | });
80 | },
81 | ),
82 | KeyboardActionsItem(
83 | focusNode: _nodeText4,
84 | displayDoneButton: false,
85 | ),
86 | KeyboardActionsItem(
87 | focusNode: _nodeText5,
88 | toolbarButtons: [
89 | //button 1
90 | (node) {
91 | return GestureDetector(
92 | onTap: () => node.unfocus(),
93 | child: Container(
94 | color: Colors.white,
95 | padding: EdgeInsets.all(8.0),
96 | child: Text(
97 | "CLOSE",
98 | style: TextStyle(color: Colors.black),
99 | ),
100 | ),
101 | );
102 | },
103 | //button 2
104 | (node) {
105 | return GestureDetector(
106 | onTap: () => node.unfocus(),
107 | child: Container(
108 | color: Colors.black,
109 | padding: EdgeInsets.all(8.0),
110 | child: Text(
111 | "DONE",
112 | style: TextStyle(color: Colors.white),
113 | ),
114 | ),
115 | );
116 | }
117 | ],
118 | ),
119 | KeyboardActionsItem(
120 | focusNode: _nodeText6,
121 | footerBuilder: (_) => PreferredSize(
122 | child: SizedBox(
123 | height: 40,
124 | child: Center(
125 | child: Text('Custom Footer'),
126 | )),
127 | preferredSize: Size.fromHeight(40)),
128 | ),
129 | KeyboardActionsItem(
130 | focusNode: _nodeText7,
131 | displayActionBar: false,
132 | footerBuilder: (_) => PreferredSize(
133 | child: SizedBox(
134 | height: 40,
135 | child: Center(
136 | child: Text('Custom Footer'),
137 | )),
138 | preferredSize: Size.fromHeight(40)),
139 | ),
140 | KeyboardActionsItem(
141 | focusNode: _nodeText8,
142 | footerBuilder: (_) => CounterKeyboard(
143 | notifier: custom1Notifier,
144 | ),
145 | ),
146 | KeyboardActionsItem(
147 | focusNode: _nodeText9,
148 | footerBuilder: (_) => ColorPickerKeyboard(
149 | notifier: custom2Notifier,
150 | ),
151 | ),
152 | KeyboardActionsItem(
153 | focusNode: _nodeText10,
154 | displayActionBar: false,
155 | footerBuilder: (_) => NumericKeyboard(
156 | focusNode: _nodeText10,
157 | notifier: custom3Notifier,
158 | ),
159 | ),
160 | ],
161 | );
162 | }
163 |
164 | @override
165 | Widget build(BuildContext context) {
166 | return KeyboardActions(
167 | isDialog: widget.isDialog,
168 | config: _buildConfig(context),
169 | child: Container(
170 | padding: const EdgeInsets.all(15.0),
171 | child: Center(
172 | child: Column(
173 | crossAxisAlignment: CrossAxisAlignment.stretch,
174 | mainAxisSize: MainAxisSize.min,
175 | children: [
176 | TextField(
177 | keyboardType: TextInputType.number,
178 | focusNode: _nodeText1,
179 | decoration: InputDecoration(
180 | hintText: "Input Number",
181 | ),
182 | ),
183 | TextField(
184 | keyboardType: TextInputType.text,
185 | focusNode: _nodeText2,
186 | decoration: InputDecoration(
187 | hintText: "Input Text with Custom Done Widget",
188 | ),
189 | ),
190 | TextField(
191 | keyboardType: TextInputType.number,
192 | focusNode: _nodeText3,
193 | decoration: InputDecoration(
194 | hintText: "Input Number with Custom Action",
195 | ),
196 | ),
197 | TextField(
198 | keyboardType: TextInputType.text,
199 | focusNode: _nodeText4,
200 | decoration: InputDecoration(
201 | hintText: "Input Text without Done Button",
202 | ),
203 | ),
204 | TextField(
205 | keyboardType: TextInputType.number,
206 | focusNode: _nodeText5,
207 | decoration: InputDecoration(
208 | hintText: "Input Number with Toolbar Buttons",
209 | ),
210 | ),
211 | TextField(
212 | keyboardType: TextInputType.number,
213 | focusNode: _nodeText6,
214 | decoration: InputDecoration(
215 | hintText: "Input Number with Custom Footer",
216 | ),
217 | ),
218 | TextField(
219 | keyboardType: TextInputType.number,
220 | focusNode: _nodeText7,
221 | decoration: InputDecoration(
222 | hintText: "Input Number with Custom Footer without Bar",
223 | ),
224 | ),
225 | KeyboardCustomInput(
226 | focusNode: _nodeText8,
227 | height: 65,
228 | notifier: custom1Notifier,
229 | builder: (context, val, hasFocus) {
230 | return Container(
231 | alignment: Alignment.center,
232 | color: hasFocus == true ? Colors.grey[300] : Colors.white,
233 | child: Text(
234 | val,
235 | style:
236 | TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
237 | ),
238 | );
239 | },
240 | ),
241 | KeyboardCustomInput(
242 | focusNode: _nodeText9,
243 | height: 65,
244 | notifier: custom2Notifier,
245 | builder: (context, val, hasFocus) {
246 | return Container(
247 | width: double.maxFinite,
248 | color: val,
249 | );
250 | },
251 | ),
252 | KeyboardCustomInput(
253 | focusNode: _nodeText10,
254 | height: 65,
255 | notifier: custom3Notifier,
256 | builder: (context, val, hasFocus) {
257 | return Container(
258 | alignment: Alignment.center,
259 | child: Text(
260 | val.isEmpty ? "Tap Here" : val,
261 | style:
262 | TextStyle(fontSize: 25, fontWeight: FontWeight.w500),
263 | ),
264 | );
265 | },
266 | ),
267 | ],
268 | ),
269 | ),
270 | ),
271 | );
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/example/lib/custom_input.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:keyboard_actions/keyboard_actions.dart';
3 | import 'package:intl/intl.dart';
4 |
5 | /// A quick example "keyboard" widget for picking a color.
6 | class ColorPickerKeyboard extends StatelessWidget
7 | with KeyboardCustomPanelMixin
8 | implements PreferredSizeWidget {
9 | final ValueNotifier notifier;
10 | static const double _kKeyboardHeight = 200;
11 |
12 | ColorPickerKeyboard({Key? key, required this.notifier}) : super(key: key);
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | final double rows = 3;
17 | final double screenWidth = MediaQuery.of(context).size.width;
18 | final int colorsCount = Colors.primaries.length;
19 | final int colorsPerRow = (colorsCount / rows).ceil();
20 | final double itemWidth = screenWidth / colorsPerRow;
21 | final double itemHeight = _kKeyboardHeight / rows;
22 |
23 | return Container(
24 | height: _kKeyboardHeight,
25 | child: Wrap(
26 | children: [
27 | for (final color in Colors.primaries)
28 | GestureDetector(
29 | onTap: () {
30 | updateValue(color);
31 | },
32 | child: Container(
33 | color: color,
34 | width: itemWidth,
35 | height: itemHeight,
36 | ),
37 | )
38 | ],
39 | ),
40 | );
41 | }
42 |
43 | @override
44 | Size get preferredSize => Size.fromHeight(_kKeyboardHeight);
45 | }
46 |
47 | /// A quick example "keyboard" widget for Counter.
48 | class CounterKeyboard extends StatelessWidget
49 | with KeyboardCustomPanelMixin
50 | implements PreferredSizeWidget {
51 | final ValueNotifier notifier;
52 |
53 | CounterKeyboard({Key? key, required this.notifier}) : super(key: key);
54 |
55 | @override
56 | Size get preferredSize => Size.fromHeight(200);
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | return Container(
61 | height: preferredSize.height,
62 | child: Row(
63 | children: [
64 | Expanded(
65 | child: InkWell(
66 | onTap: () {
67 | int value = int.tryParse(notifier.value) ?? 0;
68 | value--;
69 | updateValue(value.toString());
70 | },
71 | child: FittedBox(
72 | child: Text(
73 | "-",
74 | style: TextStyle(
75 | fontWeight: FontWeight.bold,
76 | ),
77 | ),
78 | ),
79 | ),
80 | ),
81 | Expanded(
82 | child: InkWell(
83 | onTap: () {
84 | int value = int.tryParse(notifier.value) ?? 0;
85 | value++;
86 | updateValue(value.toString());
87 | },
88 | child: FittedBox(
89 | child: Text(
90 | "+",
91 | style: TextStyle(
92 | fontWeight: FontWeight.bold,
93 | ),
94 | ),
95 | ),
96 | ),
97 | ),
98 | ],
99 | ),
100 | );
101 | }
102 | }
103 |
104 | /// A quick example "keyboard" widget for Numeric.
105 | class NumericKeyboard extends StatelessWidget
106 | with KeyboardCustomPanelMixin
107 | implements PreferredSizeWidget {
108 | final ValueNotifier notifier;
109 | final FocusNode focusNode;
110 |
111 | NumericKeyboard({
112 | Key? key,
113 | required this.notifier,
114 | required this.focusNode,
115 | }) : super(key: key);
116 |
117 | @override
118 | Size get preferredSize => Size.fromHeight(280);
119 |
120 | final format = NumberFormat("0000");
121 |
122 | String _formatValue(String value) {
123 | final updatedValue = format.format(double.parse(value));
124 | final finalValue = updatedValue.substring(0, updatedValue.length - 2) +
125 | "." +
126 | updatedValue.substring(updatedValue.length - 2, updatedValue.length);
127 | return finalValue;
128 | }
129 |
130 | void _onTapNumber(String value) {
131 | if (value == "Done") {
132 | focusNode.unfocus();
133 | return;
134 | }
135 | final currentValue = notifier.value.replaceAll(".", "");
136 | final temp = currentValue + value;
137 | updateValue(_formatValue(temp));
138 | }
139 |
140 | void _onTapBackspace() {
141 | final currentValue = notifier.value.replaceAll(".", "");
142 | final temp = currentValue.substring(0, currentValue.length - 1);
143 | updateValue(_formatValue(temp));
144 | }
145 |
146 | @override
147 | Widget build(BuildContext context) {
148 | return Container(
149 | height: preferredSize.height,
150 | color: Color(0xFF313131),
151 | child: Padding(
152 | padding: const EdgeInsets.all(8.0),
153 | child: GridView(
154 | shrinkWrap: true,
155 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
156 | crossAxisCount: 3,
157 | childAspectRatio: 2.2,
158 | crossAxisSpacing: 10,
159 | mainAxisSpacing: 10,
160 | ),
161 | children: [
162 | _buildButton(text: "7"),
163 | _buildButton(text: "8"),
164 | _buildButton(text: "9"),
165 | _buildButton(text: "4"),
166 | _buildButton(text: "5"),
167 | _buildButton(text: "6"),
168 | _buildButton(text: "1"),
169 | _buildButton(text: "2"),
170 | _buildButton(text: "3"),
171 | _buildButton(icon: Icons.backspace, color: Colors.black),
172 | _buildButton(text: "0"),
173 | _buildButton(text: "Done", color: Colors.black),
174 | ],
175 | ),
176 | ),
177 | );
178 | }
179 |
180 | Widget _buildButton({
181 | String? text,
182 | IconData? icon,
183 | Color? color,
184 | }) =>
185 | NumericButton(
186 | text: text,
187 | icon: icon,
188 | color: color,
189 | onTap: () => icon != null ? _onTapBackspace() : _onTapNumber(text!),
190 | );
191 | }
192 |
193 | class NumericButton extends StatelessWidget {
194 | final String? text;
195 | final VoidCallback onTap;
196 | final IconData? icon;
197 | final Color? color;
198 |
199 | const NumericButton({
200 | Key? key,
201 | this.text,
202 | required this.onTap,
203 | this.icon,
204 | this.color,
205 | }) : assert((icon != null) != (text != null)),
206 | super(key: key);
207 |
208 | @override
209 | Widget build(BuildContext context) {
210 | return Material(
211 | borderRadius: BorderRadius.circular(5.0),
212 | color: color ?? Color(0xFF4A4A4A),
213 | elevation: 5,
214 | child: InkWell(
215 | onTap: onTap,
216 | child: FittedBox(
217 | child: Padding(
218 | padding: const EdgeInsets.all(3.0),
219 | child: icon != null
220 | ? Icon(
221 | icon,
222 | color: Colors.white,
223 | )
224 | : Text(
225 | text!,
226 | style: TextStyle(
227 | color: Colors.white,
228 | fontWeight: FontWeight.w300,
229 | ),
230 | ),
231 | ),
232 | ),
233 | ),
234 | );
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/content.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | import 'sample.dart';
5 | import 'sample2.dart';
6 | import 'sample3.dart';
7 | import 'sample4.dart';
8 | import 'sample5.dart';
9 |
10 | // Application entry-point
11 | void main() => runApp(MyApp());
12 |
13 | class MyApp extends StatelessWidget {
14 | const MyApp({Key? key}) : super(key: key);
15 |
16 | _openWidget(BuildContext context, Widget widget) =>
17 | Navigator.of(context).push(
18 | MaterialPageRoute(builder: (_) => widget),
19 | );
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return MaterialApp(
24 | theme: ThemeData(
25 | primarySwatch: Colors.blue,
26 | ),
27 | home: Scaffold(
28 | backgroundColor: Colors.amber,
29 | body: Builder(
30 | builder: (myContext) => Center(
31 | child: Padding(
32 | padding: const EdgeInsets.all(18.0),
33 | child: Column(
34 | mainAxisAlignment: MainAxisAlignment.center,
35 | crossAxisAlignment: CrossAxisAlignment.stretch,
36 | children: [
37 | ElevatedButton(
38 | child: Text("Full Screen form"),
39 | onPressed: () => _openWidget(
40 | myContext,
41 | ScaffoldTest(),
42 | ),
43 | ),
44 | const SizedBox(
45 | height: 25,
46 | ),
47 | ElevatedButton(
48 | child: Text("Dialog form"),
49 | onPressed: () => _openWidget(
50 | myContext,
51 | DialogTest(),
52 | ),
53 | ),
54 | const SizedBox(
55 | height: 25,
56 | ),
57 | ElevatedButton(
58 | child: Text("Custom Sample 1"),
59 | onPressed: () => _openWidget(
60 | myContext,
61 | Sample(),
62 | ),
63 | ),
64 | const SizedBox(
65 | height: 25,
66 | ),
67 | ElevatedButton(
68 | child: Text("Custom Sample 2"),
69 | onPressed: () => _openWidget(
70 | myContext,
71 | Sample2(),
72 | ),
73 | ),
74 | const SizedBox(
75 | height: 25,
76 | ),
77 | ElevatedButton(
78 | child: Text("Custom Sample 3"),
79 | onPressed: () => _openWidget(
80 | myContext,
81 | Sample3(),
82 | ),
83 | ),
84 | const SizedBox(
85 | height: 25,
86 | ),
87 | ElevatedButton(
88 | child: Text("Custom Sample 4"),
89 | onPressed: () => _openWidget(
90 | myContext,
91 | Sample4(),
92 | ),
93 | ),
94 | const SizedBox(
95 | height: 25,
96 | ),
97 | ElevatedButton(
98 | child: Text("Custom Sample 5"),
99 | onPressed: () => _openWidget(
100 | myContext,
101 | Sample5(),
102 | ),
103 | ),
104 | ],
105 | ),
106 | ),
107 | ),
108 | ),
109 | ),
110 | );
111 | }
112 | }
113 |
114 | /// Displays our [TextField]s in a [Scaffold] with a [FormKeyboardActions].
115 | class ScaffoldTest extends StatelessWidget {
116 | @override
117 | Widget build(BuildContext context) {
118 | return Scaffold(
119 | appBar: AppBar(
120 | title: Text("Keyboard Actions Sample"),
121 | ),
122 | body: Content(),
123 | );
124 | }
125 | }
126 |
127 | /// Displays our [FormKeyboardActions] nested in a [AlertDialog].
128 | class DialogTest extends StatelessWidget {
129 | @override
130 | Widget build(BuildContext context) {
131 | return Scaffold(
132 | appBar: AppBar(
133 | title: Text("Keyboard Actions Sample"),
134 | ),
135 | body: Center(
136 | child: TextButton(
137 | child: Text('Launch dialog'),
138 | onPressed: () => _launchInDialog(context),
139 | ),
140 | ),
141 | );
142 | }
143 |
144 | void _launchInDialog(BuildContext context) async {
145 | final height = MediaQuery.of(context).size.height / 3;
146 | await showDialog(
147 | context: context,
148 | builder: (context) {
149 | return AlertDialog(
150 | title: Text('Dialog test'),
151 | content: SizedBox(
152 | height: height,
153 | child: Content(
154 | isDialog: true,
155 | ),
156 | ),
157 | actions: [
158 | TextButton(
159 | child: Text('Ok'),
160 | onPressed: () {
161 | Navigator.of(context).pop();
162 | },
163 | ),
164 | ],
165 | );
166 | },
167 | );
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/example/lib/sample.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:keyboard_actions/keyboard_actions.dart';
3 |
4 | class Sample extends StatelessWidget {
5 | final _focusNodeName = FocusNode();
6 | final _focusNodeQuantity = FocusNode();
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | final size = MediaQuery.of(context).size;
11 | return Scaffold(
12 | floatingActionButton: FloatingActionButton(
13 | child: Icon(Icons.place),
14 | onPressed: () {
15 | _focusNodeName.requestFocus();
16 | },
17 | ),
18 | appBar: AppBar(
19 | title: Text("KeyboardActions"),
20 | ),
21 | body: Padding(
22 | padding: const EdgeInsets.only(top: 15.0, left: 15.0, right: 15.0),
23 | child: Center(
24 | child: Theme(
25 | data: Theme.of(context).copyWith(
26 | disabledColor: Colors.blue,
27 | iconTheme: IconTheme.of(context).copyWith(
28 | color: Colors.red,
29 | size: 35,
30 | ),
31 | ),
32 | child: KeyboardActions(
33 | tapOutsideBehavior: TapOutsideBehavior.opaqueDismiss,
34 | config: KeyboardActionsConfig(
35 | keyboardSeparatorColor: Colors.purple,
36 | actions: [
37 | KeyboardActionsItem(
38 | focusNode: _focusNodeName,
39 | ),
40 | KeyboardActionsItem(
41 | focusNode: _focusNodeQuantity,
42 | ),
43 | ],
44 | ),
45 | child: ListView(
46 | children: [
47 | SizedBox(
48 | height: size.height / 4,
49 | child: FlutterLogo(),
50 | ),
51 | TextField(
52 | focusNode: _focusNodeName,
53 | decoration: InputDecoration(
54 | labelText: "Product Name",
55 | ),
56 | ),
57 | TextField(
58 | focusNode: _focusNodeQuantity,
59 | keyboardType: TextInputType.phone,
60 | decoration: InputDecoration(
61 | labelText: "Quantity",
62 | ),
63 | ),
64 | ],
65 | ),
66 | ),
67 | ),
68 | ),
69 | ),
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/example/lib/sample2.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:keyboard_actions/keyboard_actions.dart';
3 |
4 | class Sample2 extends StatelessWidget {
5 | final _focusSample = FocusNode();
6 | final _textController = TextEditingController();
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Scaffold(
11 | appBar: AppBar(
12 | title: Text("Sample 2"),
13 | ),
14 | body: Padding(
15 | padding: const EdgeInsets.only(top: 15.0, left: 15.0, right: 15.0),
16 | child: Center(
17 | child: KeyboardActions(
18 | tapOutsideBehavior: TapOutsideBehavior.translucentDismiss,
19 | config: KeyboardActionsConfig(
20 | keyboardSeparatorColor: Colors.purple,
21 | actions: [
22 | KeyboardActionsItem(
23 | focusNode: _focusSample,
24 | displayArrows: false,
25 | displayActionBar: false,
26 | footerBuilder: (context) {
27 | return MyCustomBarWidget(
28 | node: _focusSample,
29 | controller: _textController,
30 | );
31 | },
32 | ),
33 | ],
34 | ),
35 | child: ListView(
36 | children: [
37 | TextField(
38 | controller: _textController,
39 | focusNode: _focusSample,
40 | keyboardType: TextInputType.phone,
41 | decoration: InputDecoration(
42 | labelText: "Sample Input",
43 | ),
44 | ),
45 | ],
46 | ),
47 | ),
48 | ),
49 | ),
50 | );
51 | }
52 | }
53 |
54 | class MyCustomBarWidget extends StatelessWidget implements PreferredSizeWidget {
55 | final FocusNode node;
56 | final TextEditingController controller;
57 |
58 | const MyCustomBarWidget({
59 | Key? key,
60 | required this.node,
61 | required this.controller,
62 | }) : super(key: key);
63 |
64 | @override
65 | Widget build(BuildContext context) {
66 | return Row(
67 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
68 | children: [
69 | IconButton(
70 | icon: Icon(Icons.access_alarm),
71 | onPressed: () => print('hello world 1')),
72 | IconButton(
73 | icon: Icon(Icons.send), onPressed: () => print(controller.text)),
74 | Spacer(),
75 | IconButton(icon: Icon(Icons.close), onPressed: () => node.unfocus()),
76 | ],
77 | );
78 | }
79 |
80 | @override
81 | Size get preferredSize => Size.fromHeight(60);
82 | }
83 |
--------------------------------------------------------------------------------
/example/lib/sample3.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:keyboard_actions/keyboard_actions.dart';
3 |
4 | /// Sample [Widget] demonstrating the usage of [KeyboardActionsConfig.defaultDoneWidget].
5 | class Sample3 extends StatelessWidget {
6 | final _focusNodes =
7 | Iterable.generate(7).map((_) => FocusNode()).toList();
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Scaffold(
12 | appBar: AppBar(
13 | title: Text("Sample 3"),
14 | ),
15 | body: Padding(
16 | padding: const EdgeInsets.only(top: 15.0, left: 15.0, right: 15.0),
17 | child: Center(
18 | child: KeyboardActions(
19 | tapOutsideBehavior: TapOutsideBehavior.translucentDismiss,
20 | config: KeyboardActionsConfig(
21 | // Define ``defaultDoneWidget`` only once in the config
22 | defaultDoneWidget: _buildMyDoneWidget(),
23 | actions: _focusNodes
24 | .map((focusNode) => KeyboardActionsItem(focusNode: focusNode))
25 | .toList(),
26 | ),
27 | child: ListView.separated(
28 | itemBuilder: (ctx, idx) => TextField(
29 | focusNode: _focusNodes[idx],
30 | keyboardType: TextInputType.text,
31 | decoration: InputDecoration(
32 | labelText: "Field ${idx + 1}",
33 | ),
34 | ),
35 | separatorBuilder: (ctx, idx) => const SizedBox(height: 10.0),
36 | itemCount: _focusNodes.length,
37 | ),
38 | ),
39 | ),
40 | ),
41 | );
42 | }
43 |
44 | /// Returns the custom [Widget] to be rendered as the *"Done"* button.
45 | Widget _buildMyDoneWidget() {
46 | return Row(
47 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
48 | children: [
49 | Text('My Done Widget'),
50 | const SizedBox(width: 10.0),
51 | Icon(Icons.arrow_drop_down, size: 20.0),
52 | ],
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/example/lib/sample4.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:keyboard_actions/keyboard_actions.dart';
3 |
4 | /// Sample [Widget] demonstrating the usage of [KeyboardActionsItem.toolbarAlignment].
5 | class Sample4 extends StatelessWidget {
6 | final _focusSample = FocusNode();
7 | final _textController = TextEditingController();
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Scaffold(
12 | appBar: AppBar(
13 | title: Text("Sample 4"),
14 | ),
15 | body: Padding(
16 | padding: const EdgeInsets.only(top: 15.0, left: 15.0, right: 15.0),
17 | child: Center(
18 | child: KeyboardActions(
19 | tapOutsideBehavior: TapOutsideBehavior.translucentDismiss,
20 | config: KeyboardActionsConfig(
21 | actions: [
22 | KeyboardActionsItem(
23 | toolbarAlignment: MainAxisAlignment.spaceAround,
24 | focusNode: _focusSample,
25 | displayArrows: false,
26 | toolbarButtons: [
27 | (_) {
28 | return IconButton(
29 | icon: Icon(Icons.format_bold),
30 | onPressed: () {},
31 | );
32 | },
33 | (_) {
34 | return IconButton(
35 | icon: Icon(Icons.format_italic),
36 | onPressed: () {},
37 | );
38 | },
39 | (_) {
40 | return IconButton(
41 | icon: Icon(Icons.format_underline),
42 | onPressed: () {},
43 | );
44 | },
45 | (_) {
46 | return IconButton(
47 | icon: Icon(Icons.format_strikethrough),
48 | onPressed: () {},
49 | );
50 | },
51 | ],
52 | ),
53 | ],
54 | ),
55 | child: ListView(
56 | children: [
57 | TextField(
58 | controller: _textController,
59 | focusNode: _focusSample,
60 | decoration: InputDecoration(
61 | labelText: "Sample Input",
62 | ),
63 | ),
64 | ],
65 | ),
66 | ),
67 | ),
68 | ),
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/example/lib/sample5.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:keyboard_actions/keyboard_actions.dart';
3 |
4 | /// Sample [Widget] demonstrating the usage of [KeyboardActionsConfig.defaultDoneWidget].
5 | class Sample5 extends StatelessWidget {
6 | final _focusNodes =
7 | Iterable.generate(7).map((_) => FocusNode()).toList();
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Scaffold(
12 | appBar: AppBar(
13 | title: Text("Sample 5"),
14 | ),
15 | body: Padding(
16 | padding: const EdgeInsets.only(top: 15.0, left: 15.0, right: 15.0),
17 | child: Column(mainAxisSize: MainAxisSize.min, children: [
18 | Expanded(
19 | flex: 2,
20 | child: Center(
21 | child: KeyboardActions(
22 | tapOutsideBehavior: TapOutsideBehavior.translucentDismiss,
23 | config: KeyboardActionsConfig(
24 | // Define ``defaultDoneWidget`` only once in the config
25 | defaultDoneWidget: _buildMyDoneWidget(),
26 | actions: _focusNodes
27 | .map((focusNode) =>
28 | KeyboardActionsItem(focusNode: focusNode))
29 | .toList(),
30 | ),
31 | child: ListView.separated(
32 | itemBuilder: (ctx, idx) => TextField(
33 | focusNode: _focusNodes[idx],
34 | keyboardType: TextInputType.text,
35 | decoration: InputDecoration(
36 | fillColor: Colors.red,
37 | filled: true,
38 | labelText: "Field ${idx + 1}",
39 | ),
40 | ),
41 | separatorBuilder: (ctx, idx) =>
42 | const SizedBox(height: 10.0),
43 | itemCount: _focusNodes.length,
44 | ),
45 | ),
46 | )),
47 | Expanded(
48 | flex: 1,
49 | child: Container(
50 | color: Colors.green,
51 | child: Center(
52 | child: TextButton(
53 | onPressed: () {},
54 | child: Text(
55 | 'I take up space below the KeyboardActions',
56 | style: TextStyle(color: Colors.white),
57 | ),
58 | ))))
59 | ]),
60 | ),
61 | );
62 | }
63 |
64 | /// Returns the custom [Widget] to be rendered as the *"Done"* button.
65 | Widget _buildMyDoneWidget() {
66 | return Row(
67 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
68 | children: [
69 | Text('My Done Widget'),
70 | const SizedBox(width: 10.0),
71 | Icon(Icons.arrow_drop_down, size: 20.0),
72 | ],
73 | );
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: Sample project using keyboard actions.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # Read more about versioning at semver.org.
14 | version: 1.0.0+1
15 |
16 | environment:
17 | sdk: ">=2.12.0 <3.0.0"
18 |
19 | dependencies:
20 | flutter:
21 | sdk: flutter
22 |
23 | intl: any
24 | keyboard_actions:
25 | path: ../
26 |
27 |
28 | dev_dependencies:
29 | flutter_test:
30 | sdk: flutter
31 |
32 |
33 | # For information on the generic Dart part of this file, see the
34 | # following page: https://www.dartlang.org/tools/pub/pubspec
35 |
36 | # The following section is specific to Flutter.
37 | flutter:
38 |
39 | # The following line ensures that the Material Icons font is
40 | # included with your application, so that you can use the icons in
41 | # the material Icons class.
42 | uses-material-design: true
43 |
44 | # To add assets to your application, add an assets section, like this:
45 | # assets:
46 | # - images/a_dot_burr.jpeg
47 | # - images/a_dot_ham.jpeg
48 |
49 | # An image asset can refer to one or more resolution-specific "variants", see
50 | # https://flutter.io/assets-and-images/#resolution-aware.
51 |
52 | # For details regarding adding assets from package dependencies, see
53 | # https://flutter.io/assets-and-images/#from-packages
54 |
55 | # To add custom fonts to your application, add a fonts section here,
56 | # in this "flutter" section. Each entry in this list should have a
57 | # "family" key with the font family name, and a "fonts" key with a
58 | # list giving the asset and other descriptors for the font. For
59 | # example:
60 | # fonts:
61 | # - family: Schyler
62 | # fonts:
63 | # - asset: fonts/Schyler-Regular.ttf
64 | # - asset: fonts/Schyler-Italic.ttf
65 | # style: italic
66 | # - family: Trajan Pro
67 | # fonts:
68 | # - asset: fonts/TrajanPro.ttf
69 | # - asset: fonts/TrajanPro_Bold.ttf
70 | # weight: 700
71 | #
72 | # For details regarding fonts from package dependencies,
73 | # see https://flutter.io/custom-fonts/#from-packages
74 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:example/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/ios/Flutter/flutter_export_environment.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # This is a generated file; do not edit or check into version control.
3 | export "FLUTTER_ROOT=/Users/diego/fvm/versions/3.7.3"
4 | export "FLUTTER_APPLICATION_PATH=/Users/diego/Development/workspaces/flutter/flutter_keyboard_actions"
5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true"
6 | export "FLUTTER_TARGET=lib/main.dart"
7 | export "FLUTTER_BUILD_DIR=build"
8 | export "FLUTTER_BUILD_NAME=4.1.1"
9 | export "FLUTTER_BUILD_NUMBER=4.1.1"
10 | export "DART_OBFUSCATION=false"
11 | export "TRACK_WIDGET_CREATION=true"
12 | export "TREE_SHAKE_ICONS=false"
13 | export "PACKAGE_CONFIG=.dart_tool/package_config.json"
14 |
--------------------------------------------------------------------------------
/lib/external/keyboard_avoider/bottom_area_avoider.dart:
--------------------------------------------------------------------------------
1 | import 'dart:collection';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/rendering.dart';
5 |
6 | /// Helps [child] stay visible by resizing it to avoid the given [areaToAvoid].
7 | ///
8 | /// Wraps the [child] in a [AnimatedContainer] that adjusts its bottom [padding] to accommodate the given area.
9 | ///
10 | /// If [autoScroll] is true and the [child] contains a focused widget such as a [TextField],
11 | /// automatically scrolls so that it is just visible above the keyboard, plus any additional [overscroll].
12 | class BottomAreaAvoider extends StatefulWidget {
13 | static const Duration defaultDuration = Duration(milliseconds: 100);
14 | static const Curve defaultCurve = Curves.easeIn;
15 | static const double defaultOverscroll = 12.0;
16 | static const bool defaultAutoScroll = false;
17 |
18 | /// The child to embed.
19 | ///
20 | /// If the [child] is not a [ScrollView], it is automatically embedded in a [SingleChildScrollView].
21 | /// If the [child] is a [ScrollView], it must have a [ScrollController].
22 | final Widget? child;
23 |
24 | /// Amount of bottom area to avoid. For example, the height of the currently-showing system keyboard, or
25 | /// any custom bottom overlays.
26 | final double areaToAvoid;
27 |
28 | /// Whether to auto-scroll to the focused widget after the keyboard appears. Defaults to false.
29 | /// Could be expensive because it searches all the child objects in this widget's render tree.
30 | final bool autoScroll;
31 |
32 | /// Extra amount to scroll past the focused widget. Defaults to [defaultOverscroll].
33 | /// Useful in case the focused widget is inside a parent widget that you also want to be visible.
34 | final double overscroll;
35 |
36 | /// Duration of the resize animation. Defaults to [defaultDuration]. To disable, set to [Duration.zero].
37 | final Duration duration;
38 |
39 | /// Animation curve. Defaults to [defaultCurve]
40 | final Curve curve;
41 |
42 | /// The [ScrollPhysics] of the [SingleChildScrollView] which contains child
43 | final ScrollPhysics? physics;
44 |
45 | BottomAreaAvoider(
46 | {Key? key,
47 | required this.child,
48 | required this.areaToAvoid,
49 | this.autoScroll = false,
50 | this.duration = defaultDuration,
51 | this.curve = defaultCurve,
52 | this.overscroll = defaultOverscroll,
53 | this.physics})
54 | : //assert(child is ScrollView ? child.controller != null : true),
55 | assert(areaToAvoid >= 0, 'Cannot avoid a negative area'),
56 | super(key: key);
57 |
58 | BottomAreaAvoiderState createState() => BottomAreaAvoiderState();
59 | }
60 |
61 | class BottomAreaAvoiderState extends State {
62 | final _animationKey = new GlobalKey();
63 | Function(AnimationStatus)? _animationListener;
64 | ScrollController? _scrollController;
65 | late double _previousAreaToAvoid;
66 |
67 | @override
68 | void didUpdateWidget(BottomAreaAvoider oldWidget) {
69 | _previousAreaToAvoid = oldWidget.areaToAvoid;
70 | super.didUpdateWidget(oldWidget);
71 | }
72 |
73 | @override
74 | void dispose() {
75 | _animationKey.currentState?.animation
76 | .removeStatusListener(_animationListener!);
77 | super.dispose();
78 | }
79 |
80 | @override
81 | Widget build(BuildContext context) {
82 | // Add a status listener to the animation after the initial build.
83 | // Wait a frame so that _animationKey.currentState is not null.
84 | if (_animationListener == null) {
85 | WidgetsBinding.instance.addPostFrameCallback((_) {
86 | _animationListener = _paddingAnimationStatusChanged;
87 | _animationKey.currentState?.animation
88 | .addStatusListener(_animationListener!);
89 | });
90 | }
91 |
92 | // If [child] is a [ScrollView], get its [ScrollController]
93 | // and embed the [child] directly in an [AnimatedContainer].
94 | if (widget.child is ScrollView) {
95 | var scrollView = widget.child as ScrollView;
96 | _scrollController =
97 | scrollView.controller ?? PrimaryScrollController.of(context);
98 | return _buildAnimatedContainer(widget.child);
99 | }
100 | // If [child] is not a [ScrollView], and [autoScroll] is true,
101 | // embed the [child] in a [SingleChildScrollView] to make
102 | // it possible to scroll to the focused widget.
103 | if (widget.autoScroll) {
104 | _scrollController = new ScrollController();
105 | return _buildAnimatedContainer(
106 | LayoutBuilder(
107 | builder: (context, constraints) {
108 | return SingleChildScrollView(
109 | physics: widget.physics,
110 | controller: _scrollController,
111 | child: ConstrainedBox(
112 | constraints: BoxConstraints(
113 | minHeight: constraints.maxHeight,
114 | ),
115 | child: widget.child,
116 | ),
117 | );
118 | },
119 | ),
120 | );
121 | }
122 | // Just embed the [child] directly in an [AnimatedContainer].
123 | return _buildAnimatedContainer(widget.child);
124 | }
125 |
126 | Widget _buildAnimatedContainer(Widget? child) {
127 | return AnimatedContainer(
128 | key: _animationKey,
129 | color: Colors.transparent,
130 | padding: EdgeInsets.only(bottom: widget.areaToAvoid),
131 | duration: widget.duration,
132 | curve: widget.curve,
133 | child: child,
134 | );
135 | }
136 |
137 | /// Called whenever the status of our padding animation changes.
138 | ///
139 | /// If the animation has completed, we added overlap, and scroll is on, scroll to that.
140 | void _paddingAnimationStatusChanged(AnimationStatus status) {
141 | if (status != AnimationStatus.completed) {
142 | return; // Only check when the animation is finishing
143 | }
144 | if (!widget.autoScroll) {
145 | return; // auto scroll is not enabled, do nothing
146 | }
147 | if (widget.areaToAvoid <= _previousAreaToAvoid) {
148 | return; // decreased-- do nothing. We only scroll when area to avoid is added (keyboard shown).
149 | }
150 | // Need to wait a frame to get the new size (todo: is this still needed? we dont use mediaquery anymore)
151 | WidgetsBinding.instance.addPostFrameCallback((_) {
152 | if (!mounted) {
153 | return; // context is no longer valid
154 | }
155 | scrollToOverscroll();
156 | });
157 | }
158 |
159 | void scrollToOverscroll() {
160 | final focused = findFocusedObject(context.findRenderObject());
161 | if (focused == null || _scrollController == null) return;
162 | scrollToObject(focused, _scrollController!, widget.duration, widget.curve,
163 | widget.overscroll);
164 | }
165 | }
166 |
167 | /// Utility helper methods
168 |
169 | /// Finds the first focused focused child of [root] using a breadth-first search.
170 | RenderObject? findFocusedObject(RenderObject? root) {
171 | final q = Queue();
172 | q.add(root);
173 | while (q.isNotEmpty) {
174 | final node = q.removeFirst()!;
175 | final config = SemanticsConfiguration();
176 | //ignore: invalid_use_of_protected_member
177 | node.describeSemanticsConfiguration(config);
178 | if (config.isFocused) {
179 | return node;
180 | }
181 | node.visitChildrenForSemantics((child) {
182 | q.add(child);
183 | });
184 | }
185 | return null;
186 | }
187 |
188 | /// Scroll to the given [object], which must be inside [scrollController]s viewport.
189 | scrollToObject(RenderObject object, ScrollController scrollController,
190 | Duration duration, Curve curve, double overscroll) {
191 | // Calculate the offset needed to show the object in the [ScrollView]
192 | // so that its bottom touches the top of the keyboard.
193 | final viewport = RenderAbstractViewport.of(object);
194 | final offset = viewport.getOffsetToReveal(object, 1.0).offset + overscroll;
195 |
196 | // If the object is covered by the keyboard, scroll to reveal it,
197 | // and add [focusPadding] between it and top of the keyboard.
198 | if (offset > scrollController.position.pixels) {
199 | scrollController.position.moveTo(
200 | offset,
201 | duration: duration,
202 | curve: curve,
203 | );
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/lib/external/keyboard_avoider/keyboard_avoider.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/widgets.dart';
4 |
5 | import 'bottom_area_avoider.dart';
6 |
7 | /// A widget that re-sizes its [child] to avoid the system keyboard.
8 | ///
9 | /// Unlike a [Scaffold], it only insets by the actual amount obscured by the keyboard.
10 | ///
11 | /// Watches for media query changes via [didChangeMetrics], and adjusts a [BottomAreaAvoider] accordingly.
12 | class KeyboardAvoider extends StatefulWidget {
13 | /// See [BottomAreaAvoider.child]
14 | final Widget child;
15 |
16 | /// See [BottomAreaAvoider.duration]
17 | final Duration duration;
18 |
19 | /// See [BottomAreaAvoider.curve]
20 | final Curve curve;
21 |
22 | /// See [BottomAreaAvoider.autoScroll]
23 | final bool autoScroll;
24 |
25 | /// See [BottomAreaAvoider.overscroll]
26 | final double overscroll;
27 |
28 | /// See [BottomAreaAvoider.physics]
29 | final ScrollPhysics? physics;
30 |
31 | KeyboardAvoider({
32 | Key? key,
33 | required this.child,
34 | this.physics,
35 | this.duration = BottomAreaAvoider.defaultDuration,
36 | this.curve = BottomAreaAvoider.defaultCurve,
37 | this.autoScroll = BottomAreaAvoider.defaultAutoScroll,
38 | this.overscroll = BottomAreaAvoider.defaultOverscroll,
39 | }) : assert(child is ScrollView ? child.controller != null : true),
40 | super(key: key);
41 |
42 | _KeyboardAvoiderState createState() => _KeyboardAvoiderState();
43 | }
44 |
45 | class _KeyboardAvoiderState extends State
46 | with WidgetsBindingObserver {
47 | /// The current amount of keyboard overlap.
48 | double _keyboardOverlap = 0.0;
49 |
50 | @override
51 | void initState() {
52 | super.initState();
53 | WidgetsBinding.instance.addObserver(this);
54 | }
55 |
56 | @override
57 | void dispose() {
58 | WidgetsBinding.instance.removeObserver(this);
59 | super.dispose();
60 | }
61 |
62 | @override
63 | Widget build(BuildContext context) {
64 | return BottomAreaAvoider(
65 | child: widget.child,
66 | areaToAvoid: _keyboardOverlap,
67 | autoScroll: widget.autoScroll,
68 | curve: widget.curve,
69 | duration: widget.duration,
70 | overscroll: widget.overscroll,
71 | physics: widget.physics,
72 | );
73 | }
74 |
75 | /// WidgetsBindingObserver
76 |
77 | @override
78 | void didChangeMetrics() {
79 | // Need to wait a frame to get the new size
80 | WidgetsBinding.instance.addPostFrameCallback((_) {
81 | _resize();
82 | });
83 | }
84 |
85 | /// Re-calculates the amount of overlap, based on the current [MediaQueryData.viewInsets].
86 | void _resize() {
87 | if (!mounted) {
88 | return;
89 | }
90 |
91 | // Calculate Rect of widget on screen
92 | final object = context.findRenderObject()!;
93 | final box = object as RenderBox;
94 | final offset = box.localToGlobal(Offset.zero);
95 | final widgetRect = Rect.fromLTWH(
96 | offset.dx,
97 | offset.dy,
98 | box.size.width,
99 | box.size.height,
100 | );
101 |
102 | // Calculate top of keyboard
103 | final mediaQuery = MediaQuery.of(context);
104 | final screenSize = mediaQuery.size;
105 | final screenInsets = mediaQuery.viewInsets;
106 | final keyboardTop = screenSize.height - screenInsets.bottom;
107 |
108 | // If widget is entirely covered by keyboard, do nothing
109 | if (widgetRect.top > keyboardTop) {
110 | return;
111 | }
112 |
113 | // If widget is partially obscured by the keyboard, adjust bottom padding to fully expose it
114 | final overlap = max(0.0, widgetRect.bottom - keyboardTop);
115 | if (overlap != _keyboardOverlap) {
116 | setState(() {
117 | _keyboardOverlap = overlap;
118 | });
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/external/platform_check/platform_check.dart:
--------------------------------------------------------------------------------
1 | import 'platform_web.dart' if (dart.library.io) 'platform_io.dart';
2 |
3 | /// Class to check which is the current platform allow the compilation from web/mobile/desktop
4 | abstract class PlatformCheck {
5 | static bool get isWeb => currentPlatform == PlatformCheckType.Web;
6 | static bool get isMacOS => currentPlatform == PlatformCheckType.MacOS;
7 | static bool get isWindows => currentPlatform == PlatformCheckType.Windows;
8 | static bool get isLinux => currentPlatform == PlatformCheckType.Linux;
9 | static bool get isAndroid => currentPlatform == PlatformCheckType.Android;
10 | static bool get isIOS => currentPlatform == PlatformCheckType.IOS;
11 | }
12 |
13 | enum PlatformCheckType { Web, Windows, Linux, MacOS, Android, Fuchsia, IOS }
14 |
--------------------------------------------------------------------------------
/lib/external/platform_check/platform_io.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'platform_check.dart';
3 |
4 | PlatformCheckType get currentPlatform {
5 | if (Platform.isWindows) return PlatformCheckType.Windows;
6 | if (Platform.isFuchsia) return PlatformCheckType.Fuchsia;
7 | if (Platform.isMacOS) return PlatformCheckType.MacOS;
8 | if (Platform.isLinux) return PlatformCheckType.Linux;
9 | if (Platform.isIOS) return PlatformCheckType.IOS;
10 | return PlatformCheckType.Android;
11 | }
12 |
--------------------------------------------------------------------------------
/lib/external/platform_check/platform_web.dart:
--------------------------------------------------------------------------------
1 | import 'platform_check.dart';
2 |
3 | //Default to web, the platform_io class will override this if it gets imported.
4 | PlatformCheckType get currentPlatform => PlatformCheckType.Web;
5 |
--------------------------------------------------------------------------------
/lib/keyboard_actions.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:keyboard_actions/external/keyboard_avoider/bottom_area_avoider.dart';
6 | import 'package:keyboard_actions/external/platform_check/platform_check.dart';
7 |
8 | import 'keyboard_actions_config.dart';
9 | import 'keyboard_actions_item.dart';
10 |
11 | export 'keyboard_actions_config.dart';
12 | export 'keyboard_actions_item.dart';
13 | export 'keyboard_custom.dart';
14 |
15 | const double _kBarSize = 45.0;
16 | const Duration _timeToDismiss = Duration(milliseconds: 110);
17 |
18 | enum KeyboardActionsPlatform {
19 | ANDROID,
20 | IOS,
21 | ALL,
22 | }
23 |
24 | /// The behavior when tapped outside the keyboard.
25 | ///
26 | /// none: no overlay is added;
27 | ///
28 | /// opaqueDismiss: an overlay is added which blocks the underneath widgets from
29 | /// gestures. Once tapped, the keyboard will be dismissed;
30 | ///
31 | /// translucentDismiss: an overlay is added which permits the underneath widgets
32 | /// to receive gestures. Once tapped, the keyboard will be dismissed;
33 | enum TapOutsideBehavior {
34 | none,
35 | opaqueDismiss,
36 | translucentDismiss,
37 | }
38 |
39 | /// A widget that shows a bar of actions above the keyboard, to help customize input.
40 | ///
41 | /// To use this class, add it somewhere higher up in your widget hierarchy. Then, from any child
42 | /// widgets, add [KeyboardActionsConfig] to configure it with the [KeyboardAction]s you'd
43 | /// like to use. These will be displayed whenever the wrapped focus nodes are selected.
44 | ///
45 | /// This widget wraps a [KeyboardAvoider], which takes over functionality from [Scaffold]: when the
46 | /// focus changes, this class re-sizes [child]'s focused object to still be visible, and scrolls to the
47 | /// focused node. **As such, set [Scaffold.resizeToAvoidBottomInset] to _false_ when using this Widget.**
48 | ///
49 | /// We manage resizing ourselves so that:
50 | ///
51 | /// 1. using scaffold is not required
52 | /// 2. content is only shrunk as needed (a problem with scaffold)
53 | /// 3. we shrink an additional [_kBarSize] so the keyboard action bar doesn't cover content either.
54 | class KeyboardActions extends StatefulWidget {
55 | /// Any content you want to resize/scroll when the keyboard comes up
56 | final Widget? child;
57 |
58 | /// Keyboard configuration
59 | final KeyboardActionsConfig config;
60 |
61 | /// If you want the content to auto-scroll when focused; see [KeyboardAvoider.autoScroll]
62 | final bool autoScroll;
63 |
64 | /// In case you don't want to enable keyboard_action bar (e.g. You are running your app on iPad)
65 | final bool enable;
66 |
67 | /// If you are using keyboard_actions inside a Dialog it must be true
68 | final bool isDialog;
69 |
70 | /// Tap outside the keyboard will dismiss this
71 | @Deprecated('Use tapOutsideBehavior instead.')
72 | final bool tapOutsideToDismiss;
73 |
74 | /// Tap outside behavior
75 | final TapOutsideBehavior tapOutsideBehavior;
76 |
77 | /// If you want to add overscroll. Eg: In some cases you have a [TextField] with an error text below that.
78 | final double overscroll;
79 |
80 | /// If you want to control the scroll physics of [BottomAreaAvoider] which uses a [SingleChildScrollView] to contain the child.
81 | final ScrollPhysics? bottomAvoiderScrollPhysics;
82 |
83 | /// If you are using [KeyboardActions] for just one textfield and don't need to scroll the content set this to `true`
84 | final bool disableScroll;
85 |
86 | /// Does not clear the focus if you tap on the node focused, useful for keeping the text cursor selection working. Usually used with tapOutsideBehavior as translucent
87 | final bool keepFocusOnTappingNode;
88 |
89 | /// Override default height of the bar. `null` is dynamic height
90 | final double? barSize;
91 |
92 | const KeyboardActions(
93 | {this.child,
94 | this.bottomAvoiderScrollPhysics,
95 | this.enable = true,
96 | this.autoScroll = true,
97 | this.isDialog = false,
98 | @Deprecated('Use tapOutsideBehavior instead.')
99 | this.tapOutsideToDismiss = false,
100 | this.tapOutsideBehavior = TapOutsideBehavior.none,
101 | required this.config,
102 | this.overscroll = 12.0,
103 | this.disableScroll = false,
104 | this.keepFocusOnTappingNode = false,
105 | this.barSize = _kBarSize})
106 | : assert(child != null);
107 |
108 | @override
109 | KeyboardActionstate createState() => KeyboardActionstate();
110 | }
111 |
112 | /// State class for [KeyboardActions].
113 | class KeyboardActionstate extends State
114 | with WidgetsBindingObserver {
115 | /// The currently configured keyboard actions
116 | KeyboardActionsConfig? config;
117 |
118 | /// private state
119 | Map _map = Map();
120 | KeyboardActionsItem? _currentAction;
121 | int? _currentIndex = 0;
122 | OverlayEntry? _overlayEntry;
123 | double _offset = 0;
124 | PreferredSizeWidget? _currentFooter;
125 | bool _dismissAnimationNeeded = true;
126 | final _keyParent = GlobalKey();
127 | Completer? _dismissAnimation;
128 |
129 | /// If the keyboard bar is on for the current platform
130 | bool get _isAvailable {
131 | return config!.keyboardActionsPlatform == KeyboardActionsPlatform.ALL ||
132 | (config!.keyboardActionsPlatform == KeyboardActionsPlatform.IOS &&
133 | PlatformCheck.isIOS) ||
134 | (config!.keyboardActionsPlatform == KeyboardActionsPlatform.ANDROID &&
135 | PlatformCheck.isAndroid);
136 | }
137 |
138 | /// If we are currently showing the keyboard bar
139 | bool get _isShowing {
140 | return _overlayEntry != null;
141 | }
142 |
143 | /// The current previous index, or null.
144 | int? get _previousIndex {
145 | final nextIndex = _currentIndex! - 1;
146 | return nextIndex >= 0 ? nextIndex : null;
147 | }
148 |
149 | /// The current next index, or null.
150 | int? get _nextIndex {
151 | final nextIndex = _currentIndex! + 1;
152 | return nextIndex < _map.length ? nextIndex : null;
153 | }
154 |
155 | /// The distance from the bottom of the KeyboardActions widget to the
156 | /// bottom of the view port.
157 | ///
158 | /// Used to correctly calculate the offset to "avoid" with BottomAreaAvoider.
159 | double get _distanceBelowWidget {
160 | if (_keyParent.currentContext != null) {
161 | final widgetRenderBox =
162 | _keyParent.currentContext!.findRenderObject() as RenderBox;
163 | final fullHeight = MediaQuery.of(context).size.height;
164 | final widgetHeight = widgetRenderBox.size.height;
165 | final widgetTop = widgetRenderBox.localToGlobal(Offset.zero).dy;
166 | final widgetBottom = widgetTop + widgetHeight;
167 | final distanceBelowWidget = fullHeight - widgetBottom;
168 | return distanceBelowWidget;
169 | }
170 | return 0;
171 | }
172 |
173 | /// Set the config for the keyboard action bar.
174 | void setConfig(KeyboardActionsConfig newConfig) {
175 | clearConfig();
176 | config = newConfig;
177 | for (int i = 0; i < config!.actions!.length; i++) {
178 | _addAction(i, config!.actions![i]);
179 | }
180 | _startListeningFocus();
181 | }
182 |
183 | /// Clear any existing configuration. Unsubscribe from focus listeners.
184 | void clearConfig() {
185 | _dismissListeningFocus();
186 | _clearAllFocusNode();
187 | config = null;
188 | }
189 |
190 | void _addAction(int index, KeyboardActionsItem action) {
191 | _map[index] = action;
192 | }
193 |
194 | void _clearAllFocusNode() {
195 | _map = Map();
196 | }
197 |
198 | void _clearFocus() {
199 | _currentAction?.focusNode.unfocus();
200 | }
201 |
202 | Future _focusNodeListener() async {
203 | bool hasFocusFound = false;
204 | _map.keys.forEach((key) {
205 | final currentAction = _map[key]!;
206 | if (currentAction.focusNode.hasFocus) {
207 | hasFocusFound = true;
208 | _currentAction = currentAction;
209 | _currentIndex = key;
210 | return;
211 | }
212 | });
213 | _focusChanged(hasFocusFound);
214 | }
215 |
216 | void _shouldGoToNextFocus(KeyboardActionsItem action, int? nextIndex) async {
217 | _dismissAnimationNeeded = true;
218 | _currentAction = action;
219 | _currentIndex = nextIndex;
220 | //remove focus for unselected fields
221 | _map.keys.forEach((key) {
222 | final currentAction = _map[key]!;
223 | if (currentAction == _currentAction &&
224 | currentAction.footerBuilder != null) {
225 | _dismissAnimationNeeded = false;
226 | }
227 | if (currentAction != _currentAction) {
228 | currentAction.focusNode.unfocus();
229 | }
230 | });
231 | //if it is a custom keyboard then wait until the focus was dismissed from the others
232 | if (_currentAction!.footerBuilder != null) {
233 | await Future.delayed(
234 | Duration(milliseconds: _timeToDismiss.inMilliseconds),
235 | );
236 | }
237 |
238 | FocusScope.of(context).requestFocus(_currentAction!.focusNode);
239 | await Future.delayed(const Duration(milliseconds: 100));
240 | bottomAreaAvoiderKey.currentState?.scrollToOverscroll();
241 | }
242 |
243 | void _onTapUp() {
244 | if (_previousIndex != null) {
245 | final currentAction = _map[_previousIndex!]!;
246 | if (currentAction.enabled) {
247 | _shouldGoToNextFocus(currentAction, _previousIndex);
248 | } else {
249 | _currentIndex = _previousIndex;
250 | _onTapUp();
251 | }
252 | }
253 | }
254 |
255 | void _onTapDown() {
256 | if (_nextIndex != null) {
257 | final currentAction = _map[_nextIndex!]!;
258 | if (currentAction.enabled) {
259 | _shouldGoToNextFocus(currentAction, _nextIndex);
260 | } else {
261 | _currentIndex = _nextIndex;
262 | _onTapDown();
263 | }
264 | }
265 | }
266 |
267 | /// Shows or hides the keyboard bar as needed, and re-calculates the overlay offset.
268 | ///
269 | /// Called every time the focus changes, and when the app is resumed on Android.
270 | void _focusChanged(bool showBar) async {
271 | if (_isAvailable) {
272 | if (_dismissAnimation != null) {
273 | // wait for the previous animation to complete
274 | await _dismissAnimation?.future;
275 | }
276 | if (showBar && !_isShowing) {
277 | _insertOverlay();
278 | } else if (!showBar && _isShowing) {
279 | _removeOverlay();
280 | } else if (showBar && _isShowing) {
281 | if (PlatformCheck.isAndroid) {
282 | _updateOffset();
283 | }
284 | _overlayEntry!.markNeedsBuild();
285 | }
286 | if (_currentAction != null && _currentAction!.footerBuilder != null) {
287 | WidgetsBinding.instance.addPostFrameCallback((_) {
288 | _updateOffset();
289 | });
290 | }
291 | }
292 | }
293 |
294 | @override
295 | void didChangeMetrics() {
296 | if (PlatformCheck.isAndroid) {
297 | final value = WidgetsBinding.instance.window.viewInsets.bottom;
298 | bool keyboardIsOpen = value > 0;
299 | _onKeyboardChanged(keyboardIsOpen);
300 | isKeyboardOpen = keyboardIsOpen;
301 | }
302 | // Need to wait a frame to get the new size
303 | WidgetsBinding.instance.addPostFrameCallback((_) {
304 | _updateOffset();
305 | });
306 | }
307 |
308 | void _startListeningFocus() {
309 | _map.values
310 | .forEach((action) => action.focusNode.addListener(_focusNodeListener));
311 | }
312 |
313 | void _dismissListeningFocus() {
314 | _map.values.forEach(
315 | (action) => action.focusNode.removeListener(_focusNodeListener));
316 | }
317 |
318 | bool _inserted = false;
319 |
320 | /// Insert the keyboard bar as an Overlay.
321 | ///
322 | /// This will be inserted above everything else in the MaterialApp, including dialog modals.
323 | ///
324 | /// Position the overlay based on the current [MediaQuery] to land above the keyboard.
325 | void _insertOverlay() {
326 | OverlayState os = Overlay.of(context);
327 | _inserted = true;
328 | _overlayEntry = OverlayEntry(builder: (context) {
329 | // Update and build footer, if any
330 | _currentFooter = (_currentAction!.footerBuilder != null)
331 | ? _currentAction!.footerBuilder!(context)
332 | : null;
333 |
334 | final queryData = MediaQuery.of(context);
335 | return Stack(
336 | children: [
337 | if (widget.tapOutsideBehavior != TapOutsideBehavior.none ||
338 | // ignore: deprecated_member_use_from_same_package
339 | widget.tapOutsideToDismiss)
340 | Positioned.fill(
341 | child: Listener(
342 | onPointerDown: (event) {
343 | if (!widget.keepFocusOnTappingNode ||
344 | _currentAction?.focusNode.rect.contains(event.position) !=
345 | true) {
346 | _clearFocus();
347 | }
348 | },
349 | behavior: widget.tapOutsideBehavior ==
350 | TapOutsideBehavior.translucentDismiss
351 | ? HitTestBehavior.translucent
352 | : HitTestBehavior.opaque,
353 | ),
354 | ),
355 | Positioned(
356 | left: 0,
357 | right: 0,
358 | bottom: queryData.viewInsets.bottom,
359 | child: Material(
360 | color: config!.keyboardBarColor ?? Colors.grey[200],
361 | elevation: config!.keyboardBarElevation ?? 20,
362 | child: Column(
363 | mainAxisSize: MainAxisSize.min,
364 | children: [
365 | if (_currentAction!.displayActionBar)
366 | _buildBar(_currentAction!.displayArrows),
367 | if (_currentFooter != null)
368 | AnimatedContainer(
369 | duration: _timeToDismiss,
370 | child: _currentFooter,
371 | height:
372 | _inserted ? _currentFooter!.preferredSize.height : 0,
373 | ),
374 | ],
375 | ),
376 | ),
377 | ),
378 | ],
379 | );
380 | });
381 | os.insert(_overlayEntry!);
382 | }
383 |
384 | /// Remove the overlay bar. Call when losing focus or being dismissed.
385 | void _removeOverlay({bool fromDispose = false}) async {
386 | _inserted = false;
387 | if (_currentFooter != null && _dismissAnimationNeeded) {
388 | if (mounted && !fromDispose) {
389 | _overlayEntry?.markNeedsBuild();
390 | // add a completer to indicate the completion of dismiss animation.
391 | _dismissAnimation = Completer();
392 | await Future.delayed(_timeToDismiss);
393 | _dismissAnimation?.complete();
394 | _dismissAnimation = null;
395 | }
396 | }
397 | _overlayEntry?.remove();
398 | _overlayEntry = null;
399 | _currentFooter = null;
400 | if (!fromDispose && _dismissAnimationNeeded) _updateOffset();
401 | _dismissAnimationNeeded = true;
402 | }
403 |
404 | void _updateOffset() {
405 | if (!mounted) {
406 | return;
407 | }
408 |
409 | if (!_isShowing || !_isAvailable) {
410 | setState(() {
411 | _offset = 0.0;
412 | });
413 | return;
414 | }
415 |
416 | double newOffset = _currentAction!.displayActionBar
417 | ? _kBarSize
418 | : 0; // offset for the actions bar
419 |
420 | final keyboardHeight = EdgeInsets.fromWindowPadding(
421 | WidgetsBinding.instance.window.viewInsets,
422 | WidgetsBinding.instance.window.devicePixelRatio)
423 | .bottom;
424 |
425 | newOffset += keyboardHeight; // + offset for the system keyboard
426 |
427 | if (_currentFooter != null) {
428 | newOffset +=
429 | _currentFooter!.preferredSize.height; // + offset for the footer
430 | }
431 |
432 | newOffset -= _localMargin + _distanceBelowWidget;
433 |
434 | if (newOffset < 0) newOffset = 0;
435 |
436 | // Update state if changed
437 | if (_offset != newOffset) {
438 | setState(() {
439 | _offset = newOffset;
440 | });
441 | }
442 | }
443 |
444 | double _localMargin = 0.0;
445 |
446 | void _onLayout() {
447 | if (widget.isDialog) {
448 | final render =
449 | _keyParent.currentContext?.findRenderObject() as RenderBox?;
450 | final fullHeight = MediaQuery.of(context).size.height;
451 | final localHeight = render?.size.height ?? 0;
452 | _localMargin = (fullHeight - localHeight) / 2;
453 | }
454 | }
455 |
456 | @override
457 | void didChangeAppLifecycleState(AppLifecycleState state) {
458 | if (defaultTargetPlatform == TargetPlatform.android) {
459 | if (state == AppLifecycleState.paused) {
460 | FocusScope.of(context).requestFocus(FocusNode());
461 | _focusChanged(false);
462 | }
463 | }
464 | super.didChangeAppLifecycleState(state);
465 | }
466 |
467 | @override
468 | void didUpdateWidget(KeyboardActions oldWidget) {
469 | if (widget.enable) setConfig(widget.config);
470 | super.didUpdateWidget(oldWidget);
471 | }
472 |
473 | @override
474 | void dispose() {
475 | clearConfig();
476 | _removeOverlay(fromDispose: true);
477 | WidgetsBinding.instance.removeObserver(this);
478 | super.dispose();
479 | }
480 |
481 | @override
482 | void initState() {
483 | WidgetsBinding.instance.addObserver(this);
484 | if (widget.enable) {
485 | setConfig(widget.config);
486 | WidgetsBinding.instance.addPostFrameCallback((_) {
487 | _onLayout();
488 | _updateOffset();
489 | });
490 | }
491 | super.initState();
492 | }
493 |
494 | var isKeyboardOpen = false;
495 |
496 | void _onKeyboardChanged(bool isVisible) {
497 | bool footerHasSize = _checkIfFooterHasSize();
498 | if (!isVisible && isKeyboardOpen && !footerHasSize) {
499 | _clearFocus();
500 | }
501 | }
502 |
503 | bool _checkIfFooterHasSize() {
504 | return _currentFooter != null &&
505 | (_currentFooter?.preferredSize.height ?? 0) > 0;
506 | }
507 |
508 | /// Build the keyboard action bar based on the current [config].
509 | Widget _buildBar(bool displayArrows) {
510 | return AnimatedCrossFade(
511 | duration: _timeToDismiss,
512 | crossFadeState:
513 | _isShowing ? CrossFadeState.showFirst : CrossFadeState.showSecond,
514 | firstChild: Container(
515 | height: widget.barSize,
516 | width: MediaQuery.of(context).size.width,
517 | decoration: BoxDecoration(
518 | border: Border(
519 | top: BorderSide(
520 | color: widget.config.keyboardSeparatorColor,
521 | width: 1.0,
522 | ),
523 | ),
524 | ),
525 | child: SafeArea(
526 | top: false,
527 | bottom: false,
528 | child: Row(
529 | mainAxisAlignment:
530 | _currentAction?.toolbarAlignment ?? MainAxisAlignment.end,
531 | children: [
532 | if (config!.nextFocus && displayArrows) ...[
533 | IconButton(
534 | icon: Icon(Icons.keyboard_arrow_up),
535 | tooltip: 'Previous',
536 | iconSize: IconTheme.of(context).size!,
537 | color: IconTheme.of(context).color,
538 | disabledColor: Theme.of(context).disabledColor,
539 | onPressed: _previousIndex != null ? _onTapUp : null,
540 | ),
541 | IconButton(
542 | icon: Icon(Icons.keyboard_arrow_down),
543 | tooltip: 'Next',
544 | iconSize: IconTheme.of(context).size!,
545 | color: IconTheme.of(context).color,
546 | disabledColor: Theme.of(context).disabledColor,
547 | onPressed: _nextIndex != null ? _onTapDown : null,
548 | ),
549 | const Spacer(),
550 | ],
551 | if (_currentAction?.displayDoneButton != null &&
552 | _currentAction!.displayDoneButton &&
553 | (_currentAction!.toolbarButtons == null ||
554 | _currentAction!.toolbarButtons!.isEmpty))
555 | Padding(
556 | padding: const EdgeInsets.all(5.0),
557 | child: InkWell(
558 | onTap: () {
559 | if (_currentAction?.onTapAction != null) {
560 | _currentAction!.onTapAction!();
561 | }
562 | _clearFocus();
563 | },
564 | child: Container(
565 | padding:
566 | EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
567 | child: config?.defaultDoneWidget ??
568 | Text(
569 | "Done",
570 | style: TextStyle(
571 | fontSize: 16.0,
572 | fontWeight: FontWeight.w500,
573 | ),
574 | ),
575 | ),
576 | ),
577 | ),
578 | if (_currentAction?.toolbarButtons != null)
579 | ..._currentAction!.toolbarButtons!
580 | .map((item) => item(_currentAction!.focusNode))
581 | .toList()
582 | ],
583 | ),
584 | ),
585 | ),
586 | secondChild: const SizedBox.shrink(),
587 | );
588 | }
589 |
590 | final GlobalKey bottomAreaAvoiderKey =
591 | GlobalKey();
592 |
593 | @override
594 | Widget build(BuildContext context) {
595 | // Return the given child wrapped in a [KeyboardAvoider].
596 | // We will call [_buildBar] and insert it via overlay on demand.
597 | // Add [_kBarSize] padding to ensure we scroll past the action bar.
598 |
599 | // We need to add this sized box to support embedding in IntrinsicWidth
600 | // areas, like AlertDialog. This is because of the LayoutBuilder KeyboardAvoider uses
601 | // if it has no child ScrollView.
602 | // If we don't, we get "LayoutBuilder does not support returning intrinsic dimensions".
603 | // See https://github.com/flutter/flutter/issues/18108.
604 | // The SizedBox can be removed when thats fixed.
605 | return widget.enable && !widget.disableScroll
606 | ? Material(
607 | color: Colors.transparent,
608 | child: SizedBox(
609 | width: double.maxFinite,
610 | key: _keyParent,
611 | child: BottomAreaAvoider(
612 | key: bottomAreaAvoiderKey,
613 | areaToAvoid: _offset,
614 | overscroll: widget.overscroll,
615 | duration: Duration(
616 | milliseconds:
617 | (_timeToDismiss.inMilliseconds * 1.8).toInt()),
618 | autoScroll: widget.autoScroll,
619 | physics: widget.bottomAvoiderScrollPhysics,
620 | child: widget.child,
621 | ),
622 | ),
623 | )
624 | : widget.child!;
625 | }
626 | }
627 |
--------------------------------------------------------------------------------
/lib/keyboard_actions_config.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'keyboard_actions.dart';
3 |
4 | /// Wrapper for a single configuration of the keyboard actions bar.
5 | class KeyboardActionsConfig {
6 | /// Keyboard Action for specific platform
7 | /// KeyboardActionsPlatform : ANDROID , IOS , ALL
8 | final KeyboardActionsPlatform keyboardActionsPlatform;
9 |
10 | /// true to display arrows prev/next to move focus between inputs
11 | final bool nextFocus;
12 |
13 | /// [KeyboardActionsItem] for each input
14 | final List? actions;
15 |
16 | /// Color of the background to the Custom keyboard buttons
17 | final Color? keyboardBarColor;
18 |
19 | /// Elevation of the Custom keyboard buttons
20 | final double? keyboardBarElevation;
21 |
22 | /// Color of the line separator between keyboard and content
23 | final Color keyboardSeparatorColor;
24 |
25 | /// A [Widget] to be optionally used instead of the "Done" button
26 | /// which dismisses the keyboard.
27 | final Widget? defaultDoneWidget;
28 |
29 | const KeyboardActionsConfig({
30 | this.keyboardActionsPlatform = KeyboardActionsPlatform.ALL,
31 | this.nextFocus = true,
32 | this.actions,
33 | this.keyboardBarColor,
34 | this.keyboardBarElevation,
35 | this.keyboardSeparatorColor = Colors.transparent,
36 | this.defaultDoneWidget,
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/lib/keyboard_actions_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | typedef ButtonBuilder = Widget Function(FocusNode focusNode);
4 |
5 | ///Class to define the `focusNode` that you pass to your `TextField` too and other params to customize
6 | ///the bar that will appear over your keyboard
7 | class KeyboardActionsItem {
8 | /// The Focus object coupled to TextField, listening for got/lost focus events
9 | final FocusNode focusNode;
10 |
11 | /// Optional widgets to display to the right of the bar/
12 | /// NOTE: `toolbarButtons` override the Done button by default
13 | final List? toolbarButtons;
14 |
15 | /// true [default] to display the Done button
16 | final bool displayDoneButton;
17 |
18 | /// Optional callback if the Done button for TextField was tapped
19 | /// It will only work if `displayDoneButton` is [true] and `toolbarButtons` is null or empty
20 | final VoidCallback? onTapAction;
21 |
22 | /// true [default] to display the arrows to move between the fields
23 | final bool displayArrows;
24 |
25 | /// true [default] if the TextField is enabled
26 | final bool enabled;
27 |
28 | /// true [default] to display the action bar
29 | final bool displayActionBar;
30 |
31 | /// Builder for an optional widget to show below the action bar.
32 | ///
33 | /// Consider using for field validation or as a replacement for a system keyboard.
34 | ///
35 | /// This widget must be a PreferredSizeWidget to report its exact height; use [Size.fromHeight]
36 | final PreferredSizeWidget Function(BuildContext context)? footerBuilder;
37 |
38 | /// Alignment of the row that displays [toolbarButtons]. If you want to show your
39 | /// buttons from the left side of the toolbar, you can set [toolbarAlignment] and
40 | /// set the value of [displayArrows] to `false`
41 | final MainAxisAlignment toolbarAlignment;
42 |
43 | const KeyboardActionsItem({
44 | required this.focusNode,
45 | this.onTapAction,
46 | this.toolbarButtons,
47 | this.enabled = true,
48 | this.displayActionBar = true,
49 | this.displayArrows = true,
50 | this.displayDoneButton = true,
51 | this.footerBuilder,
52 | this.toolbarAlignment = MainAxisAlignment.end,
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/lib/keyboard_custom.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// Signature for a function that creates a widget for a given value
4 | typedef WidgetKeyboardBuilder = Widget Function(
5 | BuildContext context, T value, bool? hasFocus);
6 |
7 | /// A widget that allow us to create a custom keyboard instead of the platform keyboard.
8 | class KeyboardCustomInput extends StatefulWidget {
9 | ///Create your own widget and receive the [T] value
10 | final WidgetKeyboardBuilder builder;
11 |
12 | ///Set the same `focusNode` you add to the [KeyboardAction]
13 | final FocusNode focusNode;
14 |
15 | ///The height of your widget
16 | final double? height;
17 |
18 | ///Set the same `notifier` you add to the [KeyboardAction]
19 | final ValueNotifier notifier;
20 |
21 | const KeyboardCustomInput({
22 | Key? key,
23 | required this.focusNode,
24 | required this.builder,
25 | required this.notifier,
26 | this.height,
27 | }) : super(key: key);
28 |
29 | @override
30 | _KeyboardCustomInputState createState() => _KeyboardCustomInputState();
31 | }
32 |
33 | class _KeyboardCustomInputState extends State>
34 | with AutomaticKeepAliveClientMixin {
35 | bool? _hasFocus;
36 |
37 | @override
38 | void initState() {
39 | super.initState();
40 | _hasFocus = widget.focusNode.hasFocus;
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | super.build(context);
46 | return Focus(
47 | focusNode: widget.focusNode,
48 | child: GestureDetector(
49 | onTap: () {
50 | if (!widget.focusNode.hasFocus) {
51 | widget.focusNode.requestFocus();
52 | }
53 | },
54 | child: Container(
55 | height: widget.height,
56 | width: double.maxFinite,
57 | child: InputDecorator(
58 | decoration: InputDecoration(
59 | border: InputBorder.none,
60 | filled: false,
61 | disabledBorder: InputBorder.none,
62 | focusedBorder: InputBorder.none,
63 | errorBorder: InputBorder.none,
64 | enabled: false,
65 | ),
66 | isFocused: _hasFocus!,
67 | child: MergeSemantics(
68 | child: Semantics(
69 | focused: _hasFocus,
70 | child: Container(
71 | child: AnimatedBuilder(
72 | animation: widget.notifier,
73 | builder: (context, child) => widget.builder(
74 | context, widget.notifier.value, _hasFocus),
75 | ),
76 | ),
77 | ),
78 | ),
79 | ),
80 | ),
81 | ),
82 | onFocusChange: (newValue) => setState(() {
83 | _hasFocus = newValue;
84 | }),
85 | );
86 | }
87 |
88 | @override
89 | bool get wantKeepAlive => true;
90 | }
91 |
92 | /// A mixin which help to update the notifier, you must mix this class in case you want to create your own keyboard
93 | mixin KeyboardCustomPanelMixin {
94 | ///We'll use this notifier to send the data and refresh the widget inside [KeyboardCustomInput]
95 | ValueNotifier get notifier;
96 |
97 | ///This method will update the notifier
98 | void updateValue(T value) {
99 | notifier.value = value;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: keyboard_actions
2 | description: Now you can add features to the Android / iOS keyboard in a very simple way.
3 | version: 4.2.0
4 | homepage: https://github.com/diegoveloper/
5 | repository: https://github.com/diegoveloper/flutter_keyboard_actions/
6 |
7 | environment:
8 | sdk: '>=2.17.0 <3.0.0'
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | flutter:
--------------------------------------------------------------------------------