├── .github └── workflows │ └── dart.yaml ├── .gitignore ├── .metadata ├── .pubignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── coverage └── lcov.info ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── 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-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── 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 │ │ └── Runner-Bridging-Header.h ├── lib │ └── main.dart ├── pubspec.yaml └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ └── Icon-512.png │ ├── index.html │ └── manifest.json ├── lib ├── async_button_builder.dart └── src │ ├── async_button_builder.dart │ ├── async_button_notification.dart │ └── button_state │ ├── button_state.dart │ └── button_state.freezed.dart ├── pubspec.yaml ├── screenshots ├── ezgif-7-4088c909ba83.gif ├── ezgif-7-61c436edaec2.gif ├── ezgif-7-a971c6afaabf.gif └── ezgif-7-b620d3def232.gif └── test └── async_button_builder_test.dart /.github/workflows/dart.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | # runs the CI everyday at 10AM 8 | - cron: "0 10 * * *" 9 | 10 | jobs: 11 | setup: 12 | timeout-minutes: 15 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: "Git Checkout" 16 | uses: actions/checkout@v3 17 | 18 | - name: "Install Flutter" 19 | uses: subosito/flutter-action@v2 20 | 21 | - name: Install dependencies 22 | run: flutter pub get 23 | 24 | - name: Check format 25 | run: flutter format --set-exit-if-changed . 26 | 27 | - name: Analyze 28 | run: flutter analyze 29 | 30 | - name: Run tests 31 | run: flutter test 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | .idea 18 | .vscode/ 19 | 20 | # The .vscode folder contains launch configuration and tasks you configure in 21 | # VS Code which you may wish to be included in version control, so this line 22 | # is commented out by default. 23 | #.vscode/ 24 | 25 | # Flutter/Dart/Pub related 26 | **/doc/api/ 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | build/ 34 | 35 | # Android related 36 | **/android/**/gradle-wrapper.jar 37 | **/android/.gradle 38 | **/android/captures/ 39 | **/android/gradlew 40 | **/android/gradlew.bat 41 | **/android/local.properties 42 | **/android/**/GeneratedPluginRegistrant.java 43 | 44 | # iOS/XCode related 45 | **/ios/**/*.mode1v3 46 | **/ios/**/*.mode2v3 47 | **/ios/**/*.moved-aside 48 | **/ios/**/*.pbxuser 49 | **/ios/**/*.perspectivev3 50 | **/ios/**/*sync/ 51 | **/ios/**/.sconsign.dblite 52 | **/ios/**/.tags* 53 | **/ios/**/.vagrant/ 54 | **/ios/**/DerivedData/ 55 | **/ios/**/Icon? 56 | **/ios/**/Pods/ 57 | **/ios/**/.symlinks/ 58 | **/ios/**/profile 59 | **/ios/**/xcuserdata 60 | **/ios/.generated/ 61 | **/ios/Flutter/App.framework 62 | **/ios/Flutter/Flutter.framework 63 | **/ios/Flutter/Flutter.podspec 64 | **/ios/Flutter/Generated.xcconfig 65 | **/ios/Flutter/ephemeral 66 | **/ios/Flutter/app.flx 67 | **/ios/Flutter/app.zip 68 | **/ios/Flutter/flutter_assets/ 69 | **/ios/Flutter/flutter_export_environment.sh 70 | **/ios/ServiceDefinitions.json 71 | **/ios/Runner/GeneratedPluginRegistrant.* 72 | 73 | # Exceptions to above rules. 74 | !**/ios/**/default.mode1v3 75 | !**/ios/**/default.mode2v3 76 | !**/ios/**/default.pbxuser 77 | !**/ios/**/default.perspectivev3 78 | 79 | 80 | .melos_tool/ 81 | pubspec.lock 82 | **/pubspec.lock 83 | example/pubspec.lock 84 | -------------------------------------------------------------------------------- /.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: 63062a64432cce03315d6b5196fda7912866eb37 8 | channel: dev 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | screenshots/ 4 | build/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.0.0+1] - 2022-02-13 2 | - Fix `Undefined name 'optionalTypeArgs'.` Also adds back the hard dep on freezed_annotation. 3 | 4 | ## [3.0.0] - 2022-02-13 5 | - Added notification functionality. The error variant now passes through the error and stacktrace which would be needed for a notification to be useful. This is a breaking change as the error widget now takes an error and stacktrace as arguments. 6 | 7 | ## [2.3.0+2] - 2023-02-13 8 | - Not pushed 9 | 10 | ## [2.3.0+1] - 2022-05-24 11 | - Added back freezed_annotation as a hard dep. 12 | 13 | ## [2.3.0+1] - 2022-05-24 14 | - Fixed onError never being called when an error occurs - Credit @jonjomckay 15 | 16 | ## [2.3.0] - 2022-05-24 17 | - Removed freezed dependency and added new callbacks @mergehez 18 | - Upgrade to freezed 2 (excluding user now) @quantosapplications @esenmx 19 | 20 | ## [2.2.0+1] - 2022-01-28 21 | Ignore .vscode, update versions and re-export button state. Credit @esenmx 22 | 23 | ## [2.2.0] - 2022-01-07 24 | Got rid of hook version as it was complicating a simple package and likely discouraging possible contributors. 25 | 26 | ## [2.1.4] - 2021-09-28 27 | Fix manual button state changes. Credit to @iliser 28 | 29 | ## [2.1.3+9] - 2021-09-28 30 | Upgraded deps 31 | 32 | ## [2.1.3+8] - 2021-09-28 33 | Upgraded deps 34 | 35 | ## [2.1.3+7] - 2021-09-08 36 | Fixed github actions 37 | 38 | ## [2.1.3+6] - 2021-08-31 39 | Trying to fix pub stuff 40 | 41 | ## [2.1.3+5] - 2021-08-27 42 | Trying to fix pub stuff 43 | 44 | ## [2.1.3+4] - 2021-08-27 45 | Fix home page 46 | 47 | ## [2.1.3+3] - 2021-08-12 48 | Have proper types exported 49 | 50 | ## [2.1.3+2] - 2021-08-12 51 | Fix possibility of nested async button builders conflicting with each other due to matching keyed subtrees. In order to avoid this situation, the parent key of the async_button_builder is used in the creation of sub widgets. 52 | 53 | ## [2.1.3+1] - 2021-06-25 54 | Was not using proper sucessDuration when setting timeouts (regression) 55 | 56 | ## [2.1.3] - 2021-06-18 57 | Tests and builds against stable. flutter_lints now used in place of pedantic 58 | 59 | ## [2.1.2+1] - 2021-06-12 60 | Fixes analysis errors so I stop getting annoying emails 61 | 62 | ## [2.1.2] - 2021-05-24 63 | Errors from onpressed will throw with orignal call stack 64 | 65 | ## [2.1.1+2] - 2021-04-14 66 | Makes onpressed nullable similar to other button's behaviour 67 | 68 | ## [2.1.1+1] - 2021-04-14 69 | Add documentation on how to handle timeouts 70 | 71 | ## [2.1.1] - 2021-04-14 72 | ValueKeys are now no longer required in order to differentiate children 73 | 74 | ## [2.1.0] - 2021-02-12 75 | Remove null-safety prefix as it's now in stable 76 | 77 | ## [2.0.9-nullsafety.0] - 2021-02-12 78 | Trying to bump version again to go over stable 79 | 80 | ## [2.0.8-nullsafety.0] - 2021-02-12 81 | Trying to bump version again to go over stable 82 | 83 | ## [2.0.7-nullsafety.8] - 2021-02-12 84 | Republish so current nullsafe becomes main. My mistake 85 | 86 | ## [2.0.2-nullsafety.7] - 2021-02-12 87 | Added a typedef in place of inline type on function. Added doc to `builder` field 88 | 89 | ## [2.0.2-nullsafety.6] - 2021-02-09 90 | Renamed Union classes to match that of factory constructor name (Should not be breaking). 91 | 92 | ## [2.0.2-nullsafety.5] - 2021-02-01 93 | Removed example integration test 94 | 95 | ## [2.0.2-nullsafety.4] - 2021-01-26 96 | Adds tests and test coverage. Adds on dispose to cancel timer. 97 | 98 | ## [2.0.2-nullsafety.3] - 2021-01-25 99 | Removes dangling controller and cleans up main code up a bit 100 | 101 | ## [2.0.2-nullsafety.2] - 2021-01-25 102 | Fixes wrong image path 103 | 104 | ## [2.0.1-nullsafety.2] - 2021-01-25 105 | Renames fields to better match standards of other buttons. 106 | 107 | ## [2.0.0-dev.1] - 2021-01-25 108 | 109 | Breaking. Replaces the fourth argument with a sealed union that better allows for directly managing states, removes unnecessary arguments such as padding, adds transition builders for custom transitions. Currently still includes AnimatedSize. 110 | 111 | ## [1.0.0-nullsafety.0] - 2021-01-12 112 | 113 | Breaking. Adds a fourth argument of loading to the builder. This removes the value notifier that was difficult or confusing to manage in actual usage in my use cases. The argument isLoading is now a `bool`. This also adds a `disabled` field in order to set that on construction. 114 | 115 | ## [0.1.0-nullsafety.0] - 2021-01-12 116 | 117 | Replaces the bool type of isLoading for a ValueNotifier. 118 | 119 | ## [0.0.1-nullsafety.2] - 2021-01-12 120 | 121 | Adds analysis options and removes unnecessary typedef 122 | 123 | ## [0.0.1-nullsafety.1] - 2021-01-12 124 | 125 | Adds docs to parameters and removes use of unnecessary undescore internal variables. 126 | 127 | ## [0.0.1-nullsafety.0] - 2021-01-11 128 | 129 | Holds basic builder AsyncButtonBuilder. No tests yet. NNBD is supported 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Rex Magana 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async_button_builder 2 | 3 | AsyncButtonBuilder offers a simple way to extend any type of button with an asynchronous aspect. It allows adding loading, disabled, errored and completed states (with fluid animation between each) on top of buttons that perform asynchronous tasks. 4 | 5 | ## Getting Started 6 | 7 | Include the package: 8 | 9 | ```yaml 10 | async_button_builder: 11 | ``` 12 | 13 | Wrap the builder around a button, passing the onPressed and child element to builder instead of the button directly. These two are the only required fields. 14 | 15 | ```dart 16 | AsyncButtonBuilder( 17 | child: Text('Click Me'), 18 | onPressed: () async { 19 | await Future.delayed(Duration(seconds: 1)); 20 | }, 21 | builder: (context, child, callback, _) { 22 | return TextButton( 23 | child: child, 24 | onPressed: callback, 25 | ); 26 | }, 27 | ), 28 | ``` 29 | 30 |

31 | 32 |

