├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── stale.yml └── workflows │ └── main.yml ├── .gitignore ├── .pubignore ├── API.md ├── API_zh.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_zh.md ├── analysis_options.yaml ├── doc └── gif │ ├── attached.gif │ ├── custom_animation.gif │ ├── custom_widget.gif │ ├── loading.gif │ ├── notification.gif │ └── text.gif ├── example ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── all.dart │ ├── attached_toast │ │ └── attached_toast.dart │ ├── custom │ │ ├── custom_animation.dart │ │ └── custom_widget.dart │ ├── loading │ │ ├── custom_loading.dart │ │ └── loading.dart │ ├── main.dart │ ├── notification │ │ ├── custom_notification.dart │ │ ├── notification.dart │ │ └── simple_notification.dart │ └── text │ │ ├── custom_text.dart │ │ └── text.dart ├── pubspec.yaml └── web │ └── index.html ├── lib ├── bot_toast.dart └── src │ ├── basis.dart │ ├── bot_toast_init.dart │ ├── bot_toast_manager.dart │ ├── keyboard_safe_area.dart │ ├── keyboard_visibility.dart │ ├── nil.dart │ ├── option.dart │ ├── toast.dart │ ├── toast_navigator_observer.dart │ └── toast_widget │ ├── animation.dart │ ├── attached.dart │ ├── loading.dart │ ├── notification.dart │ ├── text.dart │ └── toast_widget.dart ├── pubspec.yaml └── test └── widget_test.dart /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Version (please complete the following information):** 27 | - Flutter Version: [e.g. v1.5.0] 28 | - OS: [e.g. iOS/Android] 29 | - BotToast Version : [e.g. 0.3.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 8 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: "waiting for customer response" 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | 4 | on: 5 | push: 6 | paths: 7 | - '**.dart' 8 | - '**.yml' 9 | pull_request: 10 | paths: 11 | - '**.dart' 12 | - '**.yml' 13 | release: 14 | paths: 15 | - '**.dart' 16 | - '**.yml' 17 | types: [published] 18 | 19 | jobs: 20 | build: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v1 26 | - uses: actions/setup-java@v1 27 | with: 28 | java-version: '12.x' 29 | 30 | - name: Setup Flutter 31 | uses: subosito/flutter-action@v1 32 | with: 33 | channel: 'stable' 34 | 35 | - name: Flutter unit test 36 | run: flutter test --coverage --coverage-path=lcov.info 37 | 38 | - name: Report CodeCov 39 | uses: codecov/codecov-action@v1.0.2 40 | with: 41 | token: ${{secrets.CODECOV_TOKEN}} 42 | file: lcov.info 43 | flags: unittests 44 | 45 | # build-web: 46 | 47 | # runs-on: ubuntu-latest 48 | 49 | # steps: 50 | # - uses: actions/checkout@v1 51 | 52 | # - name: Setup flutter 53 | # run: | 54 | # git clone https://github.com/flutter/flutter.git -b stable --depth 1 55 | # ./flutter/bin/flutter doctor 56 | # ./flutter/bin/flutter --version 57 | 58 | # - name: Enable web 59 | # run: ./flutter/bin/flutter config --enable-web 60 | 61 | # - name: Build web 62 | # run: cd example && ../flutter/bin/flutter build web && cd .. 63 | 64 | # - name: Deploy web To GH-Page 65 | # if: github.event_name=='release' && github.event.action=='published' 66 | # uses: peaceiris/actions-gh-pages@v2.4.0 67 | # env: 68 | # ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} 69 | # PUBLISH_BRANCH: gh-pages 70 | # PUBLISH_DIR: ./example/build/web/ 71 | -------------------------------------------------------------------------------- /.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 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | **/ios/Flutter/flutter_export_environment.sh 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | .metadata 73 | pubspec.lock 74 | upload.bat 75 | upload.sh 76 | -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | /doc 2 | /example/build 3 | /example/android 4 | /example/ios 5 | /example/web 6 | /build -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [4.1.3] 2 | * fix: Modify Assertion Logic 3 | 4 | ## [4.1.2] 5 | * fix: Fix the problem of nil judgment error 6 | 7 | ## [4.1.1] 8 | * fix: Delete unused packages 9 | * fix: Using bodyMedium instead of bodyText2 10 | 11 | ## [4.1.0] 12 | * feat: Support global configuration 13 | * docs: Add English API documentation and English comments 14 | 15 | ## [4.0.4] 16 | * feat: Added margin param to showNotification 17 | 18 | ## [4.0.3] 19 | * feat: Add safe area flag for custom options 20 | 21 | ## [4.0.2] 22 | * fix: Support flutter 3.x 23 | 24 | ## [4.0.1] 25 | * fix: #107 26 | 27 | ## [4.0.0+1] 28 | * docs: Update CHANGELOG.md 29 | 30 | ## [4.0.0] 31 | * refactor: Migrate to sound null safety 32 | 33 | ## [3.0.5] 34 | * fix: cancel the use of `nullOk` attribute 35 | 36 | * feat: optimize `toast` display sequence logic 37 | 38 | * feat: add options for users to style titles 39 | 40 | ## [3.0.4] 41 | * fix:  The bug that `ProxyDispose.disposeCallback` callback cannot be triggered 42 | 43 | ## [3.0.3] 44 | * feat: add `enableKeyboardSafeArea` option 45 | 46 | ## [3.0.2] 47 | * fix bug: #73 48 | 49 | ## [3.0.1] 50 | * Add two parameters: `backgroundColor` and `borderRadius` to `showSimpleNotification` and `showNotification` methods 51 | 52 | ## [3.0.0] 53 | * Reimplemented the underlying initialization logic, the code is simpler and more general, and no longer depends on `Navigator` 54 | 55 | * Modify the initialization method 56 | 57 | ## [2.4.1] 58 | * bug fix 59 | 60 | ## [2.4.0] 61 | * feat: Support to intercept click back button event 62 | 63 | ## [2.3.1] 64 | * fix: bug [#43](https://github.com/MMMzq/bot_toast/issues/43) 65 | 66 | ## [2.3.0] 67 | * feat: add onClose callback 68 | 69 | ## [2.2.1] 70 | * bug fix: Use `safeRun` method to ensure normal running of `rearrange` 71 | 72 | ## [2.2.0] 73 | * 重构底层的实现方式以规避一些隐晦的bug 74 | 75 | * Refactor the underlying implementation to avoid some hidden bugs 76 | 77 | ## [2.1.1] 78 | * showSimpleNotification和showNotification方法添加`onTap`,`onLongPress`参数 79 | 80 | * showSimpleNotification and showNotification methods add `onTap`, `onLongPress` parameters 81 | 82 | ## [2.1.0] 83 | * bug fix: see [#11](https://github.com/MMMzq/bot_toast/issues/11) 84 | 85 | * 移除`BotToastInit`的`key`参数 86 | 87 | * `BotToast.init`方法变为私有方法不再公开 88 | 89 | * 重构了初始化的方式 90 | 91 | * Remove the `key` parameter of `BotToastInit` 92 | 93 | * `BotToast.init` method becomes private and no longer public 94 | 95 | * Refactored the way to initialize 96 | 97 | 98 | ## [2.0.0+2] 99 | * Update document 100 | 101 | ## [2.0.0+1] 102 | * Delete useless print 103 | 104 | ## [2.0.0] 105 | * 修改初始化的方式,现在更为通用 106 | 107 | * 支持自定义Toast的**动画**和持续时间😉 108 | 109 | * `showEnhancedWidget`方法的`closeFunc`参数其含义已经发生了变化了,现在是等待`closeFunc`函数执行完毕才移除Toast 110 | 111 | * 添加`showAnimationWidget`方法,可以使用此方法来高度自定义一个有动画的Toast🤩 112 | 113 | * 移除`reInit`方法(2.0版本不再需要),以及`PreferDirection.Below`和`PreferDirection.Upside`这两个已经被废除的枚举 114 | 115 | * `WrapWidget`类型方法参数发生变化(破坏性的),但是如果你没有直接使用`showEnhancedWidget`方式的话,这个修改对1.x版本的代码其实是无影响的,如果有使用请看这里进行兼容[1.x版本升级到2.x版本](README_zh.md#1x版本升级到2x版本) 116 | 117 | * `showXxxNotification` 添加`dismissDirections`参数:表示能进行滑动关闭的方向 118 | 119 | * `showXxxText`,`showXxxLoading`,`showXxxNotification`等方法添加了`align`参数:表示ToastContent区域在MainContent区域的对齐,可用于自定义Toast放置的位置 120 | 121 | * 修复了`showAttachedWidget`方法的一些bug 122 | 123 | * 支持flutter web(注意不能确保其稳定性) 124 | 125 | * 更新example的样式,并添加了更多demo 126 | 127 |
128 | 129 | * Modify the way of initialization, now more general 130 | 131 | * Support for custom Toast **animation** and **animation duration**😉 132 | 133 | * The meaning of the `closeFunc` parameter of the `showEnhancedWidget` method has changed. Now it is wait for the `closeFunc` function to complete before removing Toast. 134 | 135 | * Add the `showAnimationWidget` method, you can use this method to highly customize an animated Toast🤩 136 | 137 | * Remove the `reInit` method and the two deprecated enumerations `PreferDirection.Below` and `PreferDirection.Upside` 138 | 139 | * `WrapWidget` type method parameter changed (not compatible with 1.x version),see [1.x version upgrade 2.x version ](README.md#1x-version-upgrade-to-2x-version) 140 | 141 | * `showXxxNotification` Add `dismissDirections` parameter 142 | 143 | * `showXxxText`, `showXxxLoading`, `showXxxNotification` method adds `align` parameter 144 | 145 | * Fixed some bugs in the `showAttachedWidget` method 146 | 147 | * Support flutter web (note that it cannot ensure its stability) 148 | 149 | * Updated the style of example and added more demos 150 | 151 | ## [1.1.1] 152 | * Bug fix: 153 | 154 | The targetContext of the showAttachedWidget method causes a positioning error when it is inside the ScrollView. 155 | 156 | showAttachedWidget方法的targetContext在ScrollView里面时导致定位出错 157 | 158 | ## [1.1.0] 159 | * 主要对showAttachedWidget方法进行了增强,现在支持更多方向,定位更准确了。 160 | 161 | * `PreferDirection.Below`和`PreferDirection.Upside`被废弃了,可以改用表达更清晰的topCenter,和bottomCenter来代替,且效果完全一致。这两个枚举将会在下个大版本被删除! 162 | 163 | * `showAttachedWidget`的`preferDirection` 只是期望的方向,实际的位置可能因为空间不足而遭到调整 164 | 165 | * 实际调整顺序可以拿`topLeft`来进行说明,如果上方空间不足则调整为`bottomLeft`,然后左边空间不足的话就再判断右边的空间是否充足,充足的话结果为`bottomRight`,不充足最终结果为`bottomCenter` 166 | 167 | * 对于`preferDirection=xxxCenter`的情况,其交叉轴的偏移将会忽略 168 | 169 | ## [1.0.3] 170 | 171 | * 修复当pop所有Route再push Route会出现Bug 172 | * 加固了当项目使用MaterialApp.navigatorKey,能使用reInit重新初始化 173 | 174 | ## [1.0.2] 175 | 176 | * 放宽init重复初始化检查的程度 177 | * 替换scheduleFrame方法为ensureVisualUpdate 178 | 179 | ## [1.0.1] 180 | 181 | * Toast关闭后,清理向BotToastNavigatorObserver注册的函数 182 | 183 | 184 | ## [1.0.0+2] 185 | 186 | * 更新文档及在线例子 187 | 188 | 189 | ## [1.0.0+1] 190 | 191 | * 更新文档及在线例子 192 | 193 | ## [1.0.0] 194 | 195 | * 1.支持弹出Notification Toast 196 | * 2.支持某个Widget 显示 197 | * 3.对已有的功能进行重构,增强各个方法 198 | 199 | ## [0.0.2] 200 | 201 | * 添加Loading Toast 202 | 203 | ## [0.0.1] 204 | 205 | * 只是显示Widget到屏幕 206 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 MMMzq 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BotToast 🤖 2 | ========= 3 | A really easy to use flutter toast library! 4 | 5 | [![](https://img.shields.io/pub/v/bot_toast.svg?label=bot_toast&logo=https%3A%2F%2Fpub.flutter-io.cn%2Fpackages%2Fbot_toast)](https://pub.dev/packages/bot_toast) 6 | 7 | ### Language: English | [中文简体](README_zh.md) 8 | 9 | * [Overview](#Overview) 10 | * [Online Demo](#Online-Demo) 11 | * [Example](#Example) 12 | * [Renderings](#Renderings) 13 | * [Getting started](#Getting-started) 14 | * [3.0 version](#30-version) 15 | * [Documentation](#Documentation) 16 | 17 | ### Overview 18 | 19 | - In the true sense of Toast, you can call it whenever you need it, without any restrictions! 20 | 21 | - Feature-rich, support for displaying notifications, text, loading, attachments, etc. Toast 22 | 23 | - Support for popping up various custom Toasts, or you can pop up any Widget as long as it meets the requirements of the flutter code. 24 | 25 | - API is simple and easy to use 26 | 27 | - Pure flutter implementation 28 | 29 | - Supports global configuration 30 | 31 | 32 | ### Online Demo 33 | 34 | **[Online demo](https://mmmzq.github.io/bot_toast/#/)** (Web effects may be biased, the actual effect is subject to the mobile phone) 35 | 36 | ### Example 37 | **[Sample project](example)** 38 | 39 | ### Renderings 40 | 41 | Notification|Attached|CustomAnimation 42 | --------|-------|-------- 43 | ![Notification](doc/gif/notification.gif)|![Attached](doc/gif/attached.gif)|![CustomAnimation](doc/gif/custom_animation.gif) 44 | 45 | Loading|Text|CustomWidget 46 | --------|-------|---------- 47 | ![Loading](doc/gif/loading.gif)|![Text](doc/gif/text.gif)|![CustomWidget](doc/gif/custom_widget.gif) 48 | 49 | ### Getting started 50 | 51 | #### 1. Add dependencies into you project pubspec.yaml file 52 | ``` yaml 53 | dependencies: 54 | bot_toast: ^4.1.0 #null safety 55 | ``` 56 | 57 | #### 2. Import BotToast Lib 58 | ``` dart 59 | import 'package:bot_toast/bot_toast.dart'; 60 | ``` 61 | 62 | #### 3. Initialization BotToast 63 | ``` dart 64 | MaterialApp( 65 | title: 'BotToast Demo', 66 | builder: BotToastInit(), //1. call BotToastInit 67 | navigatorObservers: [BotToastNavigatorObserver()], //2. registered route observer 68 | home: XxxxPage(), 69 | ) 70 | ``` 71 | or 72 | ``` dart 73 | //Warning: Don't arbitrarily adjust the position of calling the BotToastInit function 74 | final botToastBuilder = BotToastInit(); //1. call BotToastInit 75 | MaterialApp( 76 | title: 'BotToast Demo', 77 | builder: (context, child) { 78 | child = myBuilder(context,child); //do something 79 | child = botToastBuilder(context,child); 80 | return child; 81 | }, 82 | navigatorObservers: [BotToastNavigatorObserver()], //2. registered route observer 83 | home: XxxxPage(), 84 | ) 85 | ``` 86 | 87 | #### 4. Use BotToast 88 | ``` dart 89 | var cancel = BotToast.showText(text:"xxxx"); //popup a text toast; 90 | ... 91 | cancel(); //close 92 | ``` 93 | 94 | ```dart 95 | var cancel = BotToast.showSimpleNotification(title: "init"); // popup a notification toast; 96 | ... 97 | cancel(); //close 98 | ``` 99 | 100 | ```dart 101 | var cancel = BotToast.showLoading(); //popup a loading toast 102 | ... 103 | cancel(); //close 104 | ``` 105 | 106 | ```dart 107 | //popup a attachments toast 108 | var cancel = BotToast.showAttachedWidget( 109 | attachedBuilder: (_) => Card( 110 | child: Padding( 111 | padding: const EdgeInsets.all(8.0), 112 | child: Icon( 113 | Icons.favorite, 114 | color: Colors.redAccent, 115 | ), 116 | ), 117 | ), 118 | duration: Duration(seconds: 2), 119 | target: Offset(520, 520)); 120 | ... 121 | cancel(); //close 122 | ``` 123 | 124 | ```dart 125 | //custom api 126 | var cancel = BotToast.showCustomNotification(...) 127 | var cancel = BotToast.showCustomText(...) 128 | var cancel = BotToast.showCustomLoading(...) 129 | var cancel = BotToast.showAnimationWidget(...) 130 | ... 131 | cancel(); //close 132 | ``` 133 | 134 | #### 5. Modify Global Configuration 135 | 136 | ``` dart 137 | /// For example: Globally change the animation duration for standard notifications to 1 second. 138 | BotToast.defaultOption.notification.animationDuration = const Duration(seconds: 1); 139 | 140 | /// For more default options, refer to the following configurations: 141 | /// [BotToast.defaultOption.simpleNotification] corresponds to the default values of [showSimpleNotification]. 142 | /// [BotToast.defaultOption.notification] corresponds to the default values of [showNotification]. 143 | /// [BotToast.defaultOption.customNotification] corresponds to the default values of [showCustomNotification]. 144 | /// [BotToast.defaultOption.text] corresponds to the default values of [showText]. 145 | /// [BotToast.defaultOption.customText] corresponds to the default values of [showCustomText]. 146 | /// [BotToast.defaultOption.loading] corresponds to the default values of [showLoading]. 147 | /// [BotToast.defaultOption.customLoading] corresponds to the default values of [showCustomLoading]. 148 | /// [BotToast.defaultOption.attached] corresponds to the default values of [showAttachedWidget]. 149 | /// [BotToast.defaultOption.animation] corresponds to the default values of [showAnimationWidget]. 150 | /// [BotToast.defaultOption.enhanced] corresponds to the default values of [showEnhancedWidget]. 151 | ``` 152 | 153 | ### 3.0 version 154 | 155 | #### Major changes: 156 | 157 | - Reimplemented the underlying initialization logic, the code is simpler and more general, and no longer depends on `Navigator` 158 | 159 | - Modify the initialization method 160 | 161 | #### 2.x version upgrade to 3.x version 162 | change: 163 | ``` dart 164 | //2.x.x version initialization method 165 | BotToastInit( 166 | child:MaterialApp( 167 | title: 'BotToast Demo', 168 | navigatorObservers: [BotToastNavigatorObserver()], 169 | home: XxxxPage(), 170 | ) 171 | ); 172 | ``` 173 | to: 174 | ``` dart 175 | //3.x.x version initialization method 176 | MaterialApp( 177 | title: 'BotToast Demo', 178 | builder: BotToastInit(), 179 | navigatorObservers: [BotToastNavigatorObserver()], 180 | home: XxxxPage(), 181 | ) 182 | ``` 183 | 184 | 185 |
186 | 187 | 188 | ### Documentation 189 | [API Documentation](API.md) 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | BotToast 🤖 2 | ========= 3 | 一个真正意义上的flutter Toast库! 4 | 5 | [![](https://img.shields.io/pub/v/bot_toast.svg?label=bot_toast&logo=https%3A%2F%2Fpub.flutter-io.cn%2Fpackages%2Fbot_toast)](https://pub.flutter-io.cn/packages/bot_toast) 6 | 7 | ### Language: [English](README.md) | 中文简体 8 | 9 | * [概述](#概述) 10 | * [在线Demo](#在线demo) 11 | * [示例项目](#示例项目) 12 | * [效果图](#效果图) 13 | * [快速使用](#快速使用) 14 | * [3.0版本](#30版本) 15 | * [注意事项](#注意事项) 16 | * [主要Api文档](#主要Api文档) 17 | 18 |
19 | 20 | ### 概述 21 | 22 | - 真正意义上的Toast,可以在任何你需要的时候调用,不会有任何限制! 23 | 24 | - 功能丰富,支持显示通知,文本,加载,附属等类型Toast 25 | 26 | - 支持弹出各种自定义Toast,或者说你可以弹出任何Widget,只要它符合flutter代码的要求即可 27 | 28 | - Api简单易用 29 | 30 | - 纯flutter实现 31 | 32 | - 支持全局配置 33 | 34 | 35 | 36 | ### 在线demo 37 | 38 | **[在线例子](https://mmmzq.github.io/bot_toast/#/)** (Web效果可能有偏差,真实效果请以手机端为准,第一次加载可能会很久) 39 | 40 | ### 示例项目 41 | **[sample project](example)** 42 | 43 | ### 效果图 44 | 45 | Notification|Attached|CustomAnimation 46 | --------|-------|-------- 47 | ![Notification](doc/gif/notification.gif)|![Attached](doc/gif/attached.gif)|![CustomAnimation](doc/gif/custom_animation.gif) 48 | 49 | Loading|Text|CustomWidget 50 | --------|-------|---------- 51 | ![Loading](doc/gif/loading.gif)|![Text](doc/gif/text.gif)|![CustomWidget](doc/gif/custom_widget.gif) 52 | 53 | ### 快速使用 54 | 55 | #### 1. pubspec.yaml文件里添加依赖 56 | ``` yaml 57 | dependencies: 58 | bot_toast: ^4.1.0 #null safety 59 | ``` 60 | 61 | #### 2. 导入BotToast库 62 | ``` dart 63 | import 'package:bot_toast/bot_toast.dart'; 64 | ``` 65 | 66 | #### 3. 初始化BotToast 67 | 68 | ``` dart 69 | MaterialApp( 70 | title: 'BotToast Demo', 71 | builder: BotToastInit(), //1.调用BotToastInit 72 | navigatorObservers: [BotToastNavigatorObserver()], //2.注册路由观察者 73 | home: XxxxPage(), 74 | ) 75 | ``` 76 | or 77 | ``` dart 78 | //警告:不要随意调整调用BotToastInit函数的位置 79 | final botToastBuilder = BotToastInit(); //1.调用BotToastInit 80 | MaterialApp( 81 | title: 'BotToast Demo', 82 | builder: (context, child) { 83 | child = myBuilder(context,child); //do something 84 | child = botToastBuilder(context,child); 85 | return child; 86 | }, 87 | navigatorObservers: [BotToastNavigatorObserver()], //2.注册路由观察者 88 | home: XxxxPage(), 89 | ) 90 | ``` 91 | 92 | #### 4. 使用BotToast 93 | ``` dart 94 | var cancel = BotToast.showText(text:"xxxx"); //弹出一个文本框; 95 | ... 96 | cancel(); //关闭 97 | ``` 98 | 99 | ```dart 100 | var cancel = BotToast.showSimpleNotification(title: "init"); //弹出简单通知Toast 101 | ... 102 | cancel(); //关闭 103 | ``` 104 | 105 | ```dart 106 | var cancel = BotToast.showLoading(); //弹出一个加载动画 107 | ... 108 | cancel(); //关闭 109 | ``` 110 | 111 | ```dart 112 | //弹出一个定位Toast 113 | var cancel = BotToast.showAttachedWidget( 114 | attachedBuilder: (_) => Card( 115 | child: Padding( 116 | padding: const EdgeInsets.all(8.0), 117 | child: Icon( 118 | Icons.favorite, 119 | color: Colors.redAccent, 120 | ), 121 | ), 122 | ), 123 | duration: Duration(seconds: 2), 124 | target: Offset(520, 520)); 125 | ... 126 | cancel(); //关闭 127 | ``` 128 | 129 | ```dart 130 | //custom api 131 | var cancel = BotToast.showCustomNotification(...) 132 | var cancel = BotToast.showCustomText(...) 133 | var cancel = BotToast.showCustomLoading(...) 134 | var cancel = BotToast.showAnimationWidget(...) 135 | ... 136 | cancel(); //关闭 137 | ``` 138 | 139 |
140 | 141 | #### 5. 修改全局配置 142 | 143 | ``` dart 144 | ///例如:全局修改标准通知的动画时间为 1 秒。 145 | BotToast.defaultOption.notification.animationDuration=const Duration(seconds: 1); 146 | 147 | ///更多默认选项参考下述配置 148 | ///[BotToast.defaultOption.simpleNotification] 对应 [showSimpleNotification] 的默认值 149 | ///[BotToast.defaultOption.notification] 对应 [showNotification] 的默认值 150 | ///[BotToast.defaultOption.customNotification] 对应 [showCustomNotification] 的默认值 151 | ///[BotToast.defaultOption.text] 对应 [showText] 的默认值 152 | ///[BotToast.defaultOption.customText] 对应 [showCustomText] 的默认值 153 | ///[BotToast.defaultOption.loading] 对应 [showLoading] 的默认值 154 | ///[BotToast.defaultOption.customLoading] 对应 [showCustomLoading] 的默认值 155 | ///[BotToast.defaultOption.attached] 对应 [showAttachedWidget] 的默认值 156 | ///[BotToast.defaultOption.animation] 对应 [showAnimationWidget] 的默认值 157 | ///[BotToast.defaultOption.enhanced] 对应 [showEnhancedWidget] 的默认值 158 | ``` 159 | 160 | ### 3.0版本 161 | 162 | #### 主要改动: 163 | 164 | - 重新实现了底层的初始化逻辑,代码更简单,通用,并且不再依赖`Navigator` 165 | 166 | - 初始化的方式改变(破坏性的) 167 | 168 | #### 2.x版本升级到3.x版本 169 | 170 | 将`BotToastInit`使用的位置: 171 | ``` dart 172 | //2.x.x版本的初始化方式 173 | BotToastInit( 174 | child:MaterialApp( 175 | title: 'BotToast Demo', 176 | navigatorObservers: [BotToastNavigatorObserver()], 177 | home: XxxxPage(), 178 | ) 179 | ); 180 | ``` 181 | 182 | 改为: 183 | ``` dart 184 | //3.x.x版本的初始化方式 185 | MaterialApp( 186 | title: 'BotToast Demo', 187 | builder: BotToastInit(), //BotToastInit移动到此处 188 | navigatorObservers: [BotToastNavigatorObserver()], 189 | home: XxxxPage(), 190 | ) 191 | ``` 192 | 193 |
194 | 195 | 196 | ### 注意事项 197 | 198 | - 如果你项目有多个`Navigator`,请将该`BotToastNavigatorObserver`添加到`Navigator.observers`,否则将会影响一些功能 199 | 200 | - 使用`ToastBuilder`方法生成widget时,请确保生成的Widget背景不会吸收点击事件,例如`Scaffold`,`Material`都会默认占满整个父空间, 201 | 并且会吸收事件(就算透明也是这种情况) 202 | 203 | 204 |
205 | 206 | ### 主要Api文档 207 | [主要Api文档](API_zh.md) 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Specify analysis options. 2 | # 3 | # Until there are meta linter rules, each desired lint must be explicitly enabled. 4 | # See: https://github.com/dart-lang/linter/issues/288 5 | # 6 | # For a list of lints, see: http://dart-lang.github.io/linter/lints/ 7 | # See the configuration guide for more 8 | # https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer 9 | # 10 | # There are four similar analysis options files in the flutter repos: 11 | # - analysis_options.yaml (this file) 12 | # - packages/flutter/lib/analysis_options_user.yaml 13 | # - https://github.com/flutter/plugins/blob/master/analysis_options.yaml 14 | # - https://github.com/flutter/engine/blob/master/analysis_options.yaml 15 | # 16 | # This file contains the analysis options used by Flutter tools, such as IntelliJ, 17 | # Android Studio, and the `flutter analyze` command. 18 | # 19 | # The flutter/plugins repo contains a copy of this file, which should be kept 20 | # in sync with this file. 21 | 22 | analyzer: 23 | language: 24 | enableSuperMixins: true 25 | strong-mode: 26 | implicit-dynamic: true 27 | errors: 28 | # treat missing required parameters as a warning (not a hint) 29 | missing_required_param: error 30 | # treat missing returns as a warning (not a hint) 31 | missing_return: error 32 | # allow having TODOs in the code 33 | todo: ignore 34 | 35 | exclude: 36 | - 'bin/cache/**' 37 | - 'flutter/**' 38 | # the following two are relative to the stocks example and the flutter package respectively 39 | # see https://github.com/dart-lang/sdk/issues/28463 40 | - 'lib/i18n/stock_messages_*.dart' 41 | - 'lib/src/http/**' 42 | 43 | linter: 44 | rules: 45 | # these rules are documented on and in the same order as 46 | # the Dart Lint rules page to make maintenance easier 47 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 48 | - always_declare_return_types 49 | - always_put_control_body_on_new_line 50 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 51 | - always_require_non_null_named_parameters 52 | - always_specify_types 53 | - annotate_overrides 54 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types 55 | - avoid_as 56 | # - avoid_bool_literals_in_conditional_expressions # not yet tested 57 | # - avoid_catches_without_on_clauses # we do this commonly 58 | # - avoid_catching_errors # we do this commonly 59 | # - avoid_classes_with_only_static_members 60 | - avoid_empty_else 61 | - avoid_function_literals_in_foreach_calls 62 | - avoid_init_to_null 63 | - avoid_null_checks_in_equality_operators 64 | # - avoid_positional_boolean_parameters # not yet tested 65 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) 66 | - avoid_relative_lib_imports 67 | - avoid_renaming_method_parameters 68 | - avoid_return_types_on_setters 69 | # - avoid_returning_null # we do this commonly 70 | # - avoid_returning_this # https://github.com/dart-lang/linter/issues/842 71 | # - avoid_setters_without_getters # not yet tested 72 | # - avoid_single_cascade_in_expression_statements # not yet tested 73 | - avoid_slow_async_io 74 | # - avoid_types_as_parameter_names # https://github.com/dart-lang/linter/pull/954/files 75 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types 76 | # - avoid_unused_constructor_parameters # https://github.com/dart-lang/linter/pull/847 77 | - await_only_futures 78 | - camel_case_types 79 | - cancel_subscriptions 80 | # - cascade_invocations # not yet tested 81 | # - close_sinks # https://github.com/flutter/flutter/issues/5789 82 | # - comment_references # blocked on https://github.com/dart-lang/dartdoc/issues/1153 83 | # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 84 | - control_flow_in_finally 85 | - directives_ordering 86 | - empty_catches 87 | - empty_constructor_bodies 88 | - empty_statements 89 | - hash_and_equals 90 | - implementation_imports 91 | # - invariant_booleans # https://github.com/flutter/flutter/issues/5790 92 | - iterable_contains_unrelated_type 93 | # - join_return_with_assignment # not yet tested 94 | - library_names 95 | - library_prefixes 96 | - list_remove_unrelated_type 97 | # - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791 98 | - no_adjacent_strings_in_list 99 | - no_duplicate_case_values 100 | - non_constant_identifier_names 101 | # - omit_local_variable_types # opposite of always_specify_types 102 | # - one_member_abstracts # too many false positives 103 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 104 | - overridden_fields 105 | - package_api_docs 106 | - package_names 107 | - package_prefixed_library_names 108 | # - parameter_assignments # we do this commonly 109 | - prefer_adjacent_string_concatenation 110 | - prefer_asserts_in_initializer_lists 111 | - prefer_bool_in_asserts 112 | - prefer_collection_literals 113 | - prefer_conditional_assignment 114 | - prefer_const_constructors 115 | - prefer_const_constructors_in_immutables 116 | - prefer_const_declarations 117 | - prefer_const_literals_to_create_immutables 118 | # - prefer_constructors_over_static_methods # not yet tested 119 | - prefer_contains 120 | # - prefer_equal_for_default_values # not yet tested 121 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods 122 | - prefer_final_fields 123 | - prefer_final_locals 124 | - prefer_foreach 125 | # - prefer_function_declarations_over_variables # not yet tested 126 | - prefer_initializing_formals 127 | # - prefer_interpolation_to_compose_strings # not yet tested 128 | - prefer_is_empty 129 | - prefer_is_not_empty 130 | - prefer_single_quotes 131 | - prefer_typing_uninitialized_variables 132 | - recursive_getters 133 | - slash_for_doc_comments 134 | # - sort_constructors_first 135 | - sort_unnamed_constructors_first 136 | - super_goes_last 137 | - test_types_in_equals 138 | - throw_in_finally 139 | # - type_annotate_public_apis # subset of always_specify_types 140 | - type_init_formals 141 | # - unawaited_futures # https://github.com/flutter/flutter/issues/5793 142 | - unnecessary_brace_in_string_interps 143 | - unnecessary_getters_setters 144 | # - unnecessary_lambdas # https://github.com/dart-lang/linter/issues/498 145 | - unnecessary_null_aware_assignments 146 | - unnecessary_null_in_if_null_operators 147 | - unnecessary_overrides 148 | # - unnecessary_parenthesis 149 | # - unnecessary_statements # not yet tested 150 | - unnecessary_this 151 | - unnecessary_new 152 | - unnecessary_const 153 | - unrelated_type_equality_checks 154 | - use_rethrow_when_possible 155 | # - use_setters_to_change_properties # not yet tested 156 | # - use_string_buffers # https://github.com/dart-lang/linter/pull/664 157 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review 158 | - valid_regexps 159 | -------------------------------------------------------------------------------- /doc/gif/attached.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/doc/gif/attached.gif -------------------------------------------------------------------------------- /doc/gif/custom_animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/doc/gif/custom_animation.gif -------------------------------------------------------------------------------- /doc/gif/custom_widget.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/doc/gif/custom_widget.gif -------------------------------------------------------------------------------- /doc/gif/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/doc/gif/loading.gif -------------------------------------------------------------------------------- /doc/gif/notification.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/doc/gif/notification.gif -------------------------------------------------------------------------------- /doc/gif/text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/doc/gif/text.gif -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | ``` 4 | cd example 5 | flutter create . 6 | flutter run 7 | ``` 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.example" 37 | minSdkVersion 16 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 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 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 11 | 15 | 22 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.example; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | -------------------------------------------------------------------------------- /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-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/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/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MMMzq/bot_toast/39fe8abde5e1d98b3185982494124b471c3b40c9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/all.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:bot_toast/bot_toast.dart'; 4 | 5 | class All extends StatefulWidget { 6 | @override 7 | _AllState createState() => _AllState(); 8 | } 9 | 10 | class _AllState extends State { 11 | @override 12 | void initState() { 13 | BotToast.showLoading(duration: const Duration(seconds: 2)); 14 | BotToast.showSimpleNotification(title: "init"); 15 | BotToast.showText(text: "init"); 16 | BotToast.showAttachedWidget( 17 | attachedBuilder: (_) => const Card( 18 | child: Padding( 19 | padding: EdgeInsets.all(8.0), 20 | child: Icon( 21 | Icons.favorite, 22 | color: Colors.redAccent, 23 | ), 24 | ), 25 | ), 26 | enableSafeArea: false, 27 | duration: const Duration(seconds: 2), 28 | target: const Offset(52, 0)); 29 | 30 | super.initState(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | appBar: AppBar( 37 | title: const Text("All"), 38 | ), 39 | body: Container( 40 | padding: const EdgeInsets.only(top: 20), 41 | child: SingleChildScrollView( 42 | child: Column( 43 | mainAxisSize: MainAxisSize.min, 44 | children: [ 45 | const Divider(), 46 | const Text("code"), 47 | const Divider(), 48 | Text( 49 | _code, 50 | textAlign: TextAlign.start, 51 | ), 52 | const Divider(), 53 | ], 54 | ), 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | 61 | String _code = ''' 62 | @override 63 | void initState() { 64 | BotToast.showLoading(duration: Duration(seconds: 2)); 65 | BotToast.showSimpleNotification(title: "init"); 66 | BotToast.showText(text: "init"); 67 | BotToast.showAttachedWidget( 68 | attachedWidget: (_) => Card( 69 | child: Padding( 70 | padding: const EdgeInsets.all(8.0), 71 | child: Icon( 72 | Icons.favorite, 73 | color: Colors.redAccent, 74 | ), 75 | ), 76 | ), 77 | duration: Duration(seconds: 2), 78 | target: Offset(520, 520)); 79 | 80 | super.initState(); 81 | } 82 | '''; 83 | -------------------------------------------------------------------------------- /example/lib/custom/custom_animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:bot_toast/bot_toast.dart'; 3 | 4 | class CustomAnimation extends StatefulWidget { 5 | @override 6 | _CustomAnimationState createState() => _CustomAnimationState(); 7 | } 8 | 9 | class _CustomAnimationState extends State { 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar( 14 | title: const Text('CustomAnimation'), 15 | ), 16 | body: ButtonTheme.fromButtonThemeData( 17 | data: ButtonTheme.of(context).copyWith(minWidth: 250), 18 | child: Container( 19 | padding: const EdgeInsets.only(top: 10), 20 | child: SingleChildScrollView( 21 | child: Column( 22 | mainAxisSize: MainAxisSize.min, 23 | children: [ 24 | ElevatedButton( 25 | onPressed: () { 26 | BotToast.showLoading( 27 | allowClick: true, 28 | animationDuration: const Duration(milliseconds: 200), 29 | duration: const Duration(seconds: 2), 30 | wrapAnimation: (controller, cancel, child) => 31 | CustomOffsetAnimation( 32 | controller: controller, child: child)); 33 | }, 34 | child: const Text('customLoadingAnimation'), 35 | ), 36 | ElevatedButton( 37 | onPressed: () { 38 | BotToast.showSimpleNotification( 39 | animationDuration: const Duration(milliseconds: 200), 40 | duration: const Duration(seconds: 2), 41 | wrapToastAnimation: (controller, cancel, child) => 42 | CustomOffsetAnimation( 43 | reverse: true, 44 | controller: controller, 45 | child: child), 46 | title: 'customNotificationAnimation'); 47 | }, 48 | child: const Text('customNotificationAnimation'), 49 | ), 50 | ElevatedButton( 51 | onPressed: () { 52 | BotToast.showText( 53 | text: 'this is custom animation ', 54 | wrapToastAnimation: (controller, cancel, Widget child) => 55 | CustomAnimationWidget( 56 | controller: controller, 57 | child: child, 58 | ), 59 | ); 60 | }, 61 | child: const Text('customTextAnimation'), 62 | ), 63 | Builder( 64 | builder: (context) => ElevatedButton( 65 | onPressed: () { 66 | BotToast.showAttachedWidget( 67 | attachedBuilder: (_) => Card( 68 | color: Colors.amber, 69 | child: Container( 70 | padding: 71 | const EdgeInsets.symmetric(horizontal: 8), 72 | child: Column( 73 | mainAxisSize: MainAxisSize.min, 74 | children: [ 75 | TextButton.icon( 76 | style:TextButton.styleFrom( 77 | padding: const EdgeInsets.all(5)), 78 | onPressed: () { 79 | BotToast.showSimpleNotification( 80 | title: 81 | "Let's go travel together.😘"); 82 | }, 83 | label: ConstrainedBox( 84 | constraints: const BoxConstraints( 85 | minWidth: 70), 86 | child: const Text('favorite'), 87 | ), 88 | icon: const Icon(Icons.favorite, 89 | color: Colors.redAccent), 90 | ), 91 | TextButton.icon( 92 | style:TextButton.styleFrom( 93 | padding: const EdgeInsets.all(5)), 94 | onPressed: () { 95 | BotToast.showSimpleNotification( 96 | title: 97 | 'Thank you for liking me.😝'); 98 | }, 99 | label: ConstrainedBox( 100 | constraints: const BoxConstraints( 101 | minWidth: 70), 102 | child: const Text('bookmark'), 103 | ), 104 | icon: const Icon(Icons.bookmark, 105 | color: Colors.redAccent), 106 | ) 107 | ], 108 | ), 109 | ), 110 | ), 111 | wrapToastAnimation: 112 | (controller, cancel, Widget child) => 113 | CustomAttachedAnimation( 114 | controller: controller, 115 | child: child, 116 | ), 117 | animationDuration: const Duration(milliseconds: 300), 118 | enableSafeArea: false, 119 | duration: const Duration(seconds: 2), 120 | targetContext: context); 121 | }, 122 | child: const Text('customAttachedAnimation'), 123 | ), 124 | ), 125 | const Divider(), 126 | ], 127 | ), 128 | ), 129 | ), 130 | ), 131 | ); 132 | } 133 | } 134 | 135 | class CustomAnimationWidget extends StatefulWidget { 136 | final AnimationController controller; 137 | final Widget child; 138 | 139 | const CustomAnimationWidget({Key? key,required this.controller,required this.child}) 140 | : super(key: key); 141 | 142 | @override 143 | _CustomAnimationWidgetState createState() => _CustomAnimationWidgetState(); 144 | } 145 | 146 | class _CustomAnimationWidgetState extends State { 147 | static final Tween tweenOffset = Tween( 148 | begin: const Offset(0, 40), 149 | end: const Offset(0, 0), 150 | ); 151 | 152 | static final Tween tweenScale = Tween(begin: 0.7, end: 1.0); 153 | late Animation animation; 154 | 155 | @override 156 | void initState() { 157 | animation = 158 | CurvedAnimation(parent: widget.controller, curve: Curves.decelerate); 159 | super.initState(); 160 | } 161 | 162 | @override 163 | Widget build(BuildContext context) { 164 | return AnimatedBuilder( 165 | child: widget.child, 166 | animation: animation, 167 | builder: (BuildContext context, Widget? child) { 168 | return Transform.translate( 169 | offset: tweenOffset.evaluate(animation), 170 | child: Transform.scale( 171 | scale: tweenScale.evaluate(animation), 172 | child: Opacity( 173 | child: child, 174 | opacity: animation.value, 175 | ), 176 | ), 177 | ); 178 | }, 179 | ); 180 | } 181 | } 182 | 183 | class CustomOffsetAnimation extends StatefulWidget { 184 | final AnimationController controller; 185 | final Widget child; 186 | final bool reverse; 187 | 188 | const CustomOffsetAnimation( 189 | {Key? key,required this.controller,required this.child, this.reverse = false}) 190 | : super(key: key); 191 | 192 | @override 193 | _CustomOffsetAnimationState createState() => _CustomOffsetAnimationState(); 194 | } 195 | 196 | class _CustomOffsetAnimationState extends State { 197 | late Tween tweenOffset; 198 | 199 | late Animation animation; 200 | 201 | @override 202 | void initState() { 203 | tweenOffset = Tween( 204 | begin: Offset(widget.reverse ? -0.8 : 0.8, 0.0), 205 | end: Offset.zero, 206 | ); 207 | animation = 208 | CurvedAnimation(parent: widget.controller, curve: Curves.decelerate); 209 | super.initState(); 210 | } 211 | 212 | @override 213 | Widget build(BuildContext context) { 214 | return AnimatedBuilder( 215 | child: widget.child, 216 | animation: widget.controller, 217 | builder: (BuildContext context, Widget? child) { 218 | return FractionalTranslation( 219 | translation: tweenOffset.evaluate(animation), 220 | child: Opacity( 221 | opacity: animation.value, 222 | child: child, 223 | )); 224 | }, 225 | ); 226 | } 227 | } 228 | 229 | class CustomAttachedAnimation extends StatefulWidget { 230 | final AnimationController controller; 231 | final Widget child; 232 | 233 | const CustomAttachedAnimation({Key? key,required this.controller,required this.child}) 234 | : super(key: key); 235 | 236 | @override 237 | _CustomAttachedAnimationState createState() => 238 | _CustomAttachedAnimationState(); 239 | } 240 | 241 | class _CustomAttachedAnimationState extends State { 242 | late Animation animation; 243 | static final Tween tweenOffset = Tween( 244 | begin: const Offset(0, 40), 245 | end: const Offset(0, 0), 246 | ); 247 | 248 | @override 249 | void initState() { 250 | animation = 251 | CurvedAnimation(parent: widget.controller, curve: Curves.decelerate); 252 | super.initState(); 253 | } 254 | 255 | @override 256 | Widget build(BuildContext context) { 257 | return AnimatedBuilder( 258 | child: widget.child, 259 | animation: widget.controller, 260 | builder: (BuildContext context, Widget? child) { 261 | return ClipRect( 262 | child: Align( 263 | heightFactor: animation.value, 264 | widthFactor: animation.value, 265 | child: Opacity( 266 | opacity: animation.value, 267 | child: child, 268 | ), 269 | ), 270 | ); 271 | }, 272 | ); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /example/lib/custom/custom_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/scheduler.dart'; 4 | 5 | void showAlertDialog(BackButtonBehavior backButtonBehavior, 6 | {VoidCallback? cancel, 7 | VoidCallback? confirm, 8 | VoidCallback? backgroundReturn}) { 9 | BotToast.showAnimationWidget( 10 | clickClose: false, 11 | allowClick: false, 12 | onlyOne: true, 13 | crossPage: true, 14 | backButtonBehavior: backButtonBehavior, 15 | wrapToastAnimation: (controller, cancel, child) => Stack( 16 | children: [ 17 | GestureDetector( 18 | onTap: () { 19 | cancel(); 20 | backgroundReturn?.call(); 21 | }, 22 | //The DecoratedBox here is very important,he will fill the entire parent component 23 | child: AnimatedBuilder( 24 | builder: (_, child) => Opacity( 25 | opacity: controller.value, 26 | child: child, 27 | ), 28 | child: const DecoratedBox( 29 | decoration: BoxDecoration(color: Colors.black26), 30 | child: SizedBox.expand(), 31 | ), 32 | animation: controller, 33 | ), 34 | ), 35 | CustomOffsetAnimation( 36 | controller: controller, 37 | child: child, 38 | ) 39 | ], 40 | ), 41 | toastBuilder: (cancelFunc) => AlertDialog( 42 | shape: 43 | RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), 44 | title: const Text('this is custom widget'), 45 | actions: [ 46 | TextButton( 47 | onPressed: () { 48 | cancelFunc(); 49 | cancel?.call(); 50 | }, 51 | child: const Text( 52 | 'cancel', 53 | style: TextStyle(color: Colors.redAccent), 54 | ), 55 | ), 56 | TextButton( 57 | onPressed: () { 58 | cancelFunc(); 59 | confirm?.call(); 60 | }, 61 | child: const Text('confirm'), 62 | ), 63 | ], 64 | ), 65 | animationDuration: const Duration(milliseconds: 300)); 66 | } 67 | 68 | class CustomWidget extends StatefulWidget { 69 | @override 70 | _CustomWidgetState createState() => _CustomWidgetState(); 71 | } 72 | 73 | class _CustomWidgetState extends State { 74 | BackButtonBehavior backButtonBehavior = BackButtonBehavior.none; 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | return Scaffold( 79 | appBar: AppBar( 80 | title: const Text('CustomWidget'), 81 | ), 82 | body: Container( 83 | padding: const EdgeInsets.only(top: 10), 84 | child: SingleChildScrollView( 85 | child: Column( 86 | mainAxisSize: MainAxisSize.min, 87 | children: [ 88 | ElevatedButton( 89 | onPressed: () { 90 | showAlertDialog(backButtonBehavior, cancel: () { 91 | BotToast.showText(text: 'Click cancel'); 92 | }, confirm: () { 93 | BotToast.showText(text: 'Click confirm'); 94 | }, backgroundReturn: () { 95 | BotToast.showText(text: 'Click background'); 96 | }); 97 | }, 98 | child: const Text('customWidget'), 99 | ), 100 | const Center( 101 | child: Text('BackButtonBehavior'), 102 | ), 103 | Row( 104 | children: [ 105 | Expanded( 106 | child: RadioListTile( 107 | value: BackButtonBehavior.none, 108 | groupValue: backButtonBehavior, 109 | onChanged: (BackButtonBehavior? value) { 110 | setState(() { 111 | backButtonBehavior = value!; 112 | }); 113 | }, 114 | title: const Text('none'), 115 | ), 116 | ), 117 | Expanded( 118 | child: RadioListTile( 119 | value: BackButtonBehavior.ignore, 120 | groupValue: backButtonBehavior, 121 | onChanged: (BackButtonBehavior? value) { 122 | setState(() { 123 | backButtonBehavior = value!; 124 | }); 125 | }, 126 | title: const Text('ignore'), 127 | ), 128 | ), 129 | Expanded( 130 | child: RadioListTile( 131 | value: BackButtonBehavior.close, 132 | groupValue: backButtonBehavior, 133 | onChanged: (BackButtonBehavior? value) { 134 | setState(() { 135 | backButtonBehavior = value!; 136 | }); 137 | }, 138 | title: const Text('close'), 139 | ), 140 | ) 141 | ], 142 | ), 143 | const Divider(), 144 | ], 145 | ), 146 | ), 147 | ), 148 | ); 149 | } 150 | } 151 | 152 | class CustomOffsetAnimation extends StatefulWidget { 153 | final AnimationController controller; 154 | final Widget child; 155 | 156 | const CustomOffsetAnimation({Key? key,required this.controller,required this.child}) 157 | : super(key: key); 158 | 159 | @override 160 | _CustomOffsetAnimationState createState() => _CustomOffsetAnimationState(); 161 | } 162 | 163 | class _CustomOffsetAnimationState extends State { 164 | late Tween tweenOffset; 165 | late Tween tweenScale; 166 | 167 | late Animation animation; 168 | 169 | @override 170 | void initState() { 171 | tweenOffset = Tween( 172 | begin: const Offset(0.0, 0.8), 173 | end: Offset.zero, 174 | ); 175 | tweenScale = Tween(begin: 0.3, end: 1.0); 176 | animation = 177 | CurvedAnimation(parent: widget.controller, curve: Curves.decelerate); 178 | super.initState(); 179 | } 180 | 181 | @override 182 | Widget build(BuildContext context) { 183 | return AnimatedBuilder( 184 | child: widget.child, 185 | animation: widget.controller, 186 | builder: (BuildContext context, Widget? child) { 187 | return FractionalTranslation( 188 | translation: tweenOffset.evaluate(animation), 189 | child: ClipRect( 190 | child: Transform.scale( 191 | scale: tweenScale.evaluate(animation), 192 | child: Opacity( 193 | child: child, 194 | opacity: animation.value, 195 | ), 196 | ), 197 | )); 198 | }, 199 | ); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /example/lib/loading/custom_loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class _CustomLoadWidget extends StatefulWidget { 6 | final CancelFunc cancelFunc; 7 | 8 | const _CustomLoadWidget({Key? key,required this.cancelFunc}) : super(key: key); 9 | 10 | @override 11 | __CustomLoadWidgetState createState() => __CustomLoadWidgetState(); 12 | } 13 | 14 | class __CustomLoadWidgetState extends State<_CustomLoadWidget> 15 | with SingleTickerProviderStateMixin { 16 | late AnimationController animationController; 17 | 18 | @override 19 | void initState() { 20 | animationController = 21 | AnimationController(vsync: this, duration: const Duration(milliseconds: 300)); 22 | 23 | animationController.addStatusListener((AnimationStatus status) { 24 | if (status == AnimationStatus.completed) { 25 | animationController.reverse(); 26 | } else if (status == AnimationStatus.dismissed) { 27 | animationController.forward(); 28 | } 29 | }); 30 | animationController.forward(); 31 | 32 | super.initState(); 33 | } 34 | 35 | @override 36 | void dispose() { 37 | animationController.dispose(); 38 | super.dispose(); 39 | } 40 | 41 | void handleTap() { 42 | BotToast.showCustomText( 43 | onlyOne: true, 44 | duration: null, 45 | toastBuilder: (textCancel) => Align( 46 | alignment: const Alignment(0, 0.8), 47 | child: Card( 48 | child: Row( 49 | mainAxisSize: MainAxisSize.min, 50 | children: [ 51 | IconButton( 52 | icon: const Icon( 53 | Icons.favorite, 54 | color: Colors.redAccent, 55 | ), 56 | onPressed: () { 57 | widget.cancelFunc(); 58 | textCancel(); 59 | }), 60 | const Padding( 61 | padding: EdgeInsets.symmetric(horizontal: 8.0), 62 | child: Text("Tap loading toast"), 63 | ) 64 | ], 65 | ), 66 | ), 67 | )); 68 | } 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | return Card( 73 | child: Padding( 74 | padding: const EdgeInsets.all(16.0), 75 | child: Column( 76 | mainAxisSize: MainAxisSize.min, 77 | children: [ 78 | FadeTransition( 79 | opacity: animationController, 80 | child: IconButton( 81 | icon: const Icon(Icons.favorite, color: Colors.redAccent, size: 30), 82 | onPressed: handleTap, 83 | ), 84 | ), 85 | const Padding( 86 | padding: EdgeInsets.all(8.0), 87 | child: Text( 88 | "Loading", 89 | ), 90 | ) 91 | ], 92 | ), 93 | ), 94 | ); 95 | } 96 | } 97 | 98 | class CustomLoading extends StatefulWidget { 99 | @override 100 | _CustomLoadingState createState() => _CustomLoadingState(); 101 | } 102 | 103 | class _CustomLoadingState extends State { 104 | int backgroundColor = 0x42000000; 105 | int seconds = 10; 106 | bool clickClose = true; 107 | bool allowClick = true; 108 | bool ignoreContentClick = false; 109 | bool crossPage = true; 110 | int animationMilliseconds = 200; 111 | int animationReverseMilliseconds = 200; 112 | BackButtonBehavior backButtonBehavior = BackButtonBehavior.none; 113 | 114 | @override 115 | Widget build(BuildContext context) { 116 | return Scaffold( 117 | appBar: AppBar( 118 | title: const Text("CustomLoading"), 119 | ), 120 | body: Container( 121 | padding: const EdgeInsets.only(top: 10), 122 | child: SingleChildScrollView( 123 | child: Column( 124 | mainAxisSize: MainAxisSize.min, 125 | children: [ 126 | ElevatedButton( 127 | onPressed: () { 128 | BotToast.showCustomLoading( 129 | clickClose: clickClose, 130 | allowClick: allowClick, 131 | backButtonBehavior: backButtonBehavior, 132 | ignoreContentClick: ignoreContentClick, 133 | animationDuration: 134 | Duration(milliseconds: animationMilliseconds), 135 | animationReverseDuration: 136 | Duration(milliseconds: animationReverseMilliseconds), 137 | duration: Duration( 138 | seconds: seconds, 139 | ), 140 | backgroundColor: Color(backgroundColor), 141 | align: Alignment.center, 142 | toastBuilder: (cancelFunc) { 143 | return _CustomLoadWidget(cancelFunc: cancelFunc); 144 | }); 145 | }, 146 | child: const Text("CustomLoading"), 147 | ), 148 | ListTile( 149 | title: Text("duration: ${seconds}s"), 150 | trailing: CupertinoSlider( 151 | min: 1, 152 | max: 20, 153 | value: seconds.toDouble(), 154 | onChanged: (double value) { 155 | setState(() { 156 | seconds = value.toInt(); 157 | }); 158 | }, 159 | ), 160 | ), 161 | ListTile( 162 | title: Text("animationDuration: ${animationMilliseconds}ms"), 163 | trailing: CupertinoSlider( 164 | min: 100, 165 | max: 1000, 166 | divisions: 18, 167 | value: animationMilliseconds.toDouble(), 168 | onChanged: (double value) { 169 | setState(() { 170 | animationMilliseconds = value.toInt(); 171 | }); 172 | }, 173 | ), 174 | ), 175 | ListTile( 176 | title: Text( 177 | "animationReverseDuration: ${animationReverseMilliseconds}ms"), 178 | trailing: CupertinoSlider( 179 | min: 100, 180 | max: 1000, 181 | divisions: 18, 182 | value: animationReverseMilliseconds.toDouble(), 183 | onChanged: (double value) { 184 | setState(() { 185 | animationReverseMilliseconds = value.toInt(); 186 | }); 187 | }, 188 | ), 189 | ), 190 | 191 | const Center(child: Text('BackButtonBehavior'),), 192 | Row( 193 | children: [ 194 | Expanded( 195 | child: RadioListTile(value: BackButtonBehavior.none, 196 | groupValue: backButtonBehavior, 197 | onChanged: (BackButtonBehavior? value) { 198 | setState(() { 199 | backButtonBehavior = value!; 200 | }); 201 | }, 202 | title: const Text('none'),), 203 | ), 204 | Expanded( 205 | child: RadioListTile(value: BackButtonBehavior.ignore, 206 | groupValue: backButtonBehavior, 207 | onChanged: (BackButtonBehavior? value) { 208 | setState(() { 209 | backButtonBehavior = value!; 210 | }); 211 | }, 212 | title: const Text('ignore'),), 213 | ), 214 | Expanded( 215 | child: RadioListTile(value: BackButtonBehavior.close, 216 | groupValue: backButtonBehavior, 217 | onChanged: (BackButtonBehavior? value) { 218 | setState(() { 219 | backButtonBehavior = value!; 220 | }); 221 | }, 222 | title: const Text('close'),), 223 | ) 224 | ], 225 | ), 226 | SwitchListTile( 227 | value: clickClose, 228 | onChanged: (bool value) { 229 | setState(() { 230 | clickClose = value; 231 | }); 232 | }, 233 | title: const Text("clickClose: "), 234 | ), 235 | SwitchListTile( 236 | value: allowClick, 237 | onChanged: (bool value) { 238 | setState(() { 239 | allowClick = value; 240 | }); 241 | }, 242 | title: const Text("allowClick: "), 243 | ), 244 | SwitchListTile( 245 | value: ignoreContentClick, 246 | onChanged: (bool value) { 247 | setState(() { 248 | ignoreContentClick = value; 249 | }); 250 | }, 251 | title: const Text("ignoreContentClick: "), 252 | ), 253 | SwitchListTile( 254 | value: crossPage, 255 | onChanged: (bool value) { 256 | setState(() { 257 | crossPage = value; 258 | }); 259 | }, 260 | title: const Text("crossPage: "), 261 | ), 262 | ListTile( 263 | title: Row( 264 | mainAxisSize: MainAxisSize.min, 265 | children: [ 266 | const Expanded(child: Text("backgroundColor:")), 267 | Container( 268 | height: 20, 269 | width: 20, 270 | color: Color(backgroundColor), 271 | ) 272 | ], 273 | ), 274 | trailing: CupertinoSlider( 275 | min: 0x00000000, 276 | max: 0xffffffff, 277 | value: backgroundColor.toDouble(), 278 | onChanged: (double value) { 279 | setState(() { 280 | backgroundColor = value.toInt(); 281 | }); 282 | }, 283 | ), 284 | ), 285 | ], 286 | ), 287 | ), 288 | ), 289 | ); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /example/lib/loading/loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class Loading extends StatefulWidget { 6 | @override 7 | _LoadingState createState() => _LoadingState(); 8 | } 9 | 10 | class _LoadingState extends State { 11 | int backgroundColor = 0x42000000; 12 | int seconds = 2; 13 | bool clickClose = true; 14 | bool allowClick = true; 15 | bool crossPage = true; 16 | int animationMilliseconds = 200; 17 | int animationReverseMilliseconds = 200; 18 | BackButtonBehavior backButtonBehavior = BackButtonBehavior.none; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Scaffold( 23 | appBar: AppBar( 24 | title: const Text("Loading"), 25 | ), 26 | body: Container( 27 | padding: const EdgeInsets.only(top: 10), 28 | child: SingleChildScrollView( 29 | child: Column( 30 | mainAxisSize: MainAxisSize.min, 31 | children: [ 32 | ElevatedButton( 33 | onPressed: () { 34 | BotToast.showLoading( 35 | clickClose: clickClose, 36 | allowClick: allowClick, 37 | crossPage: crossPage, 38 | backButtonBehavior: backButtonBehavior, 39 | animationDuration: 40 | Duration(milliseconds: animationMilliseconds), 41 | animationReverseDuration: 42 | Duration(milliseconds: animationReverseMilliseconds), 43 | duration: Duration( 44 | seconds: seconds, 45 | ), 46 | backgroundColor: Color(backgroundColor)); 47 | }, 48 | child: const Text("Loading"), 49 | ), 50 | ListTile( 51 | title: Text("duration: ${seconds}s"), 52 | trailing: CupertinoSlider( 53 | min: 1, 54 | max: 20, 55 | value: seconds.toDouble(), 56 | onChanged: (double value) { 57 | setState(() { 58 | seconds = value.toInt(); 59 | }); 60 | }, 61 | ), 62 | ), 63 | ListTile( 64 | title: Text("animationDuration: ${animationMilliseconds}ms"), 65 | trailing: CupertinoSlider( 66 | min: 100, 67 | max: 1000, 68 | divisions: 18, 69 | value: animationMilliseconds.toDouble(), 70 | onChanged: (double value) { 71 | setState(() { 72 | animationMilliseconds = value.toInt(); 73 | }); 74 | }, 75 | ), 76 | ), 77 | ListTile( 78 | title: Text( 79 | "animationReverseDuration: ${animationReverseMilliseconds}ms"), 80 | trailing: CupertinoSlider( 81 | min: 100, 82 | max: 1000, 83 | divisions: 18, 84 | value: animationReverseMilliseconds.toDouble(), 85 | onChanged: (double value) { 86 | setState(() { 87 | animationReverseMilliseconds = value.toInt(); 88 | }); 89 | }, 90 | ), 91 | ), 92 | const Center(child: Text('BackButtonBehavior'),), 93 | Row( 94 | children: [ 95 | Expanded( 96 | child: RadioListTile(value: BackButtonBehavior.none, 97 | groupValue: backButtonBehavior, 98 | onChanged: (BackButtonBehavior? value) { 99 | setState(() { 100 | backButtonBehavior = value!; 101 | }); 102 | }, 103 | title: const Text('none'),), 104 | ), 105 | Expanded( 106 | child: RadioListTile(value: BackButtonBehavior.ignore, 107 | groupValue: backButtonBehavior, 108 | onChanged: (BackButtonBehavior? value) { 109 | setState(() { 110 | backButtonBehavior = value!; 111 | }); 112 | }, 113 | title: const Text('ignore'),), 114 | ), 115 | Expanded( 116 | child: RadioListTile(value: BackButtonBehavior.close, 117 | groupValue: backButtonBehavior, 118 | onChanged: (BackButtonBehavior? value) { 119 | setState(() { 120 | backButtonBehavior = value!; 121 | }); 122 | }, 123 | title: const Text('close'),), 124 | ) 125 | ], 126 | ), 127 | SwitchListTile( 128 | value: clickClose, 129 | onChanged: (value) { 130 | setState(() { 131 | clickClose = value; 132 | }); 133 | }, 134 | title: const Text("clickClose: "), 135 | ), 136 | SwitchListTile( 137 | value: allowClick, 138 | onChanged: (value) { 139 | setState(() { 140 | allowClick = value; 141 | }); 142 | }, 143 | title: const Text("allowClick: "), 144 | ), 145 | SwitchListTile( 146 | value: crossPage, 147 | onChanged: (value) { 148 | setState(() { 149 | crossPage = value; 150 | }); 151 | }, 152 | title: const Text("crossPage: "), 153 | ), 154 | ListTile( 155 | title: Row( 156 | mainAxisSize: MainAxisSize.min, 157 | children: [ 158 | const Expanded(child: Text("backgroundColor:")), 159 | Container( 160 | height: 20, 161 | width: 20, 162 | color: Color(backgroundColor), 163 | ) 164 | ], 165 | ), 166 | trailing: CupertinoSlider( 167 | min: 0x00000000, 168 | max: 0xffffffff, 169 | value: backgroundColor.toDouble(), 170 | onChanged: (double value) { 171 | setState(() { 172 | backgroundColor = value.toInt(); 173 | }); 174 | }, 175 | ), 176 | ), 177 | 178 | ], 179 | ), 180 | ), 181 | ), 182 | ); 183 | } 184 | } 185 | 186 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'all.dart'; 5 | import 'attached_toast/attached_toast.dart'; 6 | import 'custom/custom_animation.dart'; 7 | import 'custom/custom_widget.dart'; 8 | import 'loading/custom_loading.dart'; 9 | import 'loading/loading.dart'; 10 | import 'notification/custom_notification.dart'; 11 | import 'notification/simple_notification.dart'; 12 | import 'notification/notification.dart' as notification; 13 | import 'text/custom_text.dart'; 14 | import 'text/text.dart'; 15 | 16 | void main() { 17 | runApp(MyApp()); 18 | } 19 | 20 | class MyApp extends StatelessWidget { 21 | @override 22 | Widget build(BuildContext context) { 23 | return MaterialApp( 24 | theme: ThemeData( 25 | elevatedButtonTheme: ElevatedButtonThemeData( 26 | style: ElevatedButton.styleFrom( 27 | primary: Colors.grey[300], 28 | onPrimary: Colors.black, 29 | ), 30 | )), 31 | builder: BotToastInit(), 32 | title: 'BotToast Demo', 33 | navigatorObservers: [BotToastNavigatorObserver()], 34 | home: EnterPage(), 35 | ); 36 | } 37 | } 38 | 39 | class EnterPage extends StatelessWidget { 40 | @override 41 | Widget build(BuildContext context) { 42 | return Scaffold( 43 | appBar: AppBar( 44 | title: const Text("BotToast"), 45 | centerTitle: true, 46 | ), 47 | body: Align( 48 | alignment: Alignment.topCenter, 49 | child: SingleChildScrollView( 50 | child: Container( 51 | margin: const EdgeInsets.only(top: 30), 52 | child: Column(children: [ 53 | const Text( 54 | "Notification", 55 | style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), 56 | ), 57 | const Divider(), 58 | Row( 59 | children: [ 60 | Expanded( 61 | child: Container( 62 | margin: const EdgeInsets.symmetric(horizontal: 10), 63 | child: ElevatedButton( 64 | onPressed: () { 65 | Navigator.push( 66 | context, 67 | MaterialPageRoute( 68 | builder: (_) => SimpleNotification())); 69 | }, 70 | child: const Text("SimpleNotification"), 71 | ), 72 | ), 73 | ), 74 | Expanded( 75 | child: Container( 76 | margin: const EdgeInsets.symmetric(horizontal: 10), 77 | child: ElevatedButton( 78 | onPressed: () { 79 | Navigator.push( 80 | context, 81 | MaterialPageRoute( 82 | builder: (_) => 83 | notification.Notification())); 84 | }, 85 | child: const Text("Notification"), 86 | ), 87 | ), 88 | ) 89 | ], 90 | ), 91 | Container( 92 | width: double.infinity, 93 | margin: const EdgeInsets.symmetric(horizontal: 10), 94 | child: ElevatedButton( 95 | onPressed: () { 96 | Navigator.push( 97 | context, 98 | MaterialPageRoute( 99 | builder: (_) => CustomNotification())); 100 | }, 101 | child: const Text("CustomNotification"), 102 | ), 103 | ), 104 | Container( 105 | height: 40, 106 | ), 107 | const Text( 108 | "TextToast", 109 | style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), 110 | ), 111 | const Divider(), 112 | Row( 113 | children: [ 114 | Expanded( 115 | child: Container( 116 | margin: const EdgeInsets.symmetric(horizontal: 10), 117 | child: ElevatedButton( 118 | onPressed: () { 119 | Navigator.push( 120 | context, 121 | MaterialPageRoute( 122 | builder: (_) => TextSample())); 123 | }, 124 | child: const Text("TextToast"), 125 | ), 126 | ), 127 | ), 128 | Expanded( 129 | child: Container( 130 | margin: const EdgeInsets.symmetric(horizontal: 10), 131 | child: ElevatedButton( 132 | onPressed: () { 133 | Navigator.push( 134 | context, 135 | MaterialPageRoute( 136 | builder: (_) => CustomText())); 137 | }, 138 | child: const Text("CustomText"), 139 | ), 140 | ), 141 | ) 142 | ], 143 | ), 144 | Container( 145 | height: 40, 146 | ), 147 | const Text( 148 | "Load", 149 | style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), 150 | ), 151 | const Divider(), 152 | Row( 153 | children: [ 154 | Expanded( 155 | child: Container( 156 | margin: const EdgeInsets.symmetric(horizontal: 10), 157 | child: ElevatedButton( 158 | onPressed: () { 159 | Navigator.push(context, 160 | MaterialPageRoute(builder: (_) => Loading())); 161 | }, 162 | child: const Text("Loading"), 163 | ), 164 | ), 165 | ), 166 | Expanded( 167 | child: Container( 168 | margin: const EdgeInsets.symmetric(horizontal: 10), 169 | child: ElevatedButton( 170 | onPressed: () { 171 | Navigator.push( 172 | context, 173 | MaterialPageRoute( 174 | builder: (_) => CustomLoading())); 175 | }, 176 | child: const Text("CustomLoading"), 177 | ), 178 | ), 179 | ), 180 | ], 181 | ), 182 | const Text( 183 | 'Other', 184 | style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), 185 | ), 186 | const Divider(), 187 | Row( 188 | children: [ 189 | Expanded( 190 | child: Container( 191 | margin: const EdgeInsets.symmetric(horizontal: 10), 192 | child: ElevatedButton( 193 | onPressed: () { 194 | Navigator.push( 195 | context, 196 | MaterialPageRoute( 197 | builder: (_) => AttachedToast())); 198 | }, 199 | child: const Text('AttachedToast'), 200 | ), 201 | ), 202 | ), 203 | Expanded( 204 | child: Container( 205 | margin: const EdgeInsets.symmetric(horizontal: 10), 206 | child: ElevatedButton( 207 | onPressed: () { 208 | Navigator.push(context, 209 | MaterialPageRoute(builder: (_) => All())); 210 | }, 211 | child: const Text("All"), 212 | ), 213 | ), 214 | ), 215 | ], 216 | ), 217 | Row( 218 | children: [ 219 | Expanded( 220 | child: Container( 221 | margin: const EdgeInsets.symmetric(horizontal: 10), 222 | child: ElevatedButton( 223 | onPressed: () { 224 | Navigator.push( 225 | context, 226 | MaterialPageRoute( 227 | builder: (_) => CustomAnimation())); 228 | }, 229 | child: const Text('CustomAnimation'), 230 | ), 231 | ), 232 | ), 233 | Expanded( 234 | child: Container( 235 | margin: const EdgeInsets.symmetric(horizontal: 10), 236 | child: ElevatedButton( 237 | onPressed: () { 238 | Navigator.push( 239 | context, 240 | MaterialPageRoute( 241 | builder: (_) => CustomWidget())); 242 | }, 243 | child: const Text('CustomWidget'), 244 | ), 245 | ), 246 | ) 247 | ], 248 | ) 249 | ]), 250 | ), 251 | ), 252 | )); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /example/lib/notification/custom_notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class _CustomWidget extends StatefulWidget { 6 | final CancelFunc cancelFunc; 7 | 8 | const _CustomWidget({Key? key, required this.cancelFunc}) : super(key: key); 9 | 10 | @override 11 | _CustomWidgetState createState() => _CustomWidgetState(); 12 | } 13 | 14 | class _CustomWidgetState extends State<_CustomWidget> { 15 | bool loveMe = true; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Card( 20 | child: Row( 21 | children: [ 22 | Expanded( 23 | child: IconButton( 24 | icon: const Icon(Icons.favorite), 25 | color: loveMe ? Colors.redAccent : Colors.grey, 26 | onPressed: () { 27 | setState(() { 28 | loveMe = !loveMe; 29 | BotToast.showText( 30 | onlyOne: true, 31 | text: loveMe ? "Yes, I love you.😘" : "No!!!!😫"); 32 | }); 33 | }), 34 | ), 35 | IconButton( 36 | icon: const Icon(Icons.cancel), 37 | color: loveMe ? Colors.redAccent : Colors.grey, 38 | onPressed: widget.cancelFunc, 39 | ) 40 | ], 41 | ), 42 | ); 43 | } 44 | } 45 | 46 | class CustomNotification extends StatefulWidget { 47 | @override 48 | _CustomNotificationState createState() => _CustomNotificationState(); 49 | } 50 | 51 | class _CustomNotificationState extends State { 52 | bool enableSlideOff = true; 53 | bool onlyOne = true; 54 | bool crossPage = true; 55 | int seconds = 10; 56 | int animationMilliseconds = 200; 57 | int animationReverseMilliseconds = 200; 58 | BackButtonBehavior backButtonBehavior = BackButtonBehavior.none; 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | return Scaffold( 63 | appBar: AppBar( 64 | title: const Text("CustomNotification"), 65 | ), 66 | body: Container( 67 | padding: const EdgeInsets.only(top: 20), 68 | child: SingleChildScrollView( 69 | child: Column( 70 | mainAxisSize: MainAxisSize.min, 71 | children: [ 72 | ElevatedButton( 73 | onPressed: () { 74 | BotToast.showCustomNotification( 75 | animationDuration: 76 | Duration(milliseconds: animationMilliseconds), 77 | animationReverseDuration: 78 | Duration(milliseconds: animationReverseMilliseconds), 79 | duration: Duration(seconds: seconds), 80 | backButtonBehavior: backButtonBehavior, 81 | toastBuilder: (cancel) { 82 | return _CustomWidget( 83 | cancelFunc: cancel, 84 | ); 85 | }, 86 | enableSlideOff: enableSlideOff, 87 | onlyOne: onlyOne, 88 | crossPage: crossPage); 89 | }, 90 | child: const Text("CustomNotification"), 91 | ), 92 | SwitchListTile( 93 | value: enableSlideOff, 94 | onChanged: (value) { 95 | setState(() { 96 | enableSlideOff = value; 97 | }); 98 | }, 99 | title: const Text("enableSlideOff: "), 100 | ), 101 | SwitchListTile( 102 | value: onlyOne, 103 | onChanged: (value) { 104 | setState(() { 105 | onlyOne = value; 106 | }); 107 | }, 108 | title: const Text("onlyOne: "), 109 | ), 110 | SwitchListTile( 111 | value: crossPage, 112 | onChanged: (value) { 113 | setState(() { 114 | crossPage = value; 115 | }); 116 | }, 117 | title: const Text("crossPage: "), 118 | ), 119 | const Center( 120 | child: Text('BackButtonBehavior'), 121 | ), 122 | Row( 123 | children: [ 124 | Expanded( 125 | child: RadioListTile( 126 | value: BackButtonBehavior.none, 127 | groupValue: backButtonBehavior, 128 | onChanged: (BackButtonBehavior? value) { 129 | setState(() { 130 | backButtonBehavior = value!; 131 | }); 132 | }, 133 | title: const Text('none'), 134 | ), 135 | ), 136 | Expanded( 137 | child: RadioListTile( 138 | value: BackButtonBehavior.ignore, 139 | groupValue: backButtonBehavior, 140 | onChanged: (BackButtonBehavior? value) { 141 | setState(() { 142 | backButtonBehavior = value!; 143 | }); 144 | }, 145 | title: const Text('ignore'), 146 | ), 147 | ), 148 | Expanded( 149 | child: RadioListTile( 150 | value: BackButtonBehavior.close, 151 | groupValue: backButtonBehavior, 152 | onChanged: (BackButtonBehavior? value) { 153 | setState(() { 154 | backButtonBehavior = value!; 155 | }); 156 | }, 157 | title: const Text('close'), 158 | ), 159 | ) 160 | ], 161 | ), 162 | ListTile( 163 | title: Text("duration: ${seconds}s"), 164 | trailing: CupertinoSlider( 165 | min: 1, 166 | max: 20, 167 | value: seconds.toDouble(), 168 | onChanged: (double value) { 169 | setState(() { 170 | seconds = value.toInt(); 171 | }); 172 | }, 173 | ), 174 | ), 175 | ListTile( 176 | title: Text("animationDuration: ${animationMilliseconds}ms"), 177 | trailing: CupertinoSlider( 178 | min: 100, 179 | max: 1000, 180 | divisions: 18, 181 | value: animationMilliseconds.toDouble(), 182 | onChanged: (double value) { 183 | setState(() { 184 | animationMilliseconds = value.toInt(); 185 | }); 186 | }, 187 | ), 188 | ), 189 | ListTile( 190 | title: Text( 191 | "animationReverseDuration: ${animationReverseMilliseconds}ms"), 192 | trailing: CupertinoSlider( 193 | min: 100, 194 | max: 1000, 195 | divisions: 18, 196 | value: animationReverseMilliseconds.toDouble(), 197 | onChanged: (double value) { 198 | setState(() { 199 | animationReverseMilliseconds = value.toInt(); 200 | }); 201 | }, 202 | ), 203 | ), 204 | ], 205 | ), 206 | ), 207 | ), 208 | ); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /example/lib/notification/notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class Notification extends StatefulWidget { 6 | @override 7 | _NotificationState createState() => _NotificationState(); 8 | } 9 | 10 | class _NotificationState extends State { 11 | bool enableSlideOff = true; 12 | bool onlyOne = true; 13 | bool crossPage = true; 14 | int seconds = 2; 15 | double contentPadding = 2; 16 | int animationMilliseconds = 200; 17 | int animationReverseMilliseconds = 200; 18 | BackButtonBehavior backButtonBehavior = BackButtonBehavior.none; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Scaffold( 23 | appBar: AppBar( 24 | title: const Text("Notification"), 25 | ), 26 | body: Container( 27 | padding: const EdgeInsets.only(top: 20), 28 | child: SingleChildScrollView( 29 | child: Column( 30 | mainAxisSize: MainAxisSize.min, 31 | children: [ 32 | ElevatedButton( 33 | onPressed: () { 34 | BotToast.showNotification( 35 | leading: (cancel) => SizedBox.fromSize( 36 | size: const Size(40, 40), 37 | child: IconButton( 38 | icon: const Icon(Icons.favorite, color: Colors.redAccent), 39 | onPressed: cancel, 40 | )), 41 | title: (_) => const Text('Notification title'), 42 | subtitle: (_) => const Text("Notification subtitle"), 43 | trailing: (cancel) => IconButton( 44 | icon: const Icon(Icons.cancel), 45 | onPressed: cancel, 46 | ), 47 | onTap: () { 48 | BotToast.showText(text: 'Tap toast'); 49 | }, 50 | onLongPress: () { 51 | BotToast.showText(text: 'Long press toast'); 52 | }, 53 | enableSlideOff: enableSlideOff, 54 | backButtonBehavior: backButtonBehavior, 55 | crossPage: crossPage, 56 | contentPadding: EdgeInsets.all(contentPadding), 57 | onlyOne: onlyOne, 58 | animationDuration: 59 | Duration(milliseconds: animationMilliseconds), 60 | animationReverseDuration: 61 | Duration(milliseconds: animationReverseMilliseconds), 62 | duration: Duration(seconds: seconds)); 63 | }, 64 | child: const Text("notification"), 65 | ), 66 | SwitchListTile( 67 | value: enableSlideOff, 68 | onChanged: (value) { 69 | setState(() { 70 | enableSlideOff = value; 71 | }); 72 | }, 73 | title: const Text("enableSlideOff: "), 74 | ), 75 | SwitchListTile( 76 | value: onlyOne, 77 | onChanged: (value) { 78 | setState(() { 79 | onlyOne = value; 80 | }); 81 | }, 82 | title: const Text("onlyOne: "), 83 | ), 84 | SwitchListTile( 85 | value: crossPage, 86 | onChanged: (value) { 87 | setState(() { 88 | crossPage = value; 89 | }); 90 | }, 91 | title: const Text("crossPage: "), 92 | ), 93 | const Center(child: Text('BackButtonBehavior'),), 94 | Row( 95 | children: [ 96 | Expanded( 97 | child: RadioListTile(value: BackButtonBehavior.none, 98 | groupValue: backButtonBehavior, 99 | onChanged: (BackButtonBehavior? value) { 100 | setState(() { 101 | backButtonBehavior = value!; 102 | }); 103 | }, 104 | title: const Text('none'),), 105 | ), 106 | Expanded( 107 | child: RadioListTile(value: BackButtonBehavior.ignore, 108 | groupValue: backButtonBehavior, 109 | onChanged: (BackButtonBehavior? value) { 110 | setState(() { 111 | backButtonBehavior = value!; 112 | }); 113 | }, 114 | title: const Text('ignore'),), 115 | ), 116 | Expanded( 117 | child: RadioListTile(value: BackButtonBehavior.close, 118 | groupValue: backButtonBehavior, 119 | onChanged: (BackButtonBehavior? value) { 120 | setState(() { 121 | backButtonBehavior = value!; 122 | }); 123 | }, 124 | title: const Text('close'),), 125 | ) 126 | ], 127 | ), 128 | ListTile( 129 | title: Text("duration: ${seconds}s"), 130 | trailing: CupertinoSlider( 131 | min: 1, 132 | max: 20, 133 | value: seconds.toDouble(), 134 | onChanged: (double value) { 135 | setState(() { 136 | seconds = value.toInt(); 137 | }); 138 | }, 139 | ), 140 | ), 141 | ListTile( 142 | title: Text("animationDuration: ${animationMilliseconds}ms"), 143 | trailing: CupertinoSlider( 144 | min: 100, 145 | max: 1000, 146 | divisions: 18, 147 | value: animationMilliseconds.toDouble(), 148 | onChanged: (double value) { 149 | setState(() { 150 | animationMilliseconds = value.toInt(); 151 | }); 152 | }, 153 | ), 154 | ), 155 | ListTile( 156 | title: Text( 157 | "animationReverseDuration: ${animationReverseMilliseconds}ms"), 158 | trailing: CupertinoSlider( 159 | min: 100, 160 | max: 1000, 161 | divisions: 18, 162 | value: animationReverseMilliseconds.toDouble(), 163 | onChanged: (double value) { 164 | setState(() { 165 | animationReverseMilliseconds = value.toInt(); 166 | }); 167 | }, 168 | ), 169 | ), 170 | ListTile( 171 | title: Text("contentPadding: $contentPadding"), 172 | trailing: CupertinoSlider( 173 | min: 1, 174 | max: 30, 175 | value: contentPadding, 176 | onChanged: (double value) { 177 | setState(() { 178 | contentPadding = value; 179 | }); 180 | }, 181 | ), 182 | ), 183 | ], 184 | ), 185 | ), 186 | ), 187 | ); 188 | } 189 | } 190 | 191 | 192 | -------------------------------------------------------------------------------- /example/lib/notification/simple_notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:bot_toast/bot_toast.dart'; 4 | 5 | class SimpleNotification extends StatefulWidget { 6 | @override 7 | _SimpleNotificationState createState() => _SimpleNotificationState(); 8 | } 9 | 10 | class _SimpleNotificationState extends State { 11 | bool enableSlideOff = true; 12 | bool hideCloseButton = false; 13 | bool onlyOne = true; 14 | bool crossPage = true; 15 | int seconds = 2; 16 | int animationMilliseconds = 200; 17 | int animationReverseMilliseconds = 200; 18 | BackButtonBehavior backButtonBehavior = BackButtonBehavior.none; 19 | 20 | @override 21 | void initState() { 22 | ///全局范围内将标准通知的动画时间修改为 1 秒 23 | BotToast.defaultOption.notification.animationDuration=const Duration(seconds: 1); 24 | BotToast.showSimpleNotification( 25 | title: "Notification title", 26 | subTitle: "Notification subtitle", 27 | enableSlideOff: enableSlideOff, 28 | hideCloseButton: hideCloseButton, 29 | onTap: () { 30 | BotToast.showText(text: 'Tap toast'); 31 | }, 32 | onLongPress: () { 33 | BotToast.showText(text: 'Long press toast'); 34 | }, 35 | backgroundColor: Colors.red, 36 | titleStyle: const TextStyle(color: Colors.white), 37 | subTitleStyle: const TextStyle(color: Colors.white), 38 | onlyOne: onlyOne, 39 | crossPage: crossPage, 40 | animationDuration: Duration(milliseconds: animationMilliseconds), 41 | animationReverseDuration: 42 | Duration(milliseconds: animationReverseMilliseconds), 43 | duration: Duration(seconds: seconds)); 44 | 45 | super.initState(); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Scaffold( 51 | appBar: AppBar( 52 | title: const Text("SimpleNotification"), 53 | ), 54 | body: Container( 55 | padding: const EdgeInsets.only(top: 50), 56 | child: SingleChildScrollView( 57 | child: Column( 58 | mainAxisSize: MainAxisSize.min, 59 | children: [ 60 | ElevatedButton( 61 | onPressed: () { 62 | BotToast.showSimpleNotification( 63 | title: "Notification title", 64 | subTitle: "Notification subtitle", 65 | enableSlideOff: enableSlideOff, 66 | hideCloseButton: hideCloseButton, 67 | onlyOne: onlyOne, 68 | crossPage: crossPage, 69 | backButtonBehavior: backButtonBehavior, 70 | onTap: () { 71 | BotToast.showText(text: 'Tap toast'); 72 | }, 73 | onLongPress: () { 74 | BotToast.showText(text: 'Long press toast'); 75 | }, 76 | animationDuration: 77 | Duration(milliseconds: animationMilliseconds), 78 | animationReverseDuration: 79 | Duration(milliseconds: animationReverseMilliseconds), 80 | duration: Duration(seconds: seconds)); 81 | }, 82 | child: const Text("simpleNotification"), 83 | ), 84 | SwitchListTile( 85 | value: enableSlideOff, 86 | onChanged: (bool value) { 87 | setState(() { 88 | enableSlideOff = value; 89 | }); 90 | }, 91 | title: const Text("enableSlideOff: "), 92 | ), 93 | SwitchListTile( 94 | value: hideCloseButton, 95 | onChanged: (bool value) { 96 | setState(() { 97 | hideCloseButton = value; 98 | }); 99 | }, 100 | title: const Text("hideCloseButton: "), 101 | ), 102 | SwitchListTile( 103 | value: onlyOne, 104 | onChanged: (bool value) { 105 | setState(() { 106 | onlyOne = value; 107 | }); 108 | }, 109 | title: const Text("onlyOne: "), 110 | ), 111 | SwitchListTile( 112 | value: crossPage, 113 | onChanged: (bool value) { 114 | setState(() { 115 | crossPage = value; 116 | }); 117 | }, 118 | title: const Text("crossPage: "), 119 | ), 120 | const Center( 121 | child: Text('BackButtonBehavior'), 122 | ), 123 | Row( 124 | children: [ 125 | Expanded( 126 | child: RadioListTile( 127 | value: BackButtonBehavior.none, 128 | groupValue: backButtonBehavior, 129 | onChanged: (BackButtonBehavior? value) { 130 | setState(() { 131 | backButtonBehavior = value!; 132 | }); 133 | }, 134 | title: const Text('none'), 135 | ), 136 | ), 137 | Expanded( 138 | child: RadioListTile( 139 | value: BackButtonBehavior.ignore, 140 | groupValue: backButtonBehavior, 141 | onChanged: (BackButtonBehavior? value) { 142 | setState(() { 143 | backButtonBehavior = value!; 144 | }); 145 | }, 146 | title: const Text('ignore'), 147 | ), 148 | ), 149 | Expanded( 150 | child: RadioListTile( 151 | value: BackButtonBehavior.close, 152 | groupValue: backButtonBehavior, 153 | onChanged: (BackButtonBehavior? value) { 154 | setState(() { 155 | backButtonBehavior = value!; 156 | }); 157 | }, 158 | title: const Text('close'), 159 | ), 160 | ) 161 | ], 162 | ), 163 | ListTile( 164 | title: Text("duration: ${seconds}s"), 165 | trailing: CupertinoSlider( 166 | min: 1, 167 | max: 20, 168 | divisions: 20, 169 | value: seconds.toDouble(), 170 | onChanged: (double value) { 171 | setState(() { 172 | seconds = value.toInt(); 173 | }); 174 | }, 175 | ), 176 | ), 177 | ListTile( 178 | title: Text("animationDuration: ${animationMilliseconds}ms"), 179 | trailing: CupertinoSlider( 180 | min: 100, 181 | max: 1000, 182 | divisions: 18, 183 | value: animationMilliseconds.toDouble(), 184 | onChanged: (double value) { 185 | setState(() { 186 | animationMilliseconds = value.toInt(); 187 | }); 188 | }, 189 | ), 190 | ), 191 | ListTile( 192 | title: Text( 193 | "animationReverseDuration: ${animationReverseMilliseconds}ms"), 194 | trailing: CupertinoSlider( 195 | min: 100, 196 | max: 1000, 197 | divisions: 18, 198 | value: animationReverseMilliseconds.toDouble(), 199 | onChanged: (double value) { 200 | setState(() { 201 | animationReverseMilliseconds = value.toInt(); 202 | }); 203 | }, 204 | ), 205 | ), 206 | ], 207 | ), 208 | ), 209 | ), 210 | ); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /example/lib/text/custom_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class CustomText extends StatefulWidget { 6 | @override 7 | _CustomTextState createState() => _CustomTextState(); 8 | } 9 | 10 | class _CustomTextState extends State { 11 | int seconds = 2; 12 | bool crossPage = true; 13 | bool clickClose = false; 14 | bool ignoreContentClick = false; 15 | bool onlyOne = true; 16 | int backgroundColor = 0x00000000; 17 | int animationMilliseconds = 200; 18 | int animationReverseMilliseconds = 200; 19 | BackButtonBehavior backButtonBehavior = BackButtonBehavior.none; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: AppBar( 25 | title: const Text("CustomText"), 26 | ), 27 | body: Container( 28 | padding: const EdgeInsets.only(top: 10), 29 | child: SingleChildScrollView( 30 | child: Column( 31 | mainAxisSize: MainAxisSize.min, 32 | children: [ 33 | ElevatedButton( 34 | onPressed: () { 35 | BotToast.showCustomText( 36 | duration: Duration(seconds: seconds), 37 | onlyOne: onlyOne, 38 | clickClose: clickClose, 39 | crossPage: crossPage, 40 | ignoreContentClick: ignoreContentClick, 41 | backgroundColor: Color(backgroundColor), 42 | backButtonBehavior: backButtonBehavior, 43 | animationDuration: 44 | Duration(milliseconds: animationMilliseconds), 45 | animationReverseDuration: 46 | Duration(milliseconds: animationReverseMilliseconds), 47 | toastBuilder: (_) => Align( 48 | alignment: const Alignment(0, 0.8), 49 | child: Card( 50 | child: Row( 51 | mainAxisSize: MainAxisSize.min, 52 | children: [ 53 | IconButton( 54 | icon: const Icon( 55 | Icons.favorite_border, 56 | color: Colors.redAccent, 57 | ), 58 | onPressed: () { 59 | BotToast.showSimpleNotification( 60 | title: "Notification one", 61 | crossPage: crossPage, 62 | closeIcon: const Icon( 63 | Icons.favorite, 64 | color: Colors.redAccent, 65 | ), 66 | duration: Duration(seconds: seconds)); 67 | }), 68 | const Padding( 69 | padding: 70 | EdgeInsets.symmetric(horizontal: 8), 71 | child: Text("Text one"), 72 | ), 73 | ], 74 | ), 75 | ), 76 | ), 77 | ); 78 | }, 79 | child: const Text("TextToast"), 80 | ), 81 | ListTile( 82 | title: Text("duration: ${seconds}s"), 83 | trailing: CupertinoSlider( 84 | min: 1, 85 | max: 20, 86 | value: seconds.toDouble(), 87 | onChanged: (double value) { 88 | setState(() { 89 | seconds = value.toInt(); 90 | }); 91 | }, 92 | ), 93 | ), 94 | ListTile( 95 | title: Text("animationDuration: ${animationMilliseconds}ms"), 96 | trailing: CupertinoSlider( 97 | min: 100, 98 | max: 1000, 99 | divisions: 18, 100 | value: animationMilliseconds.toDouble(), 101 | onChanged: (double value) { 102 | setState(() { 103 | animationMilliseconds = value.toInt(); 104 | }); 105 | }, 106 | ), 107 | ), 108 | ListTile( 109 | title: Text( 110 | "animationReverseDuration: ${animationReverseMilliseconds}ms"), 111 | trailing: CupertinoSlider( 112 | min: 100, 113 | max: 1000, 114 | divisions: 18, 115 | value: animationReverseMilliseconds.toDouble(), 116 | onChanged: (double value) { 117 | setState(() { 118 | animationReverseMilliseconds = value.toInt(); 119 | }); 120 | }, 121 | ), 122 | ), 123 | const Center(child: Text('BackButtonBehavior'),), 124 | Row( 125 | children: [ 126 | Expanded( 127 | child: RadioListTile(value: BackButtonBehavior.none, 128 | groupValue: backButtonBehavior, 129 | onChanged: (BackButtonBehavior? value) { 130 | setState(() { 131 | backButtonBehavior = value!; 132 | }); 133 | }, 134 | title: const Text('none'),), 135 | ), 136 | Expanded( 137 | child: RadioListTile(value: BackButtonBehavior.ignore, 138 | groupValue: backButtonBehavior, 139 | onChanged: (BackButtonBehavior? value) { 140 | setState(() { 141 | backButtonBehavior = value!; 142 | }); 143 | }, 144 | title: const Text('ignore'),), 145 | ), 146 | Expanded( 147 | child: RadioListTile(value: BackButtonBehavior.close, 148 | groupValue: backButtonBehavior, 149 | onChanged: (BackButtonBehavior? value) { 150 | setState(() { 151 | backButtonBehavior = value!; 152 | }); 153 | }, 154 | title: const Text('close'),), 155 | ) 156 | ], 157 | ), 158 | SwitchListTile( 159 | value: onlyOne, 160 | onChanged: (value) { 161 | setState(() { 162 | onlyOne = value; 163 | }); 164 | }, 165 | title: const Text("onlyOne: "), 166 | ), 167 | SwitchListTile( 168 | value: clickClose, 169 | onChanged: (value) { 170 | setState(() { 171 | clickClose = value; 172 | }); 173 | }, 174 | title: const Text("clickClose: "), 175 | ), 176 | SwitchListTile( 177 | value: crossPage, 178 | onChanged: (value) { 179 | setState(() { 180 | crossPage = value; 181 | }); 182 | }, 183 | title: const Text("crossPage: "), 184 | ), 185 | SwitchListTile( 186 | value: ignoreContentClick, 187 | onChanged: (value) { 188 | setState(() { 189 | ignoreContentClick = value; 190 | }); 191 | }, 192 | title: const Text("ignoreContentClick: "), 193 | ), 194 | 195 | 196 | ListTile( 197 | title: Row( 198 | mainAxisSize: MainAxisSize.min, 199 | children: [ 200 | const Expanded(child: Text("backgroundColor:")), 201 | Container( 202 | height: 20, 203 | width: 20, 204 | color: Color(backgroundColor), 205 | ) 206 | ], 207 | ), 208 | trailing: CupertinoSlider( 209 | min: 0x00000000, 210 | max: 0xffffffff, 211 | value: backgroundColor.toDouble(), 212 | onChanged: (double value) { 213 | setState(() { 214 | backgroundColor = value.toInt(); 215 | }); 216 | }, 217 | ), 218 | ), 219 | ], 220 | ), 221 | ), 222 | ), 223 | ); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /example/lib/text/text.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class TextSample extends StatefulWidget { 6 | @override 7 | _TextSampleState createState() => _TextSampleState(); 8 | } 9 | 10 | class _TextSampleState extends State { 11 | int seconds = 2; 12 | bool clickClose = false; 13 | bool onlyOne = true; 14 | bool crossPage = true; 15 | 16 | double align = 0.8; 17 | int fontSize = 17; 18 | int borderRadius = 8; 19 | int fontColor = 0xFFFFFFFF; 20 | int backgroundColor = 0x00000000; 21 | int contentColor = 0x8A000000; 22 | int animationMilliseconds = 200; 23 | int animationReverseMilliseconds = 200; 24 | BackButtonBehavior backButtonBehavior = BackButtonBehavior.none; 25 | 26 | int index = 0; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Scaffold( 31 | appBar: AppBar( 32 | title: const Text("TextToast"), 33 | ), 34 | body: Container( 35 | padding: const EdgeInsets.only(top: 10), 36 | child: SingleChildScrollView( 37 | child: Column( 38 | mainAxisSize: MainAxisSize.min, 39 | children: [ 40 | ElevatedButton( 41 | onPressed: () { 42 | BotToast.showText( 43 | text: (++index).isOdd 44 | ? "Text one" 45 | : "Text two", 46 | duration: Duration(seconds: seconds), 47 | onlyOne: onlyOne, 48 | clickClose: clickClose, 49 | crossPage: crossPage, 50 | backButtonBehavior: backButtonBehavior, 51 | align: Alignment(0, align), 52 | animationDuration: 53 | Duration(milliseconds: animationMilliseconds), 54 | animationReverseDuration: 55 | Duration(milliseconds: animationReverseMilliseconds), 56 | textStyle: TextStyle( 57 | color: Color(fontColor), 58 | fontSize: fontSize.toDouble()), 59 | borderRadius: 60 | BorderRadius.circular(borderRadius.toDouble()), 61 | backgroundColor: Color(backgroundColor), 62 | contentColor: Color(contentColor)); 63 | }, 64 | child: const Text("TextToast"), 65 | ), 66 | ListTile( 67 | title: Text("duration: ${seconds}s"), 68 | trailing: CupertinoSlider( 69 | min: 1, 70 | max: 20, 71 | value: seconds.toDouble(), 72 | onChanged: (double value) { 73 | setState(() { 74 | seconds = value.toInt(); 75 | }); 76 | }, 77 | ), 78 | ), 79 | ListTile( 80 | title: Text("animationDuration: ${animationMilliseconds}ms"), 81 | trailing: CupertinoSlider( 82 | min: 100, 83 | max: 1000, 84 | divisions: 18, 85 | value: animationMilliseconds.toDouble(), 86 | onChanged: (double value) { 87 | setState(() { 88 | animationMilliseconds = value.toInt(); 89 | }); 90 | }, 91 | ), 92 | ), 93 | ListTile( 94 | title: Text( 95 | "animationReverseDuration: ${animationReverseMilliseconds}ms"), 96 | trailing: CupertinoSlider( 97 | min: 100, 98 | max: 1000, 99 | divisions: 18, 100 | value: animationReverseMilliseconds.toDouble(), 101 | onChanged: (double value) { 102 | setState(() { 103 | animationReverseMilliseconds = value.toInt(); 104 | }); 105 | }, 106 | ), 107 | ), 108 | const Center(child: Text('BackButtonBehavior'),), 109 | Row( 110 | children: [ 111 | Expanded( 112 | child: RadioListTile(value: BackButtonBehavior.none, 113 | groupValue: backButtonBehavior, 114 | onChanged: (BackButtonBehavior? value) { 115 | setState(() { 116 | backButtonBehavior = value!; 117 | }); 118 | }, 119 | title: const Text('none'),), 120 | ), 121 | Expanded( 122 | child: RadioListTile(value: BackButtonBehavior.ignore, 123 | groupValue: backButtonBehavior, 124 | onChanged: (BackButtonBehavior? value) { 125 | setState(() { 126 | backButtonBehavior = value!; 127 | }); 128 | }, 129 | title: const Text('ignore'),), 130 | ), 131 | Expanded( 132 | child: RadioListTile(value: BackButtonBehavior.close, 133 | groupValue: backButtonBehavior, 134 | onChanged: (BackButtonBehavior? value) { 135 | setState(() { 136 | backButtonBehavior = value!; 137 | }); 138 | }, 139 | title: const Text('close'),), 140 | ) 141 | ], 142 | ), 143 | SwitchListTile( 144 | value: onlyOne, 145 | onChanged: (value) { 146 | setState(() { 147 | onlyOne = value; 148 | }); 149 | }, 150 | title: const Text("onlyOne: "), 151 | ), 152 | SwitchListTile( 153 | value: clickClose, 154 | onChanged: (value) { 155 | setState(() { 156 | clickClose = value; 157 | }); 158 | }, 159 | title: const Text("clickClose: "), 160 | ), 161 | SwitchListTile( 162 | value: crossPage, 163 | onChanged: (value) { 164 | setState(() { 165 | crossPage = value; 166 | }); 167 | }, 168 | title: const Text("crossPage: "), 169 | ), 170 | ListTile( 171 | title: Text("align: Alignment(0, $align)"), 172 | trailing: CupertinoSlider( 173 | min: -1, 174 | max: 1, 175 | value: align, 176 | onChanged: (double value) { 177 | setState(() { 178 | align = double.parse(value.toStringAsFixed(2)); 179 | }); 180 | }, 181 | ), 182 | ), 183 | ListTile( 184 | title: Text("borderRadius: $borderRadius"), 185 | trailing: CupertinoSlider( 186 | min: 1, 187 | max: 20, 188 | value: borderRadius.toDouble(), 189 | onChanged: (double value) { 190 | setState(() { 191 | borderRadius = value.toInt(); 192 | }); 193 | }, 194 | ), 195 | ), 196 | ListTile( 197 | title: Text("fontSize: $fontSize"), 198 | trailing: CupertinoSlider( 199 | min: 10, 200 | max: 30, 201 | value: fontSize.toDouble(), 202 | onChanged: (double value) { 203 | setState(() { 204 | fontSize = value.toInt(); 205 | }); 206 | }, 207 | ), 208 | ), 209 | ListTile( 210 | title: Row( 211 | mainAxisSize: MainAxisSize.min, 212 | children: [ 213 | const Text("fontColor: "), 214 | Container( 215 | height: 20, 216 | width: 20, 217 | color: Color(fontColor), 218 | ) 219 | ], 220 | ), 221 | trailing: CupertinoSlider( 222 | min: 0x00000000, 223 | max: 0xffffffff, 224 | value: fontColor.toDouble(), 225 | onChanged: (double value) { 226 | setState(() { 227 | fontColor = value.toInt(); 228 | }); 229 | }, 230 | ), 231 | ), 232 | ListTile( 233 | title: Row( 234 | mainAxisSize: MainAxisSize.min, 235 | children: [ 236 | const Text( 237 | "background:", 238 | overflow: TextOverflow.fade, 239 | ), 240 | Container( 241 | height: 20, 242 | width: 20, 243 | color: Color(backgroundColor), 244 | ) 245 | ], 246 | ), 247 | trailing: CupertinoSlider( 248 | min: 0x00000000, 249 | max: 0xffffffff, 250 | value: backgroundColor.toDouble(), 251 | onChanged: (double value) { 252 | setState(() { 253 | backgroundColor = value.toInt(); 254 | }); 255 | }, 256 | ), 257 | ), 258 | ListTile( 259 | title: Row( 260 | mainAxisSize: MainAxisSize.min, 261 | children: [ 262 | const Text("contentColor: "), 263 | Container( 264 | height: 20, 265 | width: 20, 266 | color: Color(contentColor), 267 | ) 268 | ], 269 | ), 270 | trailing: CupertinoSlider( 271 | min: 0x00000000, 272 | max: 0xffffffff, 273 | value: contentColor.toDouble(), 274 | onChanged: (double value) { 275 | setState(() { 276 | contentColor = value.toInt(); 277 | }); 278 | }, 279 | ), 280 | ), 281 | 282 | ], 283 | ), 284 | ), 285 | ), 286 | ); 287 | } 288 | } 289 | 290 | 291 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.12.0" 18 | 19 | dependencies: 20 | bot_toast: 21 | path: ../ 22 | flutter: 23 | sdk: flutter 24 | 25 | # The following adds the Cupertino Icons font to your application. 26 | # Use with the CupertinoIcons class for iOS style icons. 27 | 28 | dev_dependencies: 29 | flutter_test: 30 | sdk: flutter 31 | 32 | 33 | # For information on the generic Dart part of this file, see the 34 | # following page: https://www.dartlang.org/tools/pub/pubspec 35 | 36 | # The following section is specific to Flutter. 37 | flutter: 38 | 39 | # The following line ensures that the Material Icons font is 40 | # included with your application, so that you can use the icons in 41 | # the material Icons class. 42 | uses-material-design: true 43 | 44 | # To add assets to your application, add an assets section, like this: 45 | # assets: 46 | # - images/a_dot_burr.jpeg 47 | # - images/a_dot_ham.jpeg 48 | 49 | # An image asset can refer to one or more resolution-specific "variants", see 50 | # https://flutter.dev/assets-and-images/#resolution-aware. 51 | 52 | # For details regarding adding assets from package dependencies, see 53 | # https://flutter.dev/assets-and-images/#from-packages 54 | 55 | # To add custom fonts to your application, add a fonts section here, 56 | # in this "flutter" section. Each entry in this list should have a 57 | # "family" key with the font family name, and a "fonts" key with a 58 | # list giving the asset and other descriptors for the font. For 59 | # example: 60 | # fonts: 61 | # - family: Schyler 62 | # fonts: 63 | # - asset: fonts/Schyler-Regular.ttf 64 | # - asset: fonts/Schyler-Italic.ttf 65 | # style: italic 66 | # - family: Trajan Pro 67 | # fonts: 68 | # - asset: fonts/TrajanPro.ttf 69 | # - asset: fonts/TrajanPro_Bold.ttf 70 | # weight: 700 71 | # 72 | # For details regarding fonts from package dependencies, 73 | # see https://flutter.dev/custom-fonts/#from-packages 74 | -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/bot_toast.dart: -------------------------------------------------------------------------------- 1 | library bot_toast; 2 | 3 | export 'src/basis.dart'; 4 | export 'src/bot_toast_init.dart' show BotToastInit; 5 | export 'src/toast.dart'; 6 | export 'src/toast_navigator_observer.dart'; 7 | -------------------------------------------------------------------------------- /lib/src/basis.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/scheduler.dart'; 3 | 4 | ///Toast的关闭函数,调用将会提前关闭对应的Toast 5 | typedef CancelFunc = void Function(); 6 | typedef ToastBuilder = Widget Function(CancelFunc cancelFunc); 7 | typedef FutureFunc = Future Function(); 8 | typedef PopTestFunc = bool Function(); 9 | 10 | typedef WrapWidget = Widget Function(CancelFunc cancelFunc, Widget widget); 11 | typedef WrapAnimation = Widget Function(AnimationController controller, CancelFunc cancelFunc, Widget widget); 12 | 13 | class TickerProviderImpl extends TickerProvider { 14 | @override 15 | Ticker createTicker(TickerCallback onTick) { 16 | return Ticker(onTick); 17 | } 18 | } 19 | 20 | ///此枚举会决定Toast对于物理返回键的处理方式 21 | class BackButtonBehavior { 22 | ///不做任何处理 23 | 24 | static const BackButtonBehavior none = BackButtonBehavior(0, 'none'); 25 | 26 | ///拦截此次点击事件 27 | static const BackButtonBehavior ignore = BackButtonBehavior(1, 'ignore'); 28 | 29 | ///拦截此次点击事件,并把Toast关闭 30 | static const BackButtonBehavior close = BackButtonBehavior(2, 'close'); 31 | 32 | const BackButtonBehavior(this.index, this.name); 33 | 34 | final int index; 35 | final String name; 36 | 37 | @override 38 | String toString() { 39 | return 'BackButtonBehavior.$name'; 40 | } 41 | } 42 | 43 | //请不要调整顺序!! 44 | //前面是主方向,后面是对齐比如 45 | //topLeft,就是希望在上面,并且左对齐目标 46 | class PreferDirection { 47 | static const PreferDirection topLeft = PreferDirection(0, 'topLeft'); 48 | static const PreferDirection topCenter = PreferDirection(1, 'topCenter'); 49 | static const PreferDirection topRight = PreferDirection(2, 'topRight'); 50 | static const PreferDirection bottomLeft = PreferDirection(3, 'bottomLeft'); 51 | static const PreferDirection bottomCenter = PreferDirection(4, 'bottomCenter'); 52 | static const PreferDirection bottomRight = PreferDirection(5, 'bottomRight'); 53 | static const PreferDirection leftTop = PreferDirection(6, 'leftTop'); 54 | static const PreferDirection leftCenter = PreferDirection(7, 'leftCenter'); 55 | static const PreferDirection leftBottom = PreferDirection(8, 'leftBottom'); 56 | static const PreferDirection rightTop = PreferDirection(9, 'rightTop'); 57 | static const PreferDirection rightCenter = PreferDirection(10, 'rightCenter'); 58 | static const PreferDirection rightBottom = PreferDirection(11, 'rightBottom'); 59 | 60 | const PreferDirection(this.index, this.name); 61 | 62 | final int index; 63 | final String name; 64 | 65 | @override 66 | String toString() { 67 | return 'PreferDirection.$name'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/bot_toast_init.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'basis.dart'; 4 | import 'bot_toast_manager.dart'; 5 | 6 | final GlobalKey _key = GlobalKey(); 7 | 8 | BotToastManagerState get botToastManager { 9 | assert(_key.currentState != null); 10 | return _key.currentState!; 11 | } 12 | 13 | class BotToastWidgetsBindingObserver with WidgetsBindingObserver { 14 | BotToastWidgetsBindingObserver._() : _listener = [] { 15 | //Compatible with flutter 3.x 16 | (WidgetsBinding.instance as dynamic).addObserver(this); 17 | } 18 | 19 | final List _listener; 20 | 21 | static final BotToastWidgetsBindingObserver _singleton = 22 | BotToastWidgetsBindingObserver._(); 23 | 24 | static BotToastWidgetsBindingObserver get singleton => _singleton; 25 | 26 | VoidCallback registerPopListener(PopTestFunc popTestFunc) { 27 | _listener.add(popTestFunc); 28 | return () { 29 | _listener.remove(popTestFunc); 30 | }; 31 | } 32 | 33 | @override 34 | Future didPopRoute() async { 35 | if (_listener.isNotEmpty) { 36 | final clone = _listener.reversed.toList(growable: false); 37 | for (PopTestFunc popTest in clone) { 38 | if (popTest()) return true; 39 | } 40 | } 41 | return super.didPopRoute(); 42 | } 43 | } 44 | 45 | // ignore: non_constant_identifier_names 46 | TransitionBuilder BotToastInit() { 47 | //确保提前初始化,保证WidgetsBinding.instance.addObserver(this);的顺序 48 | 49 | //ignore: unnecessary_statements 50 | BotToastWidgetsBindingObserver._singleton; 51 | return (_, Widget? child) { 52 | return BotToastManager(key: _key, child: child!); 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/bot_toast_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/src/toast_widget/toast_widget.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/scheduler.dart'; 4 | 5 | void safeRun(void Function() callback) { 6 | //Compatible with flutter 3.x 7 | final SchedulerBinding instance = SchedulerBinding.instance as dynamic; 8 | instance.addPostFrameCallback((_) { 9 | callback(); 10 | }); 11 | instance.ensureVisualUpdate(); 12 | } 13 | 14 | class BotToastManager extends StatefulWidget { 15 | final Widget child; 16 | 17 | const BotToastManager({Key? key, required this.child}) : super(key: key); 18 | 19 | @override 20 | BotToastManagerState createState() => BotToastManagerState(); 21 | } 22 | 23 | class _IndexWidget extends StatelessWidget { 24 | final Widget child; 25 | 26 | final int index; 27 | 28 | const _IndexWidget({Key? key, required this.child, required this.index}) 29 | : super(key: key); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return child; 34 | } 35 | } 36 | 37 | class BotToastManagerState extends State { 38 | final Map> _map = 39 | >{}; 40 | 41 | final Set _pending = {}; 42 | 43 | int _nextAddIndex = 0; 44 | 45 | List<_IndexWidget> get _children => 46 | _map.values.fold>(<_IndexWidget>[], (value, items) { 47 | return value..addAll(items.values); 48 | }) 49 | ..sort((a, b) => a.index.compareTo(b.index)); 50 | 51 | void insert(String groupKey, UniqueKey key, Widget widget) { 52 | safeRun(() { 53 | _map[groupKey] ??= {}; 54 | final uniqueKey = UniqueKey(); 55 | 56 | widget = ProxyInitState( 57 | initStateCallback: () { 58 | _pending.remove(key); 59 | }, 60 | child: widget, 61 | ); 62 | 63 | widget = ProxyDispose( 64 | child: widget, 65 | disposeCallback: () { 66 | _map[groupKey]?.remove(key); 67 | }, 68 | ); 69 | _map[groupKey]![key] = _IndexWidget( 70 | key: uniqueKey, 71 | index: ++_nextAddIndex, 72 | child: widget, 73 | ); 74 | _pending.add(key); 75 | _update(); 76 | }); 77 | } 78 | 79 | void remove(String groupKey, UniqueKey key) { 80 | safeRun(() { 81 | if (_pending.contains(key)) { 82 | //首桢渲染完成之前,就被删除,需要确保ProxyDispose被安装,因此要放到下一帧进行删除 83 | return remove(groupKey, key); 84 | } else { 85 | _map[groupKey]?.remove(key); 86 | _update(); 87 | } 88 | }); 89 | } 90 | 91 | void removeAll(String groupKey) { 92 | safeRun(() { 93 | if (_map[groupKey] == null) { 94 | return; 95 | } 96 | 97 | _map[groupKey]!.removeWhere((key, _) => !_pending.contains(key)); 98 | _update(); 99 | 100 | _map[groupKey]!.forEach((key, value) { 101 | return remove(groupKey, key); 102 | }); 103 | }); 104 | } 105 | 106 | void cleanAll() { 107 | safeRun(() { 108 | _map.forEach((groupKey, value) { 109 | value.removeWhere((key, _) => !_pending.contains(key)); 110 | 111 | if (value.isNotEmpty) { 112 | value.forEach((key, value) { 113 | return remove(groupKey, key); 114 | }); 115 | } 116 | }); 117 | _update(); 118 | }); 119 | } 120 | 121 | void _update() { 122 | if (mounted) { 123 | setState(() {}); 124 | } 125 | } 126 | 127 | @override 128 | Widget build(BuildContext context) { 129 | return Stack( 130 | children: [ 131 | widget.child, 132 | ]..addAll(_children), 133 | ); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lib/src/keyboard_safe_area.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | //该类是基于自己offset距离屏幕底部为0的下使用的 4 | class KeyboardSafeArea extends StatelessWidget { 5 | final Widget child; 6 | 7 | final bool enable; 8 | 9 | const KeyboardSafeArea({Key? key,required this.child,required this.enable}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | if (!enable) { 14 | return child; 15 | } 16 | MediaQueryData data = MediaQuery.of(context); 17 | return Padding( 18 | padding: EdgeInsets.only(bottom: data.viewInsets.bottom), 19 | child: child, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/keyboard_visibility.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class KeyboardVisibility extends StatefulWidget { 4 | final void Function(bool open) onKeyboardVisibilityChanged; 5 | final Widget child; 6 | 7 | const KeyboardVisibility( 8 | {required this.onKeyboardVisibilityChanged, 9 | required this.child, 10 | Key? key}) 11 | : super(key: key); 12 | 13 | @override 14 | _KeyboardVisibilityState createState() => _KeyboardVisibilityState(); 15 | } 16 | 17 | class _KeyboardVisibilityState extends State 18 | with WidgetsBindingObserver { 19 | 20 | //Compatible with flutter 3.x 21 | WidgetsBinding get _instance => WidgetsBinding.instance as dynamic; 22 | 23 | @override 24 | void initState() { 25 | _instance.addObserver(this); 26 | super.initState(); 27 | } 28 | 29 | @override 30 | void didChangeMetrics() { 31 | super.didChangeMetrics(); 32 | if (MediaQuery.of(context).viewInsets.bottom == 0) { 33 | widget.onKeyboardVisibilityChanged(false); 34 | } else { 35 | widget.onKeyboardVisibilityChanged(true); 36 | } 37 | } 38 | 39 | @override 40 | void dispose() { 41 | _instance.removeObserver(this); 42 | super.dispose(); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return widget.child; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/nil.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: prefer_const_constructors 2 | // ignore_for_file: always_specify_types 3 | 4 | import 'dart:math'; 5 | 6 | import 'package:flutter/widgets.dart'; 7 | 8 | import 'basis.dart'; 9 | 10 | abstract class Nil { 11 | @override 12 | int get hashCode => identityHashCode(this); 13 | 14 | @override 15 | bool operator ==(Object other) { 16 | return other.runtimeType == runtimeType && other.hashCode == hashCode; 17 | } 18 | } 19 | 20 | class _Object extends Object with Nil { 21 | const _Object() : super(); 22 | } 23 | 24 | class _Icon extends Icon with Nil { 25 | const _Icon() : super(null); 26 | } 27 | 28 | class _Duration extends Duration with Nil { 29 | const _Duration() : super(); 30 | } 31 | 32 | class _BackButtonBehavior extends BackButtonBehavior with Nil { 33 | const _BackButtonBehavior() : super(0, ''); 34 | } 35 | 36 | class _PreferDirection extends PreferDirection with Nil { 37 | const _PreferDirection() : super(0, ''); 38 | } 39 | 40 | class _TextStyle extends TextStyle with Nil { 41 | const _TextStyle() : super(); 42 | } 43 | 44 | class _Color extends Color with Nil { 45 | const _Color() : super(0); 46 | } 47 | 48 | class _Alignment extends Alignment with Nil { 49 | const _Alignment() : super(0, 0); 50 | } 51 | 52 | class _EdgeInsets extends EdgeInsets with Nil { 53 | const _EdgeInsets() : super.all(0); 54 | } 55 | 56 | class _BorderRadius extends BorderRadius with Nil { 57 | const _BorderRadius() : super.only(); 58 | } 59 | 60 | class _List with Nil implements List { 61 | const _List(); 62 | 63 | @override 64 | E get first => throw UnimplementedError(); 65 | 66 | @override 67 | set first(E first) { 68 | throw UnimplementedError(); 69 | } 70 | 71 | @override 72 | E get last => throw UnimplementedError(); 73 | 74 | @override 75 | set last(E last) { 76 | throw UnimplementedError(); 77 | } 78 | 79 | @override 80 | int get length => throw UnimplementedError(); 81 | 82 | @override 83 | set length(int length) { 84 | throw UnimplementedError(); 85 | } 86 | 87 | @override 88 | List operator +(List other) { 89 | throw UnimplementedError(); 90 | } 91 | 92 | @override 93 | E operator [](int index) { 94 | throw UnimplementedError(); 95 | } 96 | 97 | @override 98 | void operator []=(int index, E value) {} 99 | 100 | @override 101 | void add(E value) {} 102 | 103 | @override 104 | void addAll(Iterable iterable) {} 105 | 106 | @override 107 | bool any(bool Function(E element) test) { 108 | throw UnimplementedError(); 109 | } 110 | 111 | @override 112 | Map asMap() { 113 | throw UnimplementedError(); 114 | } 115 | 116 | @override 117 | List cast() { 118 | throw UnimplementedError(); 119 | } 120 | 121 | @override 122 | void clear() {} 123 | 124 | @override 125 | bool contains(Object? element) { 126 | throw UnimplementedError(); 127 | } 128 | 129 | @override 130 | E elementAt(int index) { 131 | throw UnimplementedError(); 132 | } 133 | 134 | @override 135 | bool every(bool Function(E element) test) { 136 | throw UnimplementedError(); 137 | } 138 | 139 | @override 140 | Iterable expand(Iterable Function(E element) f) { 141 | throw UnimplementedError(); 142 | } 143 | 144 | @override 145 | void fillRange(int start, int end, [E? fillValue]) {} 146 | 147 | @override 148 | E firstWhere(bool test(E element), {E orElse()?}) { 149 | throw UnimplementedError(); 150 | } 151 | 152 | @override 153 | T fold(T initialValue, T Function(T previousValue, E element) combine) { 154 | throw UnimplementedError(); 155 | } 156 | 157 | @override 158 | Iterable followedBy(Iterable other) { 159 | throw UnimplementedError(); 160 | } 161 | 162 | @override 163 | void forEach(void Function(E element) f) {} 164 | 165 | @override 166 | Iterable getRange(int start, int end) { 167 | throw UnimplementedError(); 168 | } 169 | 170 | @override 171 | int indexOf(E element, [int start = 0]) { 172 | throw UnimplementedError(); 173 | } 174 | 175 | @override 176 | int indexWhere(bool Function(E element) test, [int start = 0]) { 177 | throw UnimplementedError(); 178 | } 179 | 180 | @override 181 | void insert(int index, E element) {} 182 | 183 | @override 184 | void insertAll(int index, Iterable iterable) {} 185 | 186 | @override 187 | bool get isEmpty => throw UnimplementedError(); 188 | 189 | @override 190 | bool get isNotEmpty => throw UnimplementedError(); 191 | 192 | @override 193 | Iterator get iterator => throw UnimplementedError(); 194 | 195 | @override 196 | String join([String separator = '']) { 197 | throw UnimplementedError(); 198 | } 199 | 200 | @override 201 | int lastIndexOf(E element, [int? start]) { 202 | throw UnimplementedError(); 203 | } 204 | 205 | @override 206 | int lastIndexWhere(bool Function(E element) test, [int? start]) { 207 | throw UnimplementedError(); 208 | } 209 | 210 | @override 211 | E lastWhere(bool test(E element), {E orElse()?}) { 212 | throw UnimplementedError(); 213 | } 214 | 215 | @override 216 | Iterable map(T Function(E e) f) { 217 | throw UnimplementedError(); 218 | } 219 | 220 | @override 221 | E reduce(E Function(E value, E element) combine) { 222 | throw UnimplementedError(); 223 | } 224 | 225 | @override 226 | bool remove(Object? value) { 227 | throw UnimplementedError(); 228 | } 229 | 230 | @override 231 | E removeAt(int index) { 232 | throw UnimplementedError(); 233 | } 234 | 235 | @override 236 | E removeLast() { 237 | throw UnimplementedError(); 238 | } 239 | 240 | @override 241 | void removeRange(int start, int end) {} 242 | 243 | @override 244 | void removeWhere(bool Function(E element) test) {} 245 | 246 | @override 247 | void replaceRange(int start, int end, Iterable replacement) {} 248 | 249 | @override 250 | void retainWhere(bool Function(E element) test) {} 251 | 252 | @override 253 | Iterable get reversed => throw UnimplementedError(); 254 | 255 | @override 256 | void setAll(int index, Iterable iterable) {} 257 | 258 | @override 259 | void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) {} 260 | 261 | @override 262 | void shuffle([Random? random]) {} 263 | 264 | @override 265 | E get single => throw UnimplementedError(); 266 | 267 | @override 268 | E singleWhere(bool test(E element), {E orElse()?}) { 269 | throw UnimplementedError(); 270 | } 271 | 272 | @override 273 | Iterable skip(int count) { 274 | throw UnimplementedError(); 275 | } 276 | 277 | @override 278 | Iterable skipWhile(bool Function(E value) test) { 279 | throw UnimplementedError(); 280 | } 281 | 282 | @override 283 | void sort([int compare(E a, E b)?]) {} 284 | 285 | @override 286 | List sublist(int start, [int? end]) { 287 | throw UnimplementedError(); 288 | } 289 | 290 | @override 291 | Iterable take(int count) { 292 | throw UnimplementedError(); 293 | } 294 | 295 | @override 296 | Iterable takeWhile(bool Function(E value) test) { 297 | throw UnimplementedError(); 298 | } 299 | 300 | @override 301 | List toList({bool growable = true}) { 302 | throw UnimplementedError(); 303 | } 304 | 305 | @override 306 | Set toSet() { 307 | throw UnimplementedError(); 308 | } 309 | 310 | @override 311 | Iterable where(bool Function(E element) test) { 312 | throw UnimplementedError(); 313 | } 314 | 315 | @override 316 | Iterable whereType() { 317 | throw UnimplementedError(); 318 | } 319 | } 320 | 321 | const _Object nil = _Object(); 322 | const _Icon nilIcon = _Icon(); 323 | const _Duration nilDuration = _Duration(); 324 | const _BackButtonBehavior nilBackButtonBehavior = _BackButtonBehavior(); 325 | const _PreferDirection nilPreferDirection = _PreferDirection(); 326 | const _TextStyle nilTextStyle = _TextStyle(); 327 | const _Color nilColor = _Color(); 328 | const _Alignment nilAlignment = _Alignment(); 329 | const _EdgeInsets nilEdgeInsets = _EdgeInsets(); 330 | const _BorderRadius nilBorderRadius = _BorderRadius(); 331 | const List nilDismissDirectionList = _List(); 332 | 333 | Widget nilWrapAnimation(AnimationController _, CancelFunc __, Widget ___) => const Placeholder(); 334 | 335 | Widget nilWrapWidget(CancelFunc __, Widget ___) => const Placeholder(); 336 | 337 | void nilVoidCallback() {} 338 | 339 | Widget nilToastBuilder(CancelFunc cancelFunc) => const Placeholder(); 340 | 341 | Future nilFutureFunc() async => {}; 342 | 343 | final Set nilSet = { 344 | nil, 345 | nilIcon, 346 | nilDuration, 347 | nilBackButtonBehavior, 348 | nilPreferDirection, 349 | nilTextStyle, 350 | nilColor, 351 | nilAlignment, 352 | nilDismissDirectionList, 353 | nilBorderRadius, 354 | nilEdgeInsets, 355 | nilWrapAnimation, 356 | nilWrapWidget, 357 | nilVoidCallback, 358 | nilToastBuilder, 359 | nilFutureFunc, 360 | }; 361 | 362 | bool isNil(Object? o) { 363 | return nilSet.contains(o); 364 | } 365 | 366 | R returnFirstIfNotNil(R v1, R v2) { 367 | if (!isNil(v1)) { 368 | return v1; 369 | } else { 370 | return v2; 371 | } 372 | } 373 | 374 | R returnFirstIfNotNilAndCast(Object? v1, Object? v2) { 375 | if (!isNil(v1)) { 376 | return v1 as R; 377 | } else { 378 | return v2 as R; 379 | } 380 | } 381 | 382 | bool isNilOr(List list) { 383 | return list.every((element) => isNil(element) || element is T || element is T?); 384 | } 385 | -------------------------------------------------------------------------------- /lib/src/option.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'basis.dart'; 4 | import 'toast_widget/toast_widget.dart'; 5 | 6 | class Option { 7 | SimpleNotificationOption simpleNotification = SimpleNotificationOption(); 8 | NotificationOption notification = NotificationOption(); 9 | CustomNotificationOption customNotification = CustomNotificationOption(); 10 | TextOption text = TextOption(); 11 | CustomTextOption customText = CustomTextOption(); 12 | LoadingOption loading = LoadingOption(); 13 | CustomLoadingOption customLoading = CustomLoadingOption(); 14 | AttachedOption attached = AttachedOption(); 15 | AnimationOption animation = AnimationOption(); 16 | EnhancedOption enhanced = EnhancedOption(); 17 | } 18 | 19 | class SimpleNotificationOption { 20 | String? subTitle; 21 | 22 | TextStyle? titleStyle; 23 | 24 | TextStyle? subTitleStyle; 25 | 26 | WrapAnimation? wrapAnimation; 27 | 28 | WrapAnimation? wrapToastAnimation=notificationAnimation; 29 | 30 | GestureTapCallback? onTap; 31 | 32 | GestureLongPressCallback? onLongPress; 33 | 34 | Color? backgroundColor; 35 | 36 | double? borderRadius; 37 | 38 | Alignment? align = const Alignment(0, -0.99); 39 | 40 | List dismissDirections = [DismissDirection.horizontal, DismissDirection.up]; 41 | 42 | Icon closeIcon = const Icon(Icons.cancel); 43 | 44 | Duration? duration = const Duration(seconds: 2); 45 | 46 | Duration? animationDuration; 47 | 48 | Duration? animationReverseDuration; 49 | 50 | BackButtonBehavior? backButtonBehavior; 51 | 52 | VoidCallback? onClose; 53 | 54 | bool enableKeyboardSafeArea = true; 55 | 56 | bool enableSlideOff = true; 57 | 58 | bool crossPage = false; 59 | 60 | bool onlyOne = true; 61 | 62 | bool hideCloseButton = true; 63 | } 64 | 65 | class NotificationOption { 66 | ToastBuilder? leading; 67 | ToastBuilder? title; 68 | ToastBuilder? subtitle; 69 | ToastBuilder? trailing; 70 | WrapAnimation? wrapAnimation; 71 | WrapAnimation? wrapToastAnimation = notificationAnimation; 72 | GestureTapCallback? onTap; 73 | Color? backgroundColor; 74 | double? borderRadius; 75 | GestureLongPressCallback? onLongPress; 76 | Alignment? align = const Alignment(0, -0.99); 77 | List dismissDirections = const [DismissDirection.horizontal, DismissDirection.up]; 78 | BackButtonBehavior? backButtonBehavior; 79 | Duration? duration = const Duration(seconds: 2); 80 | Duration? animationDuration; 81 | Duration? animationReverseDuration; 82 | EdgeInsetsGeometry? contentPadding; 83 | EdgeInsetsGeometry? margin; 84 | VoidCallback? onClose; 85 | bool enableKeyboardSafeArea = true; 86 | bool enableSlideOff = true; 87 | bool crossPage = true; 88 | bool onlyOne = true; 89 | } 90 | 91 | class CustomNotificationOption { 92 | WrapAnimation? wrapAnimation; 93 | WrapAnimation? wrapToastAnimation = notificationAnimation; 94 | Alignment? align = const Alignment(0, -0.99); 95 | List dismissDirections = const [DismissDirection.horizontal, DismissDirection.up]; 96 | Duration? duration = const Duration(seconds: 2); 97 | Duration animationDuration = const Duration(milliseconds: 256); 98 | Duration? animationReverseDuration; 99 | VoidCallback? onClose; 100 | BackButtonBehavior? backButtonBehavior; 101 | bool enableKeyboardSafeArea = true; 102 | bool enableSlideOff = true; 103 | bool crossPage = true; 104 | bool onlyOne = true; 105 | bool useSafeArea = true; 106 | } 107 | 108 | class TextOption { 109 | WrapAnimation? wrapAnimation; 110 | WrapAnimation? wrapToastAnimation = textAnimation; 111 | Color backgroundColor = Colors.transparent; 112 | Color contentColor = Colors.black54; 113 | BorderRadiusGeometry borderRadius = const BorderRadius.all(Radius.circular(8)); 114 | TextStyle textStyle = const TextStyle(fontSize: 17, color: Colors.white); 115 | AlignmentGeometry? align = const Alignment(0, 0.8); 116 | EdgeInsetsGeometry contentPadding = const EdgeInsets.only(left: 14, right: 14, top: 5, bottom: 7); 117 | Duration? duration = const Duration(seconds: 2); 118 | Duration? animationDuration; 119 | Duration? animationReverseDuration; 120 | BackButtonBehavior? backButtonBehavior; 121 | VoidCallback? onClose; 122 | bool enableKeyboardSafeArea = true; 123 | bool clickClose = false; 124 | bool crossPage = true; 125 | bool onlyOne = true; 126 | } 127 | 128 | class CustomTextOption { 129 | WrapAnimation? wrapAnimation; 130 | WrapAnimation? wrapToastAnimation = textAnimation; 131 | AlignmentGeometry? align = const Alignment(0, 0.8); 132 | Color backgroundColor = Colors.transparent; 133 | Duration? duration = const Duration(seconds: 2); 134 | Duration animationDuration = const Duration(milliseconds: 256); 135 | Duration? animationReverseDuration; 136 | VoidCallback? onClose; 137 | BackButtonBehavior? backButtonBehavior; 138 | bool enableKeyboardSafeArea = true; 139 | bool crossPage = true; 140 | bool clickClose = false; 141 | bool ignoreContentClick = false; 142 | bool onlyOne = false; 143 | bool useSafeArea = true; 144 | } 145 | 146 | class LoadingOption { 147 | WrapAnimation? wrapAnimation = loadingAnimation; 148 | WrapAnimation? wrapToastAnimation; 149 | Alignment align = Alignment.center; 150 | BackButtonBehavior? backButtonBehavior; 151 | bool crossPage = true; 152 | bool clickClose = false; 153 | bool allowClick = false; 154 | bool enableKeyboardSafeArea = true; 155 | VoidCallback? onClose; 156 | Duration? duration; 157 | Duration? animationDuration; 158 | Duration? animationReverseDuration; 159 | Color backgroundColor = Colors.black26; 160 | } 161 | 162 | class CustomLoadingOption { 163 | WrapAnimation? wrapAnimation = loadingAnimation; 164 | WrapAnimation? wrapToastAnimation; 165 | Alignment? align = Alignment.center; 166 | BackButtonBehavior? backButtonBehavior; 167 | bool clickClose = false; 168 | bool allowClick = false; 169 | bool ignoreContentClick = false; 170 | bool crossPage = false; 171 | bool enableKeyboardSafeArea = true; 172 | VoidCallback? onClose; 173 | Duration? duration; 174 | Duration animationDuration = const Duration(milliseconds: 300); 175 | Duration? animationReverseDuration; 176 | Color backgroundColor = Colors.black26; 177 | bool useSafeArea = true; 178 | } 179 | 180 | class AttachedOption { 181 | WrapAnimation? wrapAnimation; 182 | WrapAnimation? wrapToastAnimation = attachedAnimation; 183 | Color backgroundColor = Colors.transparent; 184 | double verticalOffset = 0.0; 185 | double horizontalOffset = 0.0; 186 | Duration? duration; 187 | Duration animationDuration = const Duration(milliseconds: 150); 188 | Duration? animationReverseDuration; 189 | PreferDirection? preferDirection; 190 | VoidCallback? onClose; 191 | bool ignoreContentClick = false; 192 | bool onlyOne = false; 193 | bool allowClick = true; 194 | bool enableKeyboardSafeArea = true; 195 | bool enableSafeArea = true; 196 | } 197 | 198 | class AnimationOption { 199 | Duration? animationReverseDuration; 200 | WrapAnimation? wrapAnimation; 201 | WrapAnimation? wrapToastAnimation; 202 | BackButtonBehavior? backButtonBehavior; 203 | UniqueKey? key; 204 | String? groupKey; 205 | bool crossPage = true; 206 | bool allowClick = true; 207 | bool clickClose = false; 208 | bool ignoreContentClick = false; 209 | bool onlyOne = false; 210 | bool enableKeyboardSafeArea = true; 211 | Color backgroundColor = Colors.transparent; 212 | Duration? duration; 213 | VoidCallback? onClose; 214 | } 215 | 216 | class EnhancedOption { 217 | UniqueKey? key; 218 | String? groupKey; 219 | bool crossPage = true; 220 | bool allowClick = true; 221 | bool clickClose = false; 222 | bool ignoreContentClick = false; 223 | bool onlyOne = false; 224 | bool enableKeyboardSafeArea = true; 225 | BackButtonBehavior? backButtonBehavior; 226 | FutureFunc? closeFunc; 227 | VoidCallback? onClose; 228 | Color backgroundColor = Colors.transparent; 229 | WrapWidget? warpWidget; 230 | Duration? duration; 231 | } 232 | -------------------------------------------------------------------------------- /lib/src/toast_navigator_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BotToastNavigatorObserverProxy { 4 | void Function(Route route, Route? previousRoute)? didPush; 5 | void Function(Route? newRoute, Route? oldRoute)? didReplace; 6 | void Function(Route route, Route? previousRoute)? didRemove; 7 | void Function(Route route, Route? previousRoute)? didPop; 8 | 9 | BotToastNavigatorObserverProxy( 10 | {this.didPush, this.didReplace, this.didRemove, this.didPop}); 11 | 12 | BotToastNavigatorObserverProxy.all(VoidCallback leavePageCallback) { 13 | didPush = (_, __) => leavePageCallback(); 14 | didReplace = (_, __) => leavePageCallback(); 15 | didRemove = (_, __) => leavePageCallback(); 16 | didPop = (_, __) => leavePageCallback(); 17 | } 18 | } 19 | 20 | ///If your project has multiple [Navigator] instances, please add the [BotToastNavigatorObserver] to the [Navigator.observers]. 21 | /// 22 | ///如果你项目有多个[Navigator],请将该BotToastNavigatorObserver添加到[Navigator.observers] 23 | class BotToastNavigatorObserver extends NavigatorObserver { 24 | static final List _leavePageCallbacks = []; 25 | 26 | static bool debugInitialization = false; 27 | 28 | BotToastNavigatorObserver() { 29 | assert(() { 30 | debugInitialization = true; 31 | return true; 32 | }()); 33 | } 34 | 35 | static void register( 36 | BotToastNavigatorObserverProxy botToastNavigatorObserverProxy) { 37 | assert(debugInitialization, """ 38 | Please initialize! 39 | Example: 40 | BotToastInit( 41 | child:MaterialApp( 42 | title: 'Xxxx Demo', 43 | navigatorObservers: [BotToastNavigatorObserver()], 44 | home: XxxxPage(), 45 | )); 46 | """); 47 | _leavePageCallbacks.add(botToastNavigatorObserverProxy); 48 | } 49 | 50 | static void unregister( 51 | BotToastNavigatorObserverProxy botToastNavigatorObserverProxy) { 52 | _leavePageCallbacks.remove(botToastNavigatorObserverProxy); 53 | } 54 | 55 | @override 56 | void didPush(Route route, Route? previousRoute) { 57 | final copy = _leavePageCallbacks.toList(growable: false); 58 | for (BotToastNavigatorObserverProxy observerProxy in copy) { 59 | observerProxy.didPush?.call(route, previousRoute); 60 | } 61 | } 62 | 63 | @override 64 | void didReplace({Route? newRoute, Route? oldRoute}) { 65 | final copy = _leavePageCallbacks.toList(growable: false); 66 | for (BotToastNavigatorObserverProxy observerProxy in copy) { 67 | observerProxy.didReplace?.call(newRoute, oldRoute); 68 | } 69 | } 70 | 71 | @override 72 | void didRemove(Route route, Route? previousRoute) { 73 | final copy = _leavePageCallbacks.toList(growable: false); 74 | for (BotToastNavigatorObserverProxy observerProxy in copy) { 75 | observerProxy.didRemove?.call(route, previousRoute); 76 | } 77 | } 78 | 79 | @override 80 | void didPop(Route route, Route? previousRoute) { 81 | final copy = _leavePageCallbacks.toList(growable: false); 82 | for (BotToastNavigatorObserverProxy observerProxy in copy) { 83 | observerProxy.didPop?.call(route, previousRoute); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/toast_widget/animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | Widget notificationAnimation( 5 | AnimationController controller, CancelFunc cancelFunc, Widget child) => 6 | NormalAnimation(reverse: true, controller: controller, child: child); 7 | 8 | Widget textAnimation( 9 | AnimationController controller, CancelFunc cancelFunc, Widget child) => 10 | NormalAnimation(controller: controller, child: child); 11 | 12 | Widget loadingAnimation( 13 | AnimationController controller, CancelFunc cancelFunc, Widget child) => 14 | FadeAnimation(controller: controller, child: child); 15 | 16 | Widget attachedAnimation( 17 | AnimationController controller, CancelFunc cancelFunc, Widget child) => 18 | FadeAnimation(controller: controller, child: child); 19 | 20 | class NormalAnimation extends StatefulWidget { 21 | final Widget child; 22 | final bool reverse; 23 | final AnimationController controller; 24 | 25 | const NormalAnimation( 26 | {Key? key, 27 | required this.child, 28 | this.reverse = false, 29 | required this.controller}) 30 | : super(key: key); 31 | 32 | @override 33 | NormalAnimationState createState() => NormalAnimationState(); 34 | } 35 | 36 | class NormalAnimationState extends State 37 | with SingleTickerProviderStateMixin { 38 | static final Tween reverseTweenOffset = Tween( 39 | begin: const Offset(0, -40), 40 | end: const Offset(0, 0), 41 | ); 42 | static final Tween tweenOffset = Tween( 43 | begin: const Offset(0, 40), 44 | end: const Offset(0, 0), 45 | ); 46 | static final Tween tweenOpacity = Tween(begin: 0, end: 1); 47 | late final Animation animation; 48 | 49 | late final Animation animationOffset; 50 | late final Animation animationOpacity; 51 | 52 | @override 53 | void initState() { 54 | animation = 55 | CurvedAnimation(parent: widget.controller, curve: Curves.decelerate); 56 | 57 | animationOffset = 58 | (widget.reverse ? reverseTweenOffset : tweenOffset).animate(animation); 59 | animationOpacity = tweenOpacity.animate(animation); 60 | 61 | super.initState(); 62 | } 63 | 64 | @override 65 | Widget build(BuildContext context) { 66 | return AnimatedBuilder( 67 | animation: widget.controller, 68 | builder: (_, child) { 69 | return Transform.translate( 70 | offset: animationOffset.value, 71 | child: Opacity( 72 | opacity: animationOpacity.value, 73 | child: child, 74 | ), 75 | ); 76 | }, 77 | child: widget.child, 78 | ); 79 | } 80 | } 81 | 82 | //淡出淡入动画 83 | class FadeAnimation extends StatefulWidget { 84 | final Widget? child; 85 | final AnimationController controller; 86 | 87 | const FadeAnimation({Key? key, this.child, required this.controller}) 88 | : super(key: key); 89 | 90 | @override 91 | FadeAnimationState createState() => FadeAnimationState(); 92 | } 93 | 94 | class FadeAnimationState extends State 95 | with SingleTickerProviderStateMixin { 96 | static final Tween tweenOpacity = Tween(begin: 0, end: 1); 97 | late final Animation animation; 98 | late final Animation animationOpacity; 99 | 100 | @override 101 | void initState() { 102 | animation = 103 | CurvedAnimation(parent: widget.controller, curve: Curves.decelerate); 104 | 105 | animationOpacity = tweenOpacity.animate(animation); 106 | 107 | super.initState(); 108 | } 109 | 110 | @override 111 | Widget build(BuildContext context) { 112 | return FadeTransition( 113 | opacity: animationOpacity, 114 | child: widget.child, 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/src/toast_widget/attached.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:bot_toast/src/basis.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | //定位Delegate 6 | //使用该类默认是以Offset(0,app导航栏高度)为原点 7 | class PositionDelegate extends SingleChildLayoutDelegate { 8 | PositionDelegate( 9 | {required this.target, 10 | required this.verticalOffset, 11 | required this.horizontalOffset, 12 | required this.paddingTop, 13 | this.enableSafeArea = true, 14 | PreferDirection? preferDirection}) 15 | : this.preferDirection = preferDirection ?? PreferDirection.topCenter; 16 | 17 | final bool enableSafeArea; 18 | 19 | final Rect target; 20 | 21 | final double verticalOffset; 22 | final double horizontalOffset; 23 | final double paddingTop; 24 | 25 | final PreferDirection preferDirection; 26 | 27 | @override 28 | BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints.loosen(); 29 | 30 | @override 31 | Offset getPositionForChild(Size size, Size childSize) { 32 | final double top = enableSafeArea ? paddingTop : 0; 33 | return positionToastBox( 34 | containerRect: Rect.fromLTWH(0, top, size.width, size.height - top), 35 | toastSize: childSize, 36 | targetRect: target, 37 | verticalOffset: verticalOffset, 38 | horizontalOffset: horizontalOffset, 39 | preferDirection: preferDirection, 40 | ); 41 | } 42 | 43 | @override 44 | bool shouldRelayout(PositionDelegate oldDelegate) { 45 | return target != oldDelegate.target || 46 | paddingTop != oldDelegate.paddingTop || 47 | verticalOffset != oldDelegate.verticalOffset || 48 | preferDirection != oldDelegate.preferDirection; 49 | } 50 | } 51 | 52 | //当该方法推断出来的方向等于xxxCenter时 53 | //则verticalOffset或者horizontalOffset失效 54 | //例如: 55 | //推断出来是topCent方向则horizontalOffset将会没效果,verticalOffset正常使用 56 | Offset positionToastBox( 57 | {required Size toastSize, 58 | required Rect containerRect, 59 | required Rect targetRect, 60 | required PreferDirection preferDirection, 61 | double verticalOffset = 0.0, 62 | double horizontalOffset = 0.0}) { 63 | assert(verticalOffset >= 0, 'verticalOffset must be a positive number'); 64 | assert(horizontalOffset >= 0, 'horizontalOffset must be a positive number'); 65 | //裁剪 66 | if (containerRect.overlaps(targetRect)) { 67 | targetRect = targetRect.intersect(containerRect); 68 | } else { 69 | targetRect = Rect.fromLTWH(containerRect.left, containerRect.top, 0, 0); 70 | } 71 | 72 | bool canPlaceTop({double extraSpace = 0}) { 73 | return toastSize.height + verticalOffset < targetRect.top - containerRect.top + extraSpace; 74 | } 75 | 76 | bool canPlaceBottom({double extraSpace = 0}) { 77 | return toastSize.height + verticalOffset < containerRect.bottom - targetRect.bottom + extraSpace; 78 | } 79 | 80 | bool canPlaceLeft({double extraSpace = 0}) { 81 | return toastSize.width + horizontalOffset < targetRect.left - containerRect.left + extraSpace; 82 | } 83 | 84 | bool canPlaceRight({double extraSpace = 0}) { 85 | return toastSize.width + horizontalOffset < containerRect.right - targetRect.right + extraSpace; 86 | } 87 | 88 | String direction; 89 | //判断主方向,index由小到大 90 | if (preferDirection.index <= PreferDirection.topRight.index) { 91 | direction = canPlaceTop() ? "top" : "bottom"; 92 | } else if (preferDirection.index <= PreferDirection.bottomRight.index) { 93 | direction = canPlaceBottom() ? "bottom" : "top"; 94 | } else if (preferDirection.index <= PreferDirection.leftBottom.index) { 95 | direction = canPlaceLeft() ? "left" : "right"; 96 | } else { 97 | direction = canPlaceRight() ? "right" : "left"; 98 | } 99 | 100 | //判断对齐方向 101 | if (preferDirection.index <= PreferDirection.bottomRight.index) { 102 | switch (_getDirection(preferDirection).replaceAll("top", "").replaceAll("bottom", "")) { 103 | case "Left": 104 | direction += canPlaceRight(extraSpace: targetRect.width) 105 | ? "Left" 106 | : canPlaceLeft(extraSpace: targetRect.width) 107 | ? "Right" 108 | : "Center"; 109 | break; 110 | case "Center": 111 | direction += "Center"; 112 | break; 113 | case "Right": 114 | direction += canPlaceLeft(extraSpace: targetRect.width) 115 | ? "Right" 116 | : canPlaceRight(extraSpace: targetRect.width) 117 | ? "Left" 118 | : "Center"; 119 | break; 120 | } 121 | } else { 122 | switch (_getDirection(preferDirection).replaceAll("left", "").replaceAll("right", "")) { 123 | case "Top": 124 | direction += canPlaceBottom(extraSpace: targetRect.height) 125 | ? "Top" 126 | : canPlaceTop(extraSpace: targetRect.height) 127 | ? "Bottom" 128 | : "Center"; 129 | break; 130 | case "Center": 131 | direction += "Center"; 132 | break; 133 | case "Bottom": 134 | direction += canPlaceTop(extraSpace: targetRect.height) 135 | ? "Bottom" 136 | : canPlaceBottom(extraSpace: targetRect.height) 137 | ? "Top" 138 | : "Center"; 139 | break; 140 | } 141 | } 142 | 143 | Offset resultOffset = Offset.zero; 144 | switch (direction) { 145 | case "topLeft": 146 | resultOffset = targetRect.topLeft - 147 | Offset(0, toastSize.height) + 148 | Offset( 149 | horizontalOffset, 150 | -verticalOffset, 151 | ); 152 | break; 153 | case "topCenter": 154 | bool rightOverflow = toastSize.width / 2 > containerRect.right - targetRect.topCenter.dx; 155 | bool leftOverflow = toastSize.width / 2 > targetRect.topCenter.dx - containerRect.left; 156 | if (rightOverflow && !leftOverflow) { 157 | resultOffset = Offset(containerRect.right - toastSize.width, targetRect.top - toastSize.height); 158 | } else if (leftOverflow && !rightOverflow) { 159 | resultOffset = Offset(containerRect.left, targetRect.top - toastSize.height); 160 | } else { 161 | resultOffset = targetRect.topCenter - Offset(toastSize.width / 2, toastSize.height); 162 | } 163 | resultOffset += Offset(0, -verticalOffset); 164 | break; 165 | case "topRight": 166 | resultOffset = targetRect.topRight - 167 | Offset(toastSize.width, toastSize.height) + 168 | Offset( 169 | -horizontalOffset, 170 | -verticalOffset, 171 | ); 172 | break; 173 | case "bottomLeft": 174 | resultOffset = targetRect.bottomLeft + Offset(horizontalOffset, verticalOffset); 175 | break; 176 | case "bottomCenter": 177 | bool rightOverflow = toastSize.width / 2 > containerRect.right - targetRect.topCenter.dx; 178 | bool leftOverflow = toastSize.width / 2 > targetRect.topCenter.dx - containerRect.left; 179 | if (rightOverflow && !leftOverflow) { 180 | resultOffset = Offset(containerRect.right - toastSize.width, targetRect.bottom); 181 | } else if (leftOverflow && !rightOverflow) { 182 | resultOffset = Offset(containerRect.left, targetRect.bottom); 183 | } else { 184 | resultOffset = targetRect.bottomCenter - Offset(toastSize.width / 2, 0); 185 | } 186 | resultOffset += Offset(0, verticalOffset); 187 | break; 188 | case "bottomRight": 189 | resultOffset = targetRect.bottomRight - Offset(toastSize.width, 0) + Offset(-horizontalOffset, verticalOffset); 190 | break; 191 | case "leftTop": 192 | resultOffset = targetRect.topLeft - Offset(toastSize.width, 0) + Offset(-horizontalOffset, verticalOffset); 193 | break; 194 | case "leftCenter": 195 | bool topOverflow = toastSize.height / 2 > targetRect.centerLeft.dy - containerRect.top; 196 | bool bottomOverflow = toastSize.height / 2 > containerRect.bottom - targetRect.centerLeft.dy; 197 | if (topOverflow && !bottomOverflow) { 198 | resultOffset = Offset( 199 | targetRect.left - toastSize.width, 200 | containerRect.top, 201 | ); 202 | } else if (bottomOverflow && !topOverflow) { 203 | resultOffset = Offset( 204 | targetRect.left - toastSize.width, 205 | containerRect.bottom - toastSize.height, 206 | ); 207 | } else { 208 | resultOffset = targetRect.centerLeft - Offset(toastSize.width, toastSize.height / 2); 209 | } 210 | resultOffset += Offset(-horizontalOffset, 0); 211 | break; 212 | case "leftBottom": 213 | resultOffset = targetRect.bottomLeft - Offset(toastSize.width, toastSize.height) + Offset(-horizontalOffset, -verticalOffset); 214 | break; 215 | case "rightTop": 216 | resultOffset = targetRect.topRight + Offset(horizontalOffset, verticalOffset); 217 | break; 218 | case "rightCenter": 219 | bool topOverflow = toastSize.height / 2 > targetRect.centerLeft.dy - containerRect.top; 220 | bool bottomOverflow = toastSize.height / 2 > containerRect.bottom - targetRect.centerLeft.dy; 221 | if (topOverflow && !bottomOverflow) { 222 | resultOffset = Offset( 223 | targetRect.right, 224 | containerRect.top, 225 | ); 226 | } else if (bottomOverflow && !topOverflow) { 227 | resultOffset = Offset( 228 | targetRect.right, 229 | containerRect.bottom - toastSize.height, 230 | ); 231 | } else { 232 | resultOffset = targetRect.centerRight - Offset(0, toastSize.height / 2); 233 | } 234 | resultOffset += Offset(horizontalOffset, 0); 235 | break; 236 | case "rightBottom": 237 | resultOffset = targetRect.bottomRight - Offset(0, toastSize.height) + Offset(horizontalOffset, -verticalOffset); 238 | break; 239 | } 240 | 241 | return resultOffset; 242 | } 243 | 244 | String _getDirection(PreferDirection preferDirection) { 245 | return preferDirection.toString().replaceFirst("PreferDirection.", ""); 246 | } 247 | -------------------------------------------------------------------------------- /lib/src/toast_widget/loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | //加载提示的Widget 4 | class LoadingWidget extends StatelessWidget { 5 | const LoadingWidget({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Container( 10 | padding: const EdgeInsets.all(15), 11 | decoration: const BoxDecoration( 12 | color: Colors.black54, 13 | borderRadius: BorderRadius.all(Radius.circular(8))), 14 | child: const CircularProgressIndicator( 15 | backgroundColor: Colors.white, 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/toast_widget/notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | //通知Toast的Widget 4 | class NotificationToast extends StatefulWidget { 5 | final Widget child; 6 | 7 | final Function? slideOffFunc; 8 | 9 | final List? dismissDirections; 10 | 11 | const NotificationToast( 12 | {Key? key, 13 | required this.child, 14 | this.slideOffFunc, 15 | this.dismissDirections}) 16 | : super(key: key); 17 | 18 | @override 19 | _NotificationState createState() => _NotificationState(); 20 | } 21 | 22 | class _NotificationState extends State { 23 | Future confirmDismiss(DismissDirection direction) async { 24 | widget.slideOffFunc?.call(); 25 | return true; 26 | } 27 | 28 | Key key = UniqueKey(); 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | Widget child = widget.child; 33 | 34 | if (widget.slideOffFunc != null && 35 | widget.dismissDirections != null && 36 | widget.dismissDirections!.isNotEmpty) { 37 | widget.dismissDirections!.forEach((direction) { 38 | child = Dismissible( 39 | direction: direction, 40 | key: key, 41 | confirmDismiss: confirmDismiss, 42 | child: child, 43 | ); 44 | }); 45 | } 46 | 47 | return child; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/toast_widget/text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | //文本提示的Widget 4 | class TextToast extends StatefulWidget { 5 | final String text; 6 | final EdgeInsetsGeometry? contentPadding; 7 | final Color? contentColor; 8 | final BorderRadiusGeometry? borderRadius; 9 | final TextStyle? textStyle; 10 | 11 | const TextToast({ 12 | Key? key, 13 | required this.text, 14 | this.contentPadding, 15 | this.contentColor, 16 | this.borderRadius, 17 | this.textStyle, 18 | }) : super(key: key); 19 | 20 | @override 21 | TextToastState createState() => TextToastState(); 22 | } 23 | 24 | class TextToastState extends State { 25 | @override 26 | Widget build(BuildContext context) { 27 | return LayoutBuilder( 28 | builder: (BuildContext context, BoxConstraints constraints) { 29 | return Container( 30 | constraints: 31 | constraints.copyWith(maxWidth: constraints.biggest.width * 0.6), 32 | padding: widget.contentPadding, 33 | decoration: BoxDecoration( 34 | color: widget.contentColor, 35 | borderRadius: widget.borderRadius, 36 | ), 37 | child: Text( 38 | widget.text, 39 | style: widget.textStyle, 40 | textAlign: TextAlign.center, 41 | )); 42 | }, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/toast_widget/toast_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | export 'animation.dart'; 3 | export 'attached.dart'; 4 | export 'loading.dart'; 5 | export 'notification.dart'; 6 | export 'text.dart'; 7 | 8 | class ProxyInitState extends StatefulWidget { 9 | final Widget child; 10 | final VoidCallback initStateCallback; 11 | 12 | const ProxyInitState({Key? key,required this.initStateCallback,required this.child}) 13 | : super(key: key); 14 | 15 | @override 16 | _ProxyInitStateState createState() => _ProxyInitStateState(); 17 | } 18 | 19 | class _ProxyInitStateState extends State { 20 | @override 21 | void initState() { 22 | widget.initStateCallback(); 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return widget.child; 29 | } 30 | } 31 | 32 | class ProxyDispose extends StatefulWidget { 33 | final Widget child; 34 | final VoidCallback disposeCallback; 35 | 36 | const ProxyDispose({ 37 | Key? key, 38 | required this.disposeCallback, 39 | required this.child, 40 | }) : super(key: key); 41 | 42 | @override 43 | _ProxyDisposeState createState() => _ProxyDisposeState(); 44 | } 45 | 46 | class _ProxyDisposeState extends State { 47 | @override 48 | Widget build(BuildContext context) { 49 | return widget.child; 50 | } 51 | 52 | @override 53 | void dispose() { 54 | widget.disposeCallback(); 55 | super.dispose(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: bot_toast 2 | description: A really easy to use flutter toast library.Easy to use and feature rich. 3 | homepage: https://github.com/MMMzq/bot_toast 4 | email: vivask770@163.com 5 | 6 | version: 4.1.3 7 | 8 | environment: 9 | sdk: ^2.12.0 10 | flutter: '>=2.0.0' 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://www.dartlang.org/tools/pub/pubspec 22 | 23 | # The following section is specific to Flutter. 24 | flutter: 25 | 26 | # To add assets to your package, add an assets section, like this: 27 | # assets: 28 | # - images/a_dot_burr.jpeg 29 | # - images/a_dot_ham.jpeg 30 | # 31 | # For details regarding assets in packages, see 32 | # https://flutter.dev/assets-and-images/#from-packages 33 | # 34 | # An image asset can refer to one or more resolution-specific "variants", see 35 | # https://flutter.dev/assets-and-images/#resolution-aware. 36 | 37 | # To add custom fonts to your package, add a fonts section here, 38 | # in this "flutter" section. Each entry in this list should have a 39 | # "family" key with the font family name, and a "fonts" key with a 40 | # list giving the asset and other descriptors for the font. For 41 | # example: 42 | # fonts: 43 | # - family: Schyler 44 | # fonts: 45 | # - asset: fonts/Schyler-Regular.ttf 46 | # - asset: fonts/Schyler-Italic.ttf 47 | # style: italic 48 | # - family: Trajan Pro 49 | # fonts: 50 | # - asset: fonts/TrajanPro.ttf 51 | # - asset: fonts/TrajanPro_Bold.ttf 52 | # weight: 700 53 | # 54 | # For details regarding fonts in packages, see 55 | # https://flutter.dev/custom-fonts/#from-packages 56 | --------------------------------------------------------------------------------