33 | 34 | The fourth value in the builder allows you listen to the loading state. This can be used to conditionally style the button. This package depends `freezed` in order to create a sealed union to better handle the possible states. 35 | 36 | > NOTE (Breaking change): As of v3.0.0, error now takes the error and stack trace as arguments. 37 | 38 | ```dart 39 | AsyncButtonBuilder( 40 | child: Text('Click Me'), 41 | loadingWidget: Text('Loading...'), 42 | onPressed: () async { 43 | await Future.delayed(Duration(seconds: 1)); 44 | 45 | // See the examples file for a way to handle timeouts 46 | throw 'yikes'; 47 | }, 48 | builder: (context, child, callback, buttonState) { 49 | final buttonColor = buttonState.when( 50 | idle: () => Colors.yellow[200], 51 | loading: () => Colors.grey, 52 | success: () => Colors.orangeAccent, 53 | error: (err, stack) => Colors.orange, 54 | ); 55 | 56 | return OutlinedButton( 57 | child: child, 58 | onPressed: callback, 59 | style: OutlinedButton.styleFrom( 60 | primary: Colors.black, 61 | backgroundColor: buttonColor, 62 | ), 63 | ); 64 | }, 65 | ), 66 | ``` 67 | 68 |

69 | 70 |

71 | 72 | You can also drive the state of the button yourself using the `buttonState` field: 73 | 74 | ```dart 75 | AsyncButtonBuilder( 76 | buttonState: ButtonState.completing(), 77 | // ... 78 | ), 79 | ``` 80 | 81 | ## Notifications 82 | 83 | As of v3.0.0, you can now wrap a higher level parent to handle notifications that come from buttons. Why not use something like `runZonedGuarded`? Notification bubbling handles not only the error but the state of the button. If you'd like, for example, to trigger a circular spinner in the center of the app notifiying the user that something is happening, you can do so by listening to the `AsyncButtonNotification` and then using the `buttonState` to determine what to do. 84 | 85 | It might also be a good idea to separate the errors that come from button presses and those that are not. An error wants to see why a button press silently failed but might not need to know why a background fetch failed. 86 | 87 | ```dart 88 | MaterialApp( 89 | home: NotificationListener( 90 | onNotification: (notification) { 91 | notification.buttonState.when( 92 | idle: () => // nothing -> you could use a maybeWhen as well 93 | loading: () => // show circular loading widget? 94 | success: () => // show success snackbar? 95 | error: (_, __) => // show error snackbar? 96 | ); 97 | 98 | // Tells the notification to stop bubbling 99 | return true; 100 | }, 101 | // This async button can be nested arbitrarily deep* 102 | child: AsyncButtonBuilder( 103 | duration: duration, 104 | errorDuration: const Duration(milliseconds: 100), 105 | errorWidget: const Text('error'), 106 | onPressed: () async { 107 | throw ArgumentError(); 108 | }, 109 | builder: (context, child, callback, state) { 110 | return TextButton(onPressed: callback, child: child); 111 | }, 112 | child: const Text('click me'), 113 | ), 114 | ), 115 | ) 116 | 117 | // See NotificationListener for more information 118 | ``` 119 | 120 | To disable the notifications, you can pass `false` to `notifications`. 121 | 122 | ## Customization 123 | 124 | `async_button_builder` even works for custom buttons. You can define your own widgets for loading, error, and completion as well as define the transitions between them. This example is a little verbose but shows some of what's possible. 125 | 126 | 127 | ```dart 128 | AsyncButtonBuilder( 129 | child: Padding( 130 | padding: const EdgeInsets.symmetric( 131 | horizontal: 16.0, 132 | vertical: 8.0, 133 | ), 134 | child: Text( 135 | 'Click Me', 136 | style: TextStyle(color: Colors.white), 137 | ), 138 | ), 139 | loadingWidget: Padding( 140 | padding: const EdgeInsets.all(8.0), 141 | child: SizedBox( 142 | height: 16.0, 143 | width: 16.0, 144 | child: CircularProgressIndicator( 145 | valueColor: AlwaysStoppedAnimation(Colors.white), 146 | ), 147 | ), 148 | ), 149 | successWidget: Padding( 150 | padding: const EdgeInsets.all(4.0), 151 | child: Icon( 152 | Icons.check, 153 | color: Colors.purpleAccent, 154 | ), 155 | ), 156 | onPressed: () async { 157 | await Future.delayed(Duration(seconds: 2)); 158 | }, 159 | loadingSwitchInCurve: Curves.bounceInOut, 160 | loadingTransitionBuilder: (child, animation) { 161 | return SlideTransition( 162 | position: Tween( 163 | begin: Offset(0, 1.0), 164 | end: Offset(0, 0), 165 | ).animate(animation), 166 | child: child, 167 | ); 168 | }, 169 | builder: (context, child, callback, state) { 170 | return Material( 171 | color: state.maybeWhen( 172 | success: () => Colors.purple[100], 173 | orElse: () => Colors.blue, 174 | ), 175 | // This prevents the loading indicator showing below the 176 | // button 177 | clipBehavior: Clip.hardEdge, 178 | shape: StadiumBorder(), 179 | child: InkWell( 180 | child: child, 181 | onTap: callback, 182 | ), 183 | ); 184 | }, 185 | ), 186 | ``` 187 | 188 |

189 | 190 |

191 | 192 | Issues and PR's welcome -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | strong-mode: 5 | implicit-casts: false 6 | implicit-dynamic: false 7 | exclude: 8 | # - "**/*.freezed.dart" 9 | 10 | linter: 11 | rules: 12 | # - public_member_api_docs 13 | - sort_child_properties_last 14 | - require_trailing_commas 15 | - parameter_assignments 16 | - only_throw_errors 17 | - always_use_package_imports 18 | - avoid_dynamic_calls 19 | - prefer_void_to_null 20 | - always_declare_return_types 21 | - avoid_classes_with_only_static_members 22 | - unnecessary_lambdas 23 | - unnecessary_const 24 | - avoid_void_async 25 | -------------------------------------------------------------------------------- /coverage/lcov.info: -------------------------------------------------------------------------------- 1 | SF:lib/src/async_button_builder.dart 2 | DA:104,1 3 | DA:138,1 4 | DA:143,1 5 | DA:144,1 6 | DA:152,1 7 | DA:154,3 8 | DA:155,1 9 | DA:158,0 10 | DA:160,0 11 | DA:161,0 12 | DA:162,0 13 | DA:166,0 14 | DA:169,1 15 | DA:171,2 16 | DA:173,1 17 | DA:176,1 18 | DA:178,1 19 | DA:179,2 20 | DA:180,2 21 | DA:181,2 22 | DA:187,2 23 | DA:188,1 24 | DA:190,2 25 | DA:192,2 26 | DA:193,1 27 | DA:195,1 28 | DA:197,2 29 | DA:198,2 30 | DA:205,0 31 | DA:206,0 32 | DA:210,0 33 | DA:217,0 34 | DA:223,1 35 | DA:225,2 36 | DA:226,2 37 | DA:227,2 38 | DA:228,3 39 | DA:229,3 40 | DA:230,3 41 | DA:231,3 42 | DA:233,2 43 | DA:234,3 44 | DA:235,3 45 | DA:236,3 46 | DA:237,3 47 | DA:239,2 48 | DA:240,3 49 | DA:241,3 50 | DA:242,3 51 | DA:243,3 52 | DA:245,2 53 | DA:246,2 54 | DA:247,2 55 | DA:248,2 56 | DA:250,2 57 | DA:251,2 58 | DA:254,2 59 | DA:255,2 60 | DA:258,2 61 | DA:259,2 62 | DA:265,2 63 | DA:270,2 64 | DA:271,1 65 | DA:272,2 66 | DA:273,2 67 | DA:274,2 68 | DA:275,2 69 | DA:276,2 70 | DA:280,2 71 | DA:284,2 72 | DA:285,2 73 | DA:286,1 74 | DA:289,2 75 | DA:290,1 76 | DA:293,1 77 | DA:295,2 78 | DA:296,1 79 | DA:298,1 80 | DA:299,2 81 | DA:300,2 82 | DA:301,1 83 | DA:304,3 84 | DA:306,0 85 | DA:307,0 86 | DA:311,2 87 | DA:312,1 88 | DA:314,1 89 | DA:315,2 90 | DA:316,2 91 | DA:317,1 92 | DA:320,3 93 | DA:322,0 94 | DA:323,0 95 | DA:329,1 96 | DA:331,1 97 | DA:333,1 98 | DA:337,1 99 | DA:338,2 100 | DA:340,1 101 | DA:341,2 102 | DA:343,1 103 | DA:344,2 104 | DA:345,1 105 | LF:103 106 | LH:90 107 | end_of_record 108 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | /pubspec.lock 49 | -------------------------------------------------------------------------------- /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: 63062a64432cce03315d6b5196fda7912866eb37 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.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /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 plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "com.example.example" 38 | minSdkVersion 16 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /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 localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 3 | #include "Generated.xcconfig" 4 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 3 | #include "Generated.xcconfig" 4 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | 4 | DEPENDENCIES: 5 | - Flutter (from `Flutter`) 6 | 7 | EXTERNAL SOURCES: 8 | Flutter: 9 | :path: Flutter 10 | 11 | SPEC CHECKSUMS: 12 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c 13 | 14 | PODFILE CHECKSUM: fc81e398f362bae88bdf55239bd5cf842faad39f 15 | 16 | COCOAPODS: 1.10.1 17 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 052090A8264F7CC7D59BAC4F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 166593E2DDC1580301FBE4EB /* Pods_Runner.framework */; }; 11 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 166593E2DDC1580301FBE4EB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 38 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; 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 | 979288937DB54B7A8E167BFD /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 43 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 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 | ABECF997BE9D843ACE309A51 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 49 | BFE91A80E9CF01FB47297D69 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 052090A8264F7CC7D59BAC4F /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 421781B0B716E5E3F11A65B3 /* Pods */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | ABECF997BE9D843ACE309A51 /* Pods-Runner.debug.xcconfig */, 68 | BFE91A80E9CF01FB47297D69 /* Pods-Runner.release.xcconfig */, 69 | 979288937DB54B7A8E167BFD /* Pods-Runner.profile.xcconfig */, 70 | ); 71 | name = Pods; 72 | path = Pods; 73 | sourceTree = ""; 74 | }; 75 | 9740EEB11CF90186004384FC /* Flutter */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 79 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 80 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 81 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 82 | ); 83 | name = Flutter; 84 | sourceTree = ""; 85 | }; 86 | 97C146E51CF9000F007C117D = { 87 | isa = PBXGroup; 88 | children = ( 89 | 9740EEB11CF90186004384FC /* Flutter */, 90 | 97C146F01CF9000F007C117D /* Runner */, 91 | 97C146EF1CF9000F007C117D /* Products */, 92 | 421781B0B716E5E3F11A65B3 /* Pods */, 93 | C21EF53B5D1A17BF8839457D /* Frameworks */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | 97C146EF1CF9000F007C117D /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 97C146EE1CF9000F007C117D /* Runner.app */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | 97C146F01CF9000F007C117D /* Runner */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 109 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 110 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 111 | 97C147021CF9000F007C117D /* Info.plist */, 112 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 113 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 114 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 115 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 116 | ); 117 | path = Runner; 118 | sourceTree = ""; 119 | }; 120 | C21EF53B5D1A17BF8839457D /* Frameworks */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 166593E2DDC1580301FBE4EB /* Pods_Runner.framework */, 124 | ); 125 | name = Frameworks; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | 97C146ED1CF9000F007C117D /* Runner */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 134 | buildPhases = ( 135 | 0D390D42CD24A841DCDBE0FB /* [CP] Check Pods Manifest.lock */, 136 | 9740EEB61CF901F6004384FC /* Run Script */, 137 | 97C146EA1CF9000F007C117D /* Sources */, 138 | 97C146EB1CF9000F007C117D /* Frameworks */, 139 | 97C146EC1CF9000F007C117D /* Resources */, 140 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 141 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 142 | ); 143 | buildRules = ( 144 | ); 145 | dependencies = ( 146 | ); 147 | name = Runner; 148 | productName = Runner; 149 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 150 | productType = "com.apple.product-type.application"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | 97C146E61CF9000F007C117D /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | LastUpgradeCheck = 1300; 159 | ORGANIZATIONNAME = ""; 160 | TargetAttributes = { 161 | 97C146ED1CF9000F007C117D = { 162 | CreatedOnToolsVersion = 7.3.1; 163 | LastSwiftMigration = 1100; 164 | }; 165 | }; 166 | }; 167 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 168 | compatibilityVersion = "Xcode 9.3"; 169 | developmentRegion = en; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | en, 173 | Base, 174 | ); 175 | mainGroup = 97C146E51CF9000F007C117D; 176 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 177 | projectDirPath = ""; 178 | projectRoot = ""; 179 | targets = ( 180 | 97C146ED1CF9000F007C117D /* Runner */, 181 | ); 182 | }; 183 | /* End PBXProject section */ 184 | 185 | /* Begin PBXResourcesBuildPhase section */ 186 | 97C146EC1CF9000F007C117D /* Resources */ = { 187 | isa = PBXResourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 191 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 192 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 193 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXShellScriptBuildPhase section */ 200 | 0D390D42CD24A841DCDBE0FB /* [CP] Check Pods Manifest.lock */ = { 201 | isa = PBXShellScriptBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | ); 205 | inputFileListPaths = ( 206 | ); 207 | inputPaths = ( 208 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 209 | "${PODS_ROOT}/Manifest.lock", 210 | ); 211 | name = "[CP] Check Pods Manifest.lock"; 212 | outputFileListPaths = ( 213 | ); 214 | outputPaths = ( 215 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | shellPath = /bin/sh; 219 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 220 | showEnvVarsInLog = 0; 221 | }; 222 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 223 | isa = PBXShellScriptBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | ); 227 | inputPaths = ( 228 | ); 229 | name = "Thin Binary"; 230 | outputPaths = ( 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | shellPath = /bin/sh; 234 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 235 | }; 236 | 9740EEB61CF901F6004384FC /* Run Script */ = { 237 | isa = PBXShellScriptBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | ); 241 | inputPaths = ( 242 | ); 243 | name = "Run Script"; 244 | outputPaths = ( 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | shellPath = /bin/sh; 248 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 249 | }; 250 | /* End PBXShellScriptBuildPhase section */ 251 | 252 | /* Begin PBXSourcesBuildPhase section */ 253 | 97C146EA1CF9000F007C117D /* Sources */ = { 254 | isa = PBXSourcesBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 258 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXSourcesBuildPhase section */ 263 | 264 | /* Begin PBXVariantGroup section */ 265 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 266 | isa = PBXVariantGroup; 267 | children = ( 268 | 97C146FB1CF9000F007C117D /* Base */, 269 | ); 270 | name = Main.storyboard; 271 | sourceTree = ""; 272 | }; 273 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 274 | isa = PBXVariantGroup; 275 | children = ( 276 | 97C147001CF9000F007C117D /* Base */, 277 | ); 278 | name = LaunchScreen.storyboard; 279 | sourceTree = ""; 280 | }; 281 | /* End PBXVariantGroup section */ 282 | 283 | /* Begin XCBuildConfiguration section */ 284 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | ALWAYS_SEARCH_USER_PATHS = NO; 288 | CLANG_ANALYZER_NONNULL = YES; 289 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 290 | CLANG_CXX_LIBRARY = "libc++"; 291 | CLANG_ENABLE_MODULES = YES; 292 | CLANG_ENABLE_OBJC_ARC = YES; 293 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 294 | CLANG_WARN_BOOL_CONVERSION = YES; 295 | CLANG_WARN_COMMA = YES; 296 | CLANG_WARN_CONSTANT_CONVERSION = YES; 297 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 298 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 299 | CLANG_WARN_EMPTY_BODY = YES; 300 | CLANG_WARN_ENUM_CONVERSION = YES; 301 | CLANG_WARN_INFINITE_RECURSION = YES; 302 | CLANG_WARN_INT_CONVERSION = YES; 303 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 304 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 305 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 306 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 307 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 308 | CLANG_WARN_STRICT_PROTOTYPES = YES; 309 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 310 | CLANG_WARN_UNREACHABLE_CODE = YES; 311 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 312 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 313 | COPY_PHASE_STRIP = NO; 314 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 315 | ENABLE_NS_ASSERTIONS = NO; 316 | ENABLE_STRICT_OBJC_MSGSEND = YES; 317 | GCC_C_LANGUAGE_STANDARD = gnu99; 318 | GCC_NO_COMMON_BLOCKS = YES; 319 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 320 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 321 | GCC_WARN_UNDECLARED_SELECTOR = YES; 322 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 323 | GCC_WARN_UNUSED_FUNCTION = YES; 324 | GCC_WARN_UNUSED_VARIABLE = YES; 325 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 326 | MTL_ENABLE_DEBUG_INFO = NO; 327 | SDKROOT = iphoneos; 328 | SUPPORTED_PLATFORMS = iphoneos; 329 | TARGETED_DEVICE_FAMILY = "1,2"; 330 | VALIDATE_PRODUCT = YES; 331 | }; 332 | name = Profile; 333 | }; 334 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 335 | isa = XCBuildConfiguration; 336 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 337 | buildSettings = { 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 339 | CLANG_ENABLE_MODULES = YES; 340 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 341 | ENABLE_BITCODE = NO; 342 | INFOPLIST_FILE = Runner/Info.plist; 343 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 344 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 347 | SWIFT_VERSION = 5.0; 348 | VERSIONING_SYSTEM = "apple-generic"; 349 | }; 350 | name = Profile; 351 | }; 352 | 97C147031CF9000F007C117D /* Debug */ = { 353 | isa = XCBuildConfiguration; 354 | buildSettings = { 355 | ALWAYS_SEARCH_USER_PATHS = NO; 356 | CLANG_ANALYZER_NONNULL = YES; 357 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 358 | CLANG_CXX_LIBRARY = "libc++"; 359 | CLANG_ENABLE_MODULES = YES; 360 | CLANG_ENABLE_OBJC_ARC = YES; 361 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 362 | CLANG_WARN_BOOL_CONVERSION = YES; 363 | CLANG_WARN_COMMA = YES; 364 | CLANG_WARN_CONSTANT_CONVERSION = YES; 365 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 367 | CLANG_WARN_EMPTY_BODY = YES; 368 | CLANG_WARN_ENUM_CONVERSION = YES; 369 | CLANG_WARN_INFINITE_RECURSION = YES; 370 | CLANG_WARN_INT_CONVERSION = YES; 371 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 372 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 373 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 374 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 375 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 376 | CLANG_WARN_STRICT_PROTOTYPES = YES; 377 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 378 | CLANG_WARN_UNREACHABLE_CODE = YES; 379 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 380 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 381 | COPY_PHASE_STRIP = NO; 382 | DEBUG_INFORMATION_FORMAT = dwarf; 383 | ENABLE_STRICT_OBJC_MSGSEND = YES; 384 | ENABLE_TESTABILITY = YES; 385 | GCC_C_LANGUAGE_STANDARD = gnu99; 386 | GCC_DYNAMIC_NO_PIC = NO; 387 | GCC_NO_COMMON_BLOCKS = YES; 388 | GCC_OPTIMIZATION_LEVEL = 0; 389 | GCC_PREPROCESSOR_DEFINITIONS = ( 390 | "DEBUG=1", 391 | "$(inherited)", 392 | ); 393 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 394 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 395 | GCC_WARN_UNDECLARED_SELECTOR = YES; 396 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 397 | GCC_WARN_UNUSED_FUNCTION = YES; 398 | GCC_WARN_UNUSED_VARIABLE = YES; 399 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 400 | MTL_ENABLE_DEBUG_INFO = YES; 401 | ONLY_ACTIVE_ARCH = YES; 402 | SDKROOT = iphoneos; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | }; 405 | name = Debug; 406 | }; 407 | 97C147041CF9000F007C117D /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ALWAYS_SEARCH_USER_PATHS = NO; 411 | CLANG_ANALYZER_NONNULL = YES; 412 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 413 | CLANG_CXX_LIBRARY = "libc++"; 414 | CLANG_ENABLE_MODULES = YES; 415 | CLANG_ENABLE_OBJC_ARC = YES; 416 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 417 | CLANG_WARN_BOOL_CONVERSION = YES; 418 | CLANG_WARN_COMMA = YES; 419 | CLANG_WARN_CONSTANT_CONVERSION = YES; 420 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 421 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 422 | CLANG_WARN_EMPTY_BODY = YES; 423 | CLANG_WARN_ENUM_CONVERSION = YES; 424 | CLANG_WARN_INFINITE_RECURSION = YES; 425 | CLANG_WARN_INT_CONVERSION = YES; 426 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 427 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 428 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 429 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 430 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 431 | CLANG_WARN_STRICT_PROTOTYPES = YES; 432 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 433 | CLANG_WARN_UNREACHABLE_CODE = YES; 434 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 435 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 436 | COPY_PHASE_STRIP = NO; 437 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 438 | ENABLE_NS_ASSERTIONS = NO; 439 | ENABLE_STRICT_OBJC_MSGSEND = YES; 440 | GCC_C_LANGUAGE_STANDARD = gnu99; 441 | GCC_NO_COMMON_BLOCKS = YES; 442 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 443 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 444 | GCC_WARN_UNDECLARED_SELECTOR = YES; 445 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 446 | GCC_WARN_UNUSED_FUNCTION = YES; 447 | GCC_WARN_UNUSED_VARIABLE = YES; 448 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 449 | MTL_ENABLE_DEBUG_INFO = NO; 450 | SDKROOT = iphoneos; 451 | SUPPORTED_PLATFORMS = iphoneos; 452 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 453 | TARGETED_DEVICE_FAMILY = "1,2"; 454 | VALIDATE_PRODUCT = YES; 455 | }; 456 | name = Release; 457 | }; 458 | 97C147061CF9000F007C117D /* Debug */ = { 459 | isa = XCBuildConfiguration; 460 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 461 | buildSettings = { 462 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 463 | CLANG_ENABLE_MODULES = YES; 464 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 465 | ENABLE_BITCODE = NO; 466 | INFOPLIST_FILE = Runner/Info.plist; 467 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 468 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 469 | PRODUCT_NAME = "$(TARGET_NAME)"; 470 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 471 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 472 | SWIFT_VERSION = 5.0; 473 | VERSIONING_SYSTEM = "apple-generic"; 474 | }; 475 | name = Debug; 476 | }; 477 | 97C147071CF9000F007C117D /* Release */ = { 478 | isa = XCBuildConfiguration; 479 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 480 | buildSettings = { 481 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 482 | CLANG_ENABLE_MODULES = YES; 483 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 484 | ENABLE_BITCODE = NO; 485 | INFOPLIST_FILE = Runner/Info.plist; 486 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 487 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 488 | PRODUCT_NAME = "$(TARGET_NAME)"; 489 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 490 | SWIFT_VERSION = 5.0; 491 | VERSIONING_SYSTEM = "apple-generic"; 492 | }; 493 | name = Release; 494 | }; 495 | /* End XCBuildConfiguration section */ 496 | 497 | /* Begin XCConfigurationList section */ 498 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 499 | isa = XCConfigurationList; 500 | buildConfigurations = ( 501 | 97C147031CF9000F007C117D /* Debug */, 502 | 97C147041CF9000F007C117D /* Release */, 503 | 249021D3217E4FDB00AE95B9 /* Profile */, 504 | ); 505 | defaultConfigurationIsVisible = 0; 506 | defaultConfigurationName = Release; 507 | }; 508 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 509 | isa = XCConfigurationList; 510 | buildConfigurations = ( 511 | 97C147061CF9000F007C117D /* Debug */, 512 | 97C147071CF9000F007C117D /* Release */, 513 | 249021D4217E4FDB00AE95B9 /* Profile */, 514 | ); 515 | defaultConfigurationIsVisible = 0; 516 | defaultConfigurationName = Release; 517 | }; 518 | /* End XCConfigurationList section */ 519 | }; 520 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 521 | } 522 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/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 | $(DEVELOPMENT_LANGUAGE) 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 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:async_button_builder/async_button_builder.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | void main() => runApp(const MyApp()); 5 | 6 | class MyApp extends StatelessWidget { 7 | const MyApp({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return MaterialApp( 12 | title: 'Flutter Demo', 13 | theme: ThemeData.dark(), 14 | home: const MyHomePage(), 15 | ); 16 | } 17 | } 18 | 19 | class MyHomePage extends StatelessWidget { 20 | const MyHomePage({Key? key}) : super(key: key); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | appBar: AppBar(title: const Text('Async Buttons')), 26 | body: SizedBox.expand( 27 | child: Column( 28 | crossAxisAlignment: CrossAxisAlignment.center, 29 | mainAxisAlignment: MainAxisAlignment.spaceAround, 30 | children: [ 31 | const Divider(), 32 | const Text('Text Button:'), 33 | AsyncButtonBuilder( 34 | child: const Text('Click Me'), 35 | onPressed: () async { 36 | await Future.delayed(const Duration(seconds: 1)); 37 | }, 38 | builder: (context, child, callback, _) { 39 | return TextButton( 40 | onPressed: callback, 41 | child: child, 42 | ); 43 | }, 44 | ), 45 | const Divider(), 46 | const Text('Elevated Button:'), 47 | AsyncButtonBuilder( 48 | child: const Text('Click Me'), 49 | onPressed: () async { 50 | await Future.delayed(const Duration(seconds: 1)); 51 | }, 52 | builder: (context, child, callback, _) { 53 | return ElevatedButton( 54 | onPressed: callback, 55 | child: child, 56 | ); 57 | }, 58 | ), 59 | const Divider(), 60 | const Text('Custom Outlined Button (Error):'), 61 | AsyncButtonBuilder( 62 | loadingWidget: const Text('Loading...'), 63 | onPressed: () async { 64 | await Future.delayed(const Duration(seconds: 1)); 65 | 66 | throw 'yikes'; 67 | 68 | // If you want to add a timeout, use something similar to 69 | // 70 | // try { 71 | // await Future.delayed(Duration(seconds: 1)) 72 | // .timeout(Duration(milliseconds: 500)); 73 | // } on TimeoutException catch (_) { 74 | // // Show a popup or something 75 | // rethrow; 76 | // } on Exception catch (_) { 77 | // // Show a dialog or something 78 | // rethrow; 79 | // } 80 | // 81 | // We rethrow so that async_button_builder can handle the error 82 | // state 83 | }, 84 | builder: (context, child, callback, buttonState) { 85 | final buttonColor = buttonState.when( 86 | idle: () => Colors.yellow[200], 87 | loading: () => Colors.grey, 88 | success: () => Colors.orangeAccent, 89 | error: (_, __) => Colors.orange, 90 | ); 91 | 92 | return OutlinedButton( 93 | onPressed: callback, 94 | style: OutlinedButton.styleFrom( 95 | foregroundColor: Colors.black, 96 | backgroundColor: buttonColor, 97 | ), 98 | child: child, 99 | ); 100 | }, 101 | child: const Text('Click Me'), 102 | ), 103 | const Divider(), 104 | const Text('Custom Material Button:'), 105 | const SizedBox(height: 6.0), 106 | AsyncButtonBuilder( 107 | loadingWidget: const Padding( 108 | padding: EdgeInsets.all(8.0), 109 | child: SizedBox( 110 | height: 16.0, 111 | width: 16.0, 112 | child: CircularProgressIndicator( 113 | valueColor: AlwaysStoppedAnimation(Colors.white), 114 | ), 115 | ), 116 | ), 117 | successWidget: const Padding( 118 | padding: EdgeInsets.all(4.0), 119 | child: Icon( 120 | Icons.check, 121 | color: Colors.purpleAccent, 122 | ), 123 | ), 124 | onPressed: () async { 125 | await Future.delayed(const Duration(seconds: 2)); 126 | }, 127 | loadingSwitchInCurve: Curves.bounceInOut, 128 | loadingTransitionBuilder: (child, animation) { 129 | return SlideTransition( 130 | position: Tween( 131 | begin: const Offset(0, 1.0), 132 | end: const Offset(0, 0), 133 | ).animate(animation), 134 | child: child, 135 | ); 136 | }, 137 | builder: (context, child, callback, state) { 138 | return Material( 139 | color: state.maybeWhen( 140 | success: () => Colors.purple[100], 141 | orElse: () => Colors.blue, 142 | ), 143 | // This prevents the loading indicator showing below the 144 | // button 145 | clipBehavior: Clip.hardEdge, 146 | shape: const StadiumBorder(), 147 | child: InkWell( 148 | onTap: callback, 149 | child: child, 150 | ), 151 | ); 152 | }, 153 | child: const Padding( 154 | padding: EdgeInsets.symmetric( 155 | horizontal: 16.0, 156 | vertical: 8.0, 157 | ), 158 | child: Text( 159 | 'Click Me', 160 | style: TextStyle(color: Colors.white), 161 | ), 162 | ), 163 | ), 164 | const Divider(), 165 | ], 166 | ), 167 | ), 168 | ); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.17.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | cupertino_icons: ^1.0.1 13 | 14 | async_button_builder: 15 | path: ../ 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | flutter_lints: ^2.0.0 21 | 22 | flutter: 23 | uses-material-design: true 24 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | example 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /lib/async_button_builder.dart: -------------------------------------------------------------------------------- 1 | library async_button_builder; 2 | 3 | export 'package:async_button_builder/src/async_button_builder.dart'; 4 | export 'package:async_button_builder/src/async_button_notification.dart'; 5 | export 'package:async_button_builder/src/button_state/button_state.dart'; 6 | -------------------------------------------------------------------------------- /lib/src/async_button_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:async_button_builder/async_button_builder.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | typedef AsyncButtonBuilderCallback = Widget Function( 8 | BuildContext context, 9 | Widget child, 10 | AsyncCallback? callback, 11 | ButtonState buttonState, 12 | ); 13 | 14 | /// A `builder` that wraps a button providing disabled, loading, success and 15 | /// error states while retaining almost full access to the original Button's 16 | /// API. This is useful for any long running operations and helps better 17 | /// improve UX. 18 | /// 19 | /// {@tool dartpad --template=stateful_widget_material} 20 | /// 21 | /// ```dart 22 | /// 23 | /// @override 24 | /// Widget build(BuildContext context) { 25 | /// return AsyncButtonBuilder( 26 | /// child: Text('Click Me'), 27 | /// loadingWidget: Text('Loading...'), 28 | /// onPressed: () async { 29 | /// await Future.delayed(Duration(seconds: 1)); 30 | /// 31 | /// throw 'yikes'; 32 | /// }, 33 | /// builder: (context, child, callback, buttonState) { 34 | /// final buttonColor = buttonState.when( 35 | /// idle: () => Colors.yellow[200], 36 | /// loading: () => Colors.grey, 37 | /// success: () => Colors.orangeAccent, 38 | /// error: () => Colors.orange, 39 | /// ); 40 | /// 41 | /// return OutlinedButton( 42 | /// child: child, 43 | /// onPressed: callback, 44 | /// style: OutlinedButton.styleFrom( 45 | /// primary: Colors.black, 46 | /// backgroundColor: buttonColor, 47 | /// ), 48 | /// ); 49 | /// }, 50 | /// ), 51 | /// } 52 | /// ``` 53 | /// {@end-tool} 54 | /// 55 | class AsyncButtonBuilder extends StatefulWidget { 56 | /// This builder provides the widget's [BuildContext], the variable [child] 57 | /// based on button state as well as the [callback] that should be passed to 58 | /// the button and the current [ButtonState] 59 | final AsyncButtonBuilderCallback builder; 60 | 61 | /// The child of the button. In the case of an [IconButton], this can be a an 62 | /// [Icon]. For a [TextButton], a [Text]. 63 | /// 64 | /// This child will be animated between for the [loadingWidget] or default 65 | /// [CircularProgressIndicator] when the asynchronous [onPressed] is called. 66 | /// The animation will take place over [duration]. 67 | final Widget child; 68 | 69 | /// The animation's duration between [child], [loadingWidget], 70 | /// [successWidget] and [errorWidget]. This same value is used for both the 71 | /// internal [AnimatedSize] and [TransitionBuilder]. 72 | final Duration duration; 73 | 74 | /// The animation's reverse duration between [child], [loadingWidget], 75 | /// [successWidget] and [errorWidget]. This same value is used for both the 76 | /// internal [AnimatedSize] and [TransitionBuilder]. 77 | final Duration reverseDuration; 78 | 79 | /// A callback that runs the async task. This is wrapped in order to begin 80 | /// the button's internal `isLoading` before and after the operation 81 | /// completes. 82 | final AsyncCallback? onPressed; 83 | 84 | /// A callback that runs [buttonState] changes to [ButtonState.success] 85 | final VoidCallback? onSuccess; 86 | 87 | /// A callback that runs [buttonState] changes to [ButtonState.error] 88 | final VoidCallback? onError; 89 | 90 | /// This is used to manually drive the state of the loading button thus 91 | /// initiating the corresponding animation and showing the correct button 92 | /// child. 93 | final ButtonState buttonState; 94 | 95 | /// This is used to manually drive the disabled state of the button. 96 | final bool disabled; 97 | 98 | /// The widget replaces the [child] when the button is in the loading state. 99 | /// If this is null the default widget is: 100 | /// 101 | /// SizedBox( 102 | /// height: 16.0, 103 | /// width: 16.0, 104 | /// child: CircularProgressIndicator(), 105 | /// ) 106 | final Widget? loadingWidget; 107 | 108 | /// The widget used to replace the [child] when the button is in a success 109 | /// state. If this is null the default widget is: 110 | /// 111 | /// Icon( 112 | /// Icons.check, 113 | /// color: Theme.of(context).accentColor, 114 | /// ); 115 | final Widget? successWidget; 116 | 117 | /// The widget used to replace the [child] when the button is in a error 118 | /// state. If this is null the default widget is: 119 | /// 120 | /// Icon( 121 | /// Icons.error, 122 | /// color: Theme.of(context).errorColor, 123 | /// ) 124 | final Widget? errorWidget; 125 | 126 | /// Whether to show the [successWidget] on success. 127 | final bool showSuccess; 128 | 129 | /// Whether to show the [errorWidget] on error. 130 | final bool showError; 131 | 132 | /// Optional [EdgeInsets] that will wrap around the [errorWidget]. This is a 133 | /// convenience field that can be replaced by defining your own [errorWidget] 134 | /// and wrapping it in a [Padding]. 135 | final EdgeInsets? errorPadding; 136 | 137 | /// Optional [EdgeInsets] that will wrap around the [successWidget]. This is a 138 | /// convenience field that can be replaced by defining your own 139 | /// [successWidget] and wrapping it in a [Padding]. 140 | final EdgeInsets? successPadding; 141 | 142 | /// Defines a custom transition when animating between any state and `idle` 143 | final AnimatedSwitcherTransitionBuilder idleTransitionBuilder; 144 | 145 | /// Defines a custom transition when animating between any state and `loading` 146 | final AnimatedSwitcherTransitionBuilder loadingTransitionBuilder; 147 | 148 | /// Defines a custom transition when animating between any state and `success` 149 | final AnimatedSwitcherTransitionBuilder successTransitionBuilder; 150 | 151 | /// Defines a custom transition when animating between any state and `error` 152 | final AnimatedSwitcherTransitionBuilder errorTransitionBuilder; 153 | 154 | /// The amount of idle time the [successWidget] shows 155 | final Duration successDuration; 156 | 157 | /// The amount of idle time the [errorWidget] shows 158 | final Duration errorDuration; 159 | 160 | /// Defines a curve for the custom transition. This used in in an 161 | /// [AnimatedSwitcher] and only takes effect when animating to `idle` 162 | final Curve idleSwitchInCurve; 163 | 164 | /// Defines a curve for the custom transition. This used in in an 165 | /// [AnimatedSwitcher] and only takes effect when animating to `loading` 166 | final Curve loadingSwitchInCurve; 167 | 168 | /// Defines a curve for the custom transition. This used in in an 169 | /// [AnimatedSwitcher] and only takes effect when animating to `success` 170 | final Curve successSwitchInCurve; 171 | 172 | /// Defines a curve for the custom transition. This used in in an 173 | /// [AnimatedSwitcher] and only takes effect when animating to `error` 174 | final Curve errorSwitchInCurve; 175 | 176 | /// Defines a curve for the custom transition. This used in in an 177 | /// [AnimatedSwitcher] and only takes effect when animating out of `idle` 178 | final Curve idleSwitchOutCurve; 179 | 180 | /// Defines a curve for the custom transition. This used in in an 181 | /// [AnimatedSwitcher] and only takes effect when animating out of `loading` 182 | final Curve loadingSwitchOutCurve; 183 | 184 | /// Defines a curve for the custom transition. This used in in an 185 | /// [AnimatedSwitcher] and only takes effect when animating out of `success` 186 | final Curve successSwitchOutCurve; 187 | 188 | /// Defines a curve for the custom transition. This used in in an 189 | /// [AnimatedSwitcher] and only takes effect when animating out of `error` 190 | final Curve errorSwitchOutCurve; 191 | 192 | /// Defines a curve for the internal [AnimatedSize] 193 | final Curve sizeCurve; 194 | 195 | /// Defines the [Clip] for the internal [AnimatedSize] 196 | final Clip sizeClipBehavior; 197 | 198 | /// Defines the [Alignment] for the internal [AnimatedSize] 199 | final Alignment sizeAlignment; 200 | 201 | /// Whether to animate the [Size] of the widget implicitly. 202 | final bool animateSize; 203 | 204 | /// Whether we should bubble up [AsyncButtonNotification]s up the widget tree. 205 | /// This is useful if you want to listen to the state of the button from a 206 | /// parent widget. 207 | final bool notifications; 208 | 209 | const AsyncButtonBuilder({ 210 | Key? key, 211 | required this.child, 212 | required this.onPressed, 213 | required this.builder, 214 | this.onSuccess, 215 | this.onError, 216 | this.loadingWidget, 217 | this.successWidget, 218 | this.errorWidget, 219 | this.showSuccess = true, 220 | this.showError = true, 221 | this.errorPadding, 222 | this.successPadding, 223 | this.buttonState = const ButtonState.idle(), 224 | this.duration = const Duration(milliseconds: 250), 225 | this.reverseDuration = const Duration(milliseconds: 250), 226 | this.disabled = false, 227 | this.successDuration = const Duration(seconds: 1), 228 | this.errorDuration = const Duration(seconds: 1), 229 | this.loadingTransitionBuilder = AnimatedSwitcher.defaultTransitionBuilder, 230 | this.idleTransitionBuilder = AnimatedSwitcher.defaultTransitionBuilder, 231 | this.successTransitionBuilder = AnimatedSwitcher.defaultTransitionBuilder, 232 | this.errorTransitionBuilder = AnimatedSwitcher.defaultTransitionBuilder, 233 | this.idleSwitchInCurve = Curves.linear, 234 | this.loadingSwitchInCurve = Curves.linear, 235 | this.successSwitchInCurve = Curves.linear, 236 | this.errorSwitchInCurve = Curves.linear, 237 | this.idleSwitchOutCurve = Curves.linear, 238 | this.loadingSwitchOutCurve = Curves.linear, 239 | this.successSwitchOutCurve = Curves.linear, 240 | this.errorSwitchOutCurve = Curves.linear, 241 | this.sizeCurve = Curves.linear, 242 | this.sizeClipBehavior = Clip.hardEdge, 243 | this.sizeAlignment = Alignment.center, 244 | this.animateSize = true, 245 | this.notifications = true, 246 | }) : super(key: key); 247 | 248 | @override 249 | State createState() => _AsyncButtonBuilderState(); 250 | } 251 | 252 | class _AsyncButtonBuilderState extends State 253 | with SingleTickerProviderStateMixin { 254 | late ButtonState buttonState; 255 | Timer? timer; 256 | 257 | @override 258 | void initState() { 259 | buttonState = widget.buttonState; 260 | super.initState(); 261 | } 262 | 263 | @override 264 | void didUpdateWidget(covariant AsyncButtonBuilder oldWidget) { 265 | if (widget.buttonState != oldWidget.buttonState) { 266 | setState(() { 267 | buttonState = widget.buttonState; 268 | }); 269 | } 270 | super.didUpdateWidget(oldWidget); 271 | } 272 | 273 | @override 274 | void dispose() { 275 | timer?.cancel(); 276 | super.dispose(); 277 | } 278 | 279 | @override 280 | Widget build(BuildContext context) { 281 | final theme = Theme.of(context); 282 | final onPressed = widget.onPressed; 283 | final widgetKey = widget.key; 284 | final loadingWidget = widget.loadingWidget ?? 285 | const SizedBox( 286 | height: 16.0, 287 | width: 16.0, 288 | child: CircularProgressIndicator(), 289 | ); 290 | var successWidget = widget.successWidget ?? 291 | Icon( 292 | Icons.check, 293 | color: theme.colorScheme.secondary, 294 | ); 295 | var errorWidget = widget.errorWidget ?? 296 | Icon( 297 | Icons.error, 298 | color: theme.colorScheme.error, 299 | ); 300 | final successPadding = widget.successPadding; 301 | final errorPadding = widget.errorPadding; 302 | 303 | // This is necessary in the case of nested async button builders. 304 | // We cannot have the same __idle__, __loading__, etc. keys as they might 305 | // conflict with one another. 306 | String parentKeyValue = ''; 307 | 308 | if (widgetKey != null && widgetKey is ValueKey) { 309 | parentKeyValue = widgetKey.value.toString(); 310 | } 311 | 312 | if (successPadding != null) { 313 | successWidget = Padding( 314 | padding: successPadding, 315 | child: successWidget, 316 | ); 317 | } 318 | 319 | if (errorPadding != null) { 320 | errorWidget = Padding( 321 | padding: errorPadding, 322 | child: errorWidget, 323 | ); 324 | } 325 | 326 | final switcher = AnimatedSwitcher( 327 | // TODO: This duration is same as size's duration. That's okay right? 328 | duration: widget.duration, 329 | reverseDuration: widget.reverseDuration, 330 | switchInCurve: buttonState.when( 331 | idle: () => widget.idleSwitchInCurve, 332 | loading: () => widget.loadingSwitchInCurve, 333 | success: () => widget.successSwitchInCurve, 334 | error: (_, __) => widget.errorSwitchInCurve, 335 | ), 336 | switchOutCurve: buttonState.when( 337 | idle: () => widget.idleSwitchOutCurve, 338 | loading: () => widget.loadingSwitchOutCurve, 339 | success: () => widget.successSwitchOutCurve, 340 | error: (_, __) => widget.errorSwitchOutCurve, 341 | ), 342 | transitionBuilder: buttonState.when( 343 | idle: () => widget.idleTransitionBuilder, 344 | loading: () => widget.loadingTransitionBuilder, 345 | success: () => widget.successTransitionBuilder, 346 | error: (_, __) => widget.errorTransitionBuilder, 347 | ), 348 | child: buttonState.when( 349 | idle: () => KeyedSubtree( 350 | key: ValueKey('__idle__$parentKeyValue'), 351 | child: widget.child, 352 | ), 353 | loading: () => KeyedSubtree( 354 | key: ValueKey('__loading__$parentKeyValue'), 355 | child: loadingWidget, 356 | ), 357 | success: () => KeyedSubtree( 358 | key: ValueKey('__success__$parentKeyValue'), 359 | child: successWidget, 360 | ), 361 | error: (_, __) => KeyedSubtree( 362 | key: ValueKey('__error__$parentKeyValue'), 363 | child: errorWidget, 364 | ), 365 | ), 366 | ); 367 | 368 | return widget.builder( 369 | context, 370 | // TODO: I really just wanted an AnimatedSwitcher and the default 371 | // transitionBuilder to be a SizedTransition but it was impossible 372 | // to figure out how to reproduce the exact behaviour of AnimatedSize 373 | widget.animateSize 374 | ? AnimatedSize( 375 | duration: widget.duration, 376 | reverseDuration: widget.reverseDuration, 377 | alignment: widget.sizeAlignment, 378 | clipBehavior: widget.sizeClipBehavior, 379 | curve: widget.sizeCurve, 380 | child: switcher, 381 | ) 382 | : switcher, 383 | widget.disabled 384 | ? null 385 | : onPressed == null 386 | ? null 387 | : buttonState.maybeWhen( 388 | idle: () => () { 389 | final completer = Completer(); 390 | 391 | // I might not want to set buttonState if we're being 392 | // driven by widget.buttonState... 393 | setState(() { 394 | buttonState = const ButtonState.loading(); 395 | }); 396 | 397 | if (widget.notifications) { 398 | const AsyncButtonNotification( 399 | buttonState: ButtonState.loading(), 400 | ).dispatch(context); 401 | } 402 | 403 | timer?.cancel(); 404 | 405 | onPressed.call().then((_) { 406 | completer.complete(); 407 | 408 | if (mounted) { 409 | if (widget.showSuccess) { 410 | setState(() { 411 | buttonState = const ButtonState.success(); 412 | }); 413 | 414 | if (widget.notifications) { 415 | const AsyncButtonNotification( 416 | buttonState: ButtonState.success(), 417 | ).dispatch(context); 418 | } 419 | 420 | setTimer(widget.successDuration, widget.onSuccess); 421 | } else { 422 | setState(() { 423 | buttonState = const ButtonState.idle(); 424 | }); 425 | 426 | if (widget.notifications) { 427 | const AsyncButtonNotification( 428 | buttonState: ButtonState.idle(), 429 | ).dispatch(context); 430 | } 431 | } 432 | } 433 | }).catchError((Object error, StackTrace stackTrace) { 434 | completer.completeError(error, stackTrace); 435 | 436 | if (mounted) { 437 | if (widget.showError) { 438 | setState(() { 439 | buttonState = ButtonState.error(error, stackTrace); 440 | }); 441 | 442 | if (widget.notifications) { 443 | AsyncButtonNotification( 444 | buttonState: ButtonState.error(error, stackTrace), 445 | ).dispatch(context); 446 | } 447 | 448 | setTimer(widget.errorDuration, widget.onError); 449 | } else { 450 | setState(() { 451 | buttonState = const ButtonState.idle(); 452 | }); 453 | 454 | if (widget.notifications) { 455 | const AsyncButtonNotification( 456 | buttonState: ButtonState.idle(), 457 | ).dispatch(context); 458 | } 459 | } 460 | } 461 | }); 462 | 463 | return completer.future; 464 | }, 465 | orElse: () => null, 466 | ), 467 | buttonState, 468 | ); 469 | } 470 | 471 | void setTimer(Duration duration, [VoidCallback? then]) { 472 | timer = Timer( 473 | duration, 474 | () { 475 | timer?.cancel(); 476 | 477 | then?.call(); 478 | 479 | if (mounted) { 480 | setState(() { 481 | buttonState = const ButtonState.idle(); 482 | }); 483 | 484 | if (widget.notifications) { 485 | const AsyncButtonNotification( 486 | buttonState: ButtonState.idle(), 487 | ).dispatch(context); 488 | } 489 | } 490 | }, 491 | ); 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /lib/src/async_button_notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:async_button_builder/async_button_builder.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AsyncButtonNotification extends Notification { 5 | const AsyncButtonNotification({ 6 | required this.buttonState, 7 | }); 8 | 9 | final ButtonState buttonState; 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/button_state/button_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'button_state.freezed.dart'; 4 | 5 | /// This union class represents the state of the button in either a [Idling], 6 | /// [Loading], [Success] or [Error] state. This can be considered a enum with extra 7 | /// utilities for ease of use. 8 | /// 9 | /// {@tool snippet} 10 | /// 11 | /// ```dart 12 | /// final buttonColor = buttonState.when( 13 | /// idle: () => Colors.pink, 14 | /// loading: () => Colors.blue, 15 | /// success: () => Colors.green, 16 | /// error: () => Colors.red, 17 | /// ); 18 | /// ``` 19 | /// {@end-tool} 20 | /// 21 | /// You can also disregard other states and handle only those you'd like using 22 | /// the `.maybeWhen` syntax. 23 | /// 24 | /// /// {@tool snippet} 25 | /// 26 | /// ```dart 27 | /// final buttonColor = buttonState.maybeWhen( 28 | /// idle: () => Colors.pink, 29 | /// orElse: () => Colors.red, 30 | /// ); 31 | /// ``` 32 | /// {@end-tool} 33 | @freezed 34 | class ButtonState with _$ButtonState { 35 | const factory ButtonState.idle() = Idle; 36 | const factory ButtonState.loading() = Loading; 37 | const factory ButtonState.success() = Success; 38 | const factory ButtonState.error(Object error, [StackTrace? stackTrace]) = 39 | Error; 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/button_state/button_state.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: type=lint 4 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark 5 | 6 | part of 'button_state.dart'; 7 | 8 | // ************************************************************************** 9 | // FreezedGenerator 10 | // ************************************************************************** 11 | 12 | T _$identity(T value) => value; 13 | 14 | final _privateConstructorUsedError = UnsupportedError( 15 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 16 | 17 | /// @nodoc 18 | mixin _$ButtonState { 19 | @optionalTypeArgs 20 | TResult when({ 21 | required TResult Function() idle, 22 | required TResult Function() loading, 23 | required TResult Function() success, 24 | required TResult Function(Object error, StackTrace? stackTrace) error, 25 | }) => 26 | throw _privateConstructorUsedError; 27 | @optionalTypeArgs 28 | TResult? whenOrNull({ 29 | TResult? Function()? idle, 30 | TResult? Function()? loading, 31 | TResult? Function()? success, 32 | TResult? Function(Object error, StackTrace? stackTrace)? error, 33 | }) => 34 | throw _privateConstructorUsedError; 35 | @optionalTypeArgs 36 | TResult maybeWhen({ 37 | TResult Function()? idle, 38 | TResult Function()? loading, 39 | TResult Function()? success, 40 | TResult Function(Object error, StackTrace? stackTrace)? error, 41 | required TResult orElse(), 42 | }) => 43 | throw _privateConstructorUsedError; 44 | @optionalTypeArgs 45 | TResult map({ 46 | required TResult Function(Idle value) idle, 47 | required TResult Function(Loading value) loading, 48 | required TResult Function(Success value) success, 49 | required TResult Function(Error value) error, 50 | }) => 51 | throw _privateConstructorUsedError; 52 | @optionalTypeArgs 53 | TResult? mapOrNull({ 54 | TResult? Function(Idle value)? idle, 55 | TResult? Function(Loading value)? loading, 56 | TResult? Function(Success value)? success, 57 | TResult? Function(Error value)? error, 58 | }) => 59 | throw _privateConstructorUsedError; 60 | @optionalTypeArgs 61 | TResult maybeMap({ 62 | TResult Function(Idle value)? idle, 63 | TResult Function(Loading value)? loading, 64 | TResult Function(Success value)? success, 65 | TResult Function(Error value)? error, 66 | required TResult orElse(), 67 | }) => 68 | throw _privateConstructorUsedError; 69 | } 70 | 71 | /// @nodoc 72 | abstract class $ButtonStateCopyWith<$Res> { 73 | factory $ButtonStateCopyWith( 74 | ButtonState value, $Res Function(ButtonState) then) = 75 | _$ButtonStateCopyWithImpl<$Res, ButtonState>; 76 | } 77 | 78 | /// @nodoc 79 | class _$ButtonStateCopyWithImpl<$Res, $Val extends ButtonState> 80 | implements $ButtonStateCopyWith<$Res> { 81 | _$ButtonStateCopyWithImpl(this._value, this._then); 82 | 83 | // ignore: unused_field 84 | final $Val _value; 85 | // ignore: unused_field 86 | final $Res Function($Val) _then; 87 | } 88 | 89 | /// @nodoc 90 | abstract class _$$IdleCopyWith<$Res> { 91 | factory _$$IdleCopyWith(_$Idle value, $Res Function(_$Idle) then) = 92 | __$$IdleCopyWithImpl<$Res>; 93 | } 94 | 95 | /// @nodoc 96 | class __$$IdleCopyWithImpl<$Res> extends _$ButtonStateCopyWithImpl<$Res, _$Idle> 97 | implements _$$IdleCopyWith<$Res> { 98 | __$$IdleCopyWithImpl(_$Idle _value, $Res Function(_$Idle) _then) 99 | : super(_value, _then); 100 | } 101 | 102 | /// @nodoc 103 | 104 | class _$Idle implements Idle { 105 | const _$Idle(); 106 | 107 | @override 108 | String toString() { 109 | return 'ButtonState.idle()'; 110 | } 111 | 112 | @override 113 | bool operator ==(dynamic other) { 114 | return identical(this, other) || 115 | (other.runtimeType == runtimeType && other is _$Idle); 116 | } 117 | 118 | @override 119 | int get hashCode => runtimeType.hashCode; 120 | 121 | @override 122 | @optionalTypeArgs 123 | TResult when({ 124 | required TResult Function() idle, 125 | required TResult Function() loading, 126 | required TResult Function() success, 127 | required TResult Function(Object error, StackTrace? stackTrace) error, 128 | }) { 129 | return idle(); 130 | } 131 | 132 | @override 133 | @optionalTypeArgs 134 | TResult? whenOrNull({ 135 | TResult? Function()? idle, 136 | TResult? Function()? loading, 137 | TResult? Function()? success, 138 | TResult? Function(Object error, StackTrace? stackTrace)? error, 139 | }) { 140 | return idle?.call(); 141 | } 142 | 143 | @override 144 | @optionalTypeArgs 145 | TResult maybeWhen({ 146 | TResult Function()? idle, 147 | TResult Function()? loading, 148 | TResult Function()? success, 149 | TResult Function(Object error, StackTrace? stackTrace)? error, 150 | required TResult orElse(), 151 | }) { 152 | if (idle != null) { 153 | return idle(); 154 | } 155 | return orElse(); 156 | } 157 | 158 | @override 159 | @optionalTypeArgs 160 | TResult map({ 161 | required TResult Function(Idle value) idle, 162 | required TResult Function(Loading value) loading, 163 | required TResult Function(Success value) success, 164 | required TResult Function(Error value) error, 165 | }) { 166 | return idle(this); 167 | } 168 | 169 | @override 170 | @optionalTypeArgs 171 | TResult? mapOrNull({ 172 | TResult? Function(Idle value)? idle, 173 | TResult? Function(Loading value)? loading, 174 | TResult? Function(Success value)? success, 175 | TResult? Function(Error value)? error, 176 | }) { 177 | return idle?.call(this); 178 | } 179 | 180 | @override 181 | @optionalTypeArgs 182 | TResult maybeMap({ 183 | TResult Function(Idle value)? idle, 184 | TResult Function(Loading value)? loading, 185 | TResult Function(Success value)? success, 186 | TResult Function(Error value)? error, 187 | required TResult orElse(), 188 | }) { 189 | if (idle != null) { 190 | return idle(this); 191 | } 192 | return orElse(); 193 | } 194 | } 195 | 196 | abstract class Idle implements ButtonState { 197 | const factory Idle() = _$Idle; 198 | } 199 | 200 | /// @nodoc 201 | abstract class _$$LoadingCopyWith<$Res> { 202 | factory _$$LoadingCopyWith(_$Loading value, $Res Function(_$Loading) then) = 203 | __$$LoadingCopyWithImpl<$Res>; 204 | } 205 | 206 | /// @nodoc 207 | class __$$LoadingCopyWithImpl<$Res> 208 | extends _$ButtonStateCopyWithImpl<$Res, _$Loading> 209 | implements _$$LoadingCopyWith<$Res> { 210 | __$$LoadingCopyWithImpl(_$Loading _value, $Res Function(_$Loading) _then) 211 | : super(_value, _then); 212 | } 213 | 214 | /// @nodoc 215 | 216 | class _$Loading implements Loading { 217 | const _$Loading(); 218 | 219 | @override 220 | String toString() { 221 | return 'ButtonState.loading()'; 222 | } 223 | 224 | @override 225 | bool operator ==(dynamic other) { 226 | return identical(this, other) || 227 | (other.runtimeType == runtimeType && other is _$Loading); 228 | } 229 | 230 | @override 231 | int get hashCode => runtimeType.hashCode; 232 | 233 | @override 234 | @optionalTypeArgs 235 | TResult when({ 236 | required TResult Function() idle, 237 | required TResult Function() loading, 238 | required TResult Function() success, 239 | required TResult Function(Object error, StackTrace? stackTrace) error, 240 | }) { 241 | return loading(); 242 | } 243 | 244 | @override 245 | @optionalTypeArgs 246 | TResult? whenOrNull({ 247 | TResult? Function()? idle, 248 | TResult? Function()? loading, 249 | TResult? Function()? success, 250 | TResult? Function(Object error, StackTrace? stackTrace)? error, 251 | }) { 252 | return loading?.call(); 253 | } 254 | 255 | @override 256 | @optionalTypeArgs 257 | TResult maybeWhen({ 258 | TResult Function()? idle, 259 | TResult Function()? loading, 260 | TResult Function()? success, 261 | TResult Function(Object error, StackTrace? stackTrace)? error, 262 | required TResult orElse(), 263 | }) { 264 | if (loading != null) { 265 | return loading(); 266 | } 267 | return orElse(); 268 | } 269 | 270 | @override 271 | @optionalTypeArgs 272 | TResult map({ 273 | required TResult Function(Idle value) idle, 274 | required TResult Function(Loading value) loading, 275 | required TResult Function(Success value) success, 276 | required TResult Function(Error value) error, 277 | }) { 278 | return loading(this); 279 | } 280 | 281 | @override 282 | @optionalTypeArgs 283 | TResult? mapOrNull({ 284 | TResult? Function(Idle value)? idle, 285 | TResult? Function(Loading value)? loading, 286 | TResult? Function(Success value)? success, 287 | TResult? Function(Error value)? error, 288 | }) { 289 | return loading?.call(this); 290 | } 291 | 292 | @override 293 | @optionalTypeArgs 294 | TResult maybeMap({ 295 | TResult Function(Idle value)? idle, 296 | TResult Function(Loading value)? loading, 297 | TResult Function(Success value)? success, 298 | TResult Function(Error value)? error, 299 | required TResult orElse(), 300 | }) { 301 | if (loading != null) { 302 | return loading(this); 303 | } 304 | return orElse(); 305 | } 306 | } 307 | 308 | abstract class Loading implements ButtonState { 309 | const factory Loading() = _$Loading; 310 | } 311 | 312 | /// @nodoc 313 | abstract class _$$SuccessCopyWith<$Res> { 314 | factory _$$SuccessCopyWith(_$Success value, $Res Function(_$Success) then) = 315 | __$$SuccessCopyWithImpl<$Res>; 316 | } 317 | 318 | /// @nodoc 319 | class __$$SuccessCopyWithImpl<$Res> 320 | extends _$ButtonStateCopyWithImpl<$Res, _$Success> 321 | implements _$$SuccessCopyWith<$Res> { 322 | __$$SuccessCopyWithImpl(_$Success _value, $Res Function(_$Success) _then) 323 | : super(_value, _then); 324 | } 325 | 326 | /// @nodoc 327 | 328 | class _$Success implements Success { 329 | const _$Success(); 330 | 331 | @override 332 | String toString() { 333 | return 'ButtonState.success()'; 334 | } 335 | 336 | @override 337 | bool operator ==(dynamic other) { 338 | return identical(this, other) || 339 | (other.runtimeType == runtimeType && other is _$Success); 340 | } 341 | 342 | @override 343 | int get hashCode => runtimeType.hashCode; 344 | 345 | @override 346 | @optionalTypeArgs 347 | TResult when({ 348 | required TResult Function() idle, 349 | required TResult Function() loading, 350 | required TResult Function() success, 351 | required TResult Function(Object error, StackTrace? stackTrace) error, 352 | }) { 353 | return success(); 354 | } 355 | 356 | @override 357 | @optionalTypeArgs 358 | TResult? whenOrNull({ 359 | TResult? Function()? idle, 360 | TResult? Function()? loading, 361 | TResult? Function()? success, 362 | TResult? Function(Object error, StackTrace? stackTrace)? error, 363 | }) { 364 | return success?.call(); 365 | } 366 | 367 | @override 368 | @optionalTypeArgs 369 | TResult maybeWhen({ 370 | TResult Function()? idle, 371 | TResult Function()? loading, 372 | TResult Function()? success, 373 | TResult Function(Object error, StackTrace? stackTrace)? error, 374 | required TResult orElse(), 375 | }) { 376 | if (success != null) { 377 | return success(); 378 | } 379 | return orElse(); 380 | } 381 | 382 | @override 383 | @optionalTypeArgs 384 | TResult map({ 385 | required TResult Function(Idle value) idle, 386 | required TResult Function(Loading value) loading, 387 | required TResult Function(Success value) success, 388 | required TResult Function(Error value) error, 389 | }) { 390 | return success(this); 391 | } 392 | 393 | @override 394 | @optionalTypeArgs 395 | TResult? mapOrNull({ 396 | TResult? Function(Idle value)? idle, 397 | TResult? Function(Loading value)? loading, 398 | TResult? Function(Success value)? success, 399 | TResult? Function(Error value)? error, 400 | }) { 401 | return success?.call(this); 402 | } 403 | 404 | @override 405 | @optionalTypeArgs 406 | TResult maybeMap({ 407 | TResult Function(Idle value)? idle, 408 | TResult Function(Loading value)? loading, 409 | TResult Function(Success value)? success, 410 | TResult Function(Error value)? error, 411 | required TResult orElse(), 412 | }) { 413 | if (success != null) { 414 | return success(this); 415 | } 416 | return orElse(); 417 | } 418 | } 419 | 420 | abstract class Success implements ButtonState { 421 | const factory Success() = _$Success; 422 | } 423 | 424 | /// @nodoc 425 | abstract class _$$ErrorCopyWith<$Res> { 426 | factory _$$ErrorCopyWith(_$Error value, $Res Function(_$Error) then) = 427 | __$$ErrorCopyWithImpl<$Res>; 428 | @useResult 429 | $Res call({Object error, StackTrace? stackTrace}); 430 | } 431 | 432 | /// @nodoc 433 | class __$$ErrorCopyWithImpl<$Res> 434 | extends _$ButtonStateCopyWithImpl<$Res, _$Error> 435 | implements _$$ErrorCopyWith<$Res> { 436 | __$$ErrorCopyWithImpl(_$Error _value, $Res Function(_$Error) _then) 437 | : super(_value, _then); 438 | 439 | @pragma('vm:prefer-inline') 440 | @override 441 | $Res call({ 442 | Object? error = null, 443 | Object? stackTrace = freezed, 444 | }) { 445 | return _then(_$Error( 446 | null == error ? _value.error : error, 447 | freezed == stackTrace 448 | ? _value.stackTrace 449 | : stackTrace // ignore: cast_nullable_to_non_nullable 450 | as StackTrace?, 451 | )); 452 | } 453 | } 454 | 455 | /// @nodoc 456 | 457 | class _$Error implements Error { 458 | const _$Error(this.error, [this.stackTrace]); 459 | 460 | @override 461 | final Object error; 462 | @override 463 | final StackTrace? stackTrace; 464 | 465 | @override 466 | String toString() { 467 | return 'ButtonState.error(error: $error, stackTrace: $stackTrace)'; 468 | } 469 | 470 | @override 471 | bool operator ==(dynamic other) { 472 | return identical(this, other) || 473 | (other.runtimeType == runtimeType && 474 | other is _$Error && 475 | const DeepCollectionEquality().equals(other.error, error) && 476 | (identical(other.stackTrace, stackTrace) || 477 | other.stackTrace == stackTrace)); 478 | } 479 | 480 | @override 481 | int get hashCode => Object.hash( 482 | runtimeType, const DeepCollectionEquality().hash(error), stackTrace); 483 | 484 | @JsonKey(ignore: true) 485 | @override 486 | @pragma('vm:prefer-inline') 487 | _$$ErrorCopyWith<_$Error> get copyWith => 488 | __$$ErrorCopyWithImpl<_$Error>(this, _$identity); 489 | 490 | @override 491 | @optionalTypeArgs 492 | TResult when({ 493 | required TResult Function() idle, 494 | required TResult Function() loading, 495 | required TResult Function() success, 496 | required TResult Function(Object error, StackTrace? stackTrace) error, 497 | }) { 498 | return error(this.error, stackTrace); 499 | } 500 | 501 | @override 502 | @optionalTypeArgs 503 | TResult? whenOrNull({ 504 | TResult? Function()? idle, 505 | TResult? Function()? loading, 506 | TResult? Function()? success, 507 | TResult? Function(Object error, StackTrace? stackTrace)? error, 508 | }) { 509 | return error?.call(this.error, stackTrace); 510 | } 511 | 512 | @override 513 | @optionalTypeArgs 514 | TResult maybeWhen({ 515 | TResult Function()? idle, 516 | TResult Function()? loading, 517 | TResult Function()? success, 518 | TResult Function(Object error, StackTrace? stackTrace)? error, 519 | required TResult orElse(), 520 | }) { 521 | if (error != null) { 522 | return error(this.error, stackTrace); 523 | } 524 | return orElse(); 525 | } 526 | 527 | @override 528 | @optionalTypeArgs 529 | TResult map({ 530 | required TResult Function(Idle value) idle, 531 | required TResult Function(Loading value) loading, 532 | required TResult Function(Success value) success, 533 | required TResult Function(Error value) error, 534 | }) { 535 | return error(this); 536 | } 537 | 538 | @override 539 | @optionalTypeArgs 540 | TResult? mapOrNull({ 541 | TResult? Function(Idle value)? idle, 542 | TResult? Function(Loading value)? loading, 543 | TResult? Function(Success value)? success, 544 | TResult? Function(Error value)? error, 545 | }) { 546 | return error?.call(this); 547 | } 548 | 549 | @override 550 | @optionalTypeArgs 551 | TResult maybeMap({ 552 | TResult Function(Idle value)? idle, 553 | TResult Function(Loading value)? loading, 554 | TResult Function(Success value)? success, 555 | TResult Function(Error value)? error, 556 | required TResult orElse(), 557 | }) { 558 | if (error != null) { 559 | return error(this); 560 | } 561 | return orElse(); 562 | } 563 | } 564 | 565 | abstract class Error implements ButtonState { 566 | const factory Error(final Object error, [final StackTrace? stackTrace]) = 567 | _$Error; 568 | 569 | Object get error; 570 | StackTrace? get stackTrace; 571 | @JsonKey(ignore: true) 572 | _$$ErrorCopyWith<_$Error> get copyWith => throw _privateConstructorUsedError; 573 | } 574 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: async_button_builder 2 | description: A builder to wrap around buttons that handles loading, disabled, 3 | error and succes states 4 | version: 3.0.0+1 5 | homepage: https://github.com/Nolence/async_button/tree/main/packages/async_button_builder 6 | 7 | environment: 8 | sdk: ">=2.17.0 <3.0.0" 9 | flutter: ">=1.17.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | freezed_annotation: ^2.2.0 15 | 16 | dev_dependencies: 17 | build_runner: ^2.3.3 18 | flutter_lints: ^2.0.1 19 | flutter_test: 20 | sdk: flutter 21 | freezed: ^2.3.2 22 | 23 | flutter: null 24 | -------------------------------------------------------------------------------- /screenshots/ezgif-7-4088c909ba83.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/screenshots/ezgif-7-4088c909ba83.gif -------------------------------------------------------------------------------- /screenshots/ezgif-7-61c436edaec2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/screenshots/ezgif-7-61c436edaec2.gif -------------------------------------------------------------------------------- /screenshots/ezgif-7-a971c6afaabf.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/screenshots/ezgif-7-a971c6afaabf.gif -------------------------------------------------------------------------------- /screenshots/ezgif-7-b620d3def232.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stargazing-dino/async_button_builder/447772036d62634090175a709807124308c7da94/screenshots/ezgif-7-b620d3def232.gif -------------------------------------------------------------------------------- /test/async_button_builder_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:async_button_builder/async_button_builder.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | void main() { 6 | testWidgets('displays child text', (WidgetTester tester) async { 7 | final textButton = MaterialApp( 8 | home: AsyncButtonBuilder( 9 | onPressed: () async { 10 | await Future.delayed(const Duration(seconds: 1)); 11 | }, 12 | builder: (context, child, callback, state) { 13 | return TextButton(onPressed: callback, child: child); 14 | }, 15 | child: const Text('click me'), 16 | ), 17 | ); 18 | 19 | await tester.pumpWidget(textButton); 20 | 21 | expect(find.text('click me'), findsOneWidget); 22 | }); 23 | 24 | testWidgets('shows loading widget', (WidgetTester tester) async { 25 | const duration = Duration(milliseconds: 250); 26 | final textButton = MaterialApp( 27 | home: AsyncButtonBuilder( 28 | duration: duration, 29 | loadingWidget: const Text('loading'), 30 | onPressed: () async { 31 | await Future.delayed(const Duration(seconds: 1)); 32 | }, 33 | builder: (context, child, callback, state) { 34 | return TextButton(onPressed: callback, child: child); 35 | }, 36 | child: const Text('click me'), 37 | ), 38 | ); 39 | 40 | await tester.pumpWidget(textButton); 41 | 42 | await tester.tap(find.byType(TextButton)); 43 | 44 | // 1/10 of a second later, loading should be showing 45 | await tester.pump(const Duration(milliseconds: 100)); 46 | 47 | expect(find.text('loading'), findsOneWidget); 48 | 49 | // Let the widget continue to settle otherwise I won't dispose timers 50 | // correctly. TODO: Explain why .900 is the magic number 51 | await tester.pumpAndSettle(const Duration(milliseconds: 900)); 52 | }); 53 | 54 | testWidgets('shows error widget', (WidgetTester tester) async { 55 | const duration = Duration(milliseconds: 250); 56 | final textButton = MaterialApp( 57 | home: AsyncButtonBuilder( 58 | duration: duration, 59 | errorWidget: const Text('error'), 60 | onPressed: () async { 61 | throw ArgumentError(); 62 | }, 63 | builder: (context, child, callback, state) { 64 | return TextButton(onPressed: callback, child: child); 65 | }, 66 | child: const Text('click me'), 67 | ), 68 | ); 69 | 70 | await tester.pumpWidget(textButton); 71 | final button = 72 | find.byType(TextButton).evaluate().first.widget as TextButton; 73 | 74 | expect( 75 | () => button.onPressed!.call(), 76 | throwsA(isA()), 77 | ); 78 | 79 | await tester.pump(const Duration(milliseconds: 200)); 80 | 81 | expect(find.text('error'), findsOneWidget); 82 | }); 83 | 84 | testWidgets('error notification bubbles up', (WidgetTester tester) async { 85 | const duration = Duration(milliseconds: 250); 86 | var idleCount = 0; 87 | var loadingCount = 0; 88 | var successCount = 0; 89 | var errorCount = 0; 90 | 91 | final textButton = MaterialApp( 92 | home: NotificationListener( 93 | onNotification: (notification) { 94 | notification.buttonState.when( 95 | idle: () => idleCount += 1, 96 | loading: () => loadingCount += 1, 97 | success: () => successCount += 1, 98 | error: (_, __) => errorCount += 1, 99 | ); 100 | 101 | return true; 102 | }, 103 | child: AsyncButtonBuilder( 104 | duration: duration, 105 | errorDuration: const Duration(milliseconds: 100), 106 | errorWidget: const Text('error'), 107 | onPressed: () async { 108 | throw ArgumentError(); 109 | }, 110 | builder: (context, child, callback, state) { 111 | return TextButton(onPressed: callback, child: child); 112 | }, 113 | child: const Text('click me'), 114 | ), 115 | ), 116 | ); 117 | 118 | await tester.pumpWidget(textButton); 119 | final button = 120 | find.byType(TextButton).evaluate().first.widget as TextButton; 121 | 122 | expect( 123 | () => button.onPressed!.call(), 124 | throwsA(isA()), 125 | ); 126 | 127 | await tester.pump(const Duration(milliseconds: 200)); 128 | 129 | expect(errorCount, 1); 130 | expect(idleCount, 1); 131 | expect(loadingCount, 1); 132 | expect(successCount, 0); 133 | }); 134 | 135 | testWidgets('Returns to child widget', (WidgetTester tester) async { 136 | const duration = Duration(milliseconds: 250); 137 | final textButton = MaterialApp( 138 | home: AsyncButtonBuilder( 139 | duration: duration, 140 | loadingWidget: const Text('loading'), 141 | onPressed: () async { 142 | await Future.delayed(const Duration(seconds: 1)); 143 | }, 144 | builder: (context, child, callback, state) { 145 | return TextButton(onPressed: callback, child: child); 146 | }, 147 | child: const Text('click me'), 148 | ), 149 | ); 150 | 151 | await tester.pumpWidget(textButton); 152 | 153 | await tester.tap(find.byType(TextButton)); 154 | 155 | await tester.pump(const Duration(milliseconds: 1000)); 156 | 157 | expect(find.text('loading'), findsNothing); 158 | 159 | expect(find.text('click me'), findsOneWidget); 160 | }); 161 | 162 | // TODO: Make it work on dropdown buttons 163 | // AsyncButtonBuilder( 164 | // child: Icon(Icons.arrow_upward), 165 | // onPressed: (newValue) async { 166 | // final oldValue = dropdownValue; 167 | 168 | // setState(() { 169 | // dropdownValue = newValue; 170 | // }); 171 | 172 | // await Future.delayed(Duration(seconds: 1)); 173 | 174 | // try { 175 | // if (Random().nextBool()) { 176 | // throw 'yikes'; 177 | // } 178 | // } catch (error) { 179 | // setState(() { 180 | // dropdownValue = oldValue; 181 | // }); 182 | // } 183 | // }, 184 | // builder: (context, child, callback, _) { 185 | // return DropdownButton( 186 | // // icon: SizedBox( 187 | // // height: 16.0, 188 | // // width: 16.0, 189 | // // child: CircularProgressIndicator(), 190 | // // ), 191 | // onChanged: callback, 192 | // items: ['one', 'two', 'three'] 193 | // .map((value) => DropdownMenuItem( 194 | // child: Text(value), 195 | // value: value, 196 | // )) 197 | // .toList(), 198 | // value: dropdownValue, 199 | // ); 200 | // }, 201 | // ), 202 | } 203 | --------------------------------------------------------------------------------