├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── app │ └── src │ │ └── main │ │ └── java │ │ └── io │ │ └── flutter │ │ └── plugins │ │ └── GeneratedPluginRegistrant.java └── local.properties ├── assets ├── fonts │ └── CoolUI.ttf └── images │ └── loading.png ├── cool_ui.iml ├── documents ├── custom_keyboard.md ├── images │ ├── custom_keyboard.gif │ ├── popover_demo.gif │ └── toast_demo.gif ├── popover.md └── weui_toast.md ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ └── fonts │ │ └── CoolUIExample.ttf ├── lib │ ├── cool_u_i_example_icons.dart │ ├── keyboards │ │ └── test_keyboard.dart │ ├── main.dart │ └── pages │ │ ├── custom_keyboard.dart │ │ ├── paint_event_demo.dart │ │ ├── popover_demo.dart │ │ ├── table_demo.dart │ │ └── weui_toast_demo.dart ├── pubspec.yaml ├── test │ └── widget_test.dart └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── ios ├── Flutter │ └── flutter_export_environment.sh └── Runner │ ├── GeneratedPluginRegistrant.h │ └── GeneratedPluginRegistrant.m ├── lib ├── cool_ui.dart ├── dialogs │ └── weui_toast.dart ├── icons │ └── cool_ui_icons.dart ├── keyboards │ ├── keyboard_controller.dart │ ├── keyboard_manager.dart │ ├── keyboard_media_query.dart │ ├── keyboard_root.dart │ ├── mocks │ │ ├── mock_binary_messenger.dart │ │ └── mock_binding.dart │ └── number_keyboard.dart ├── utils │ ├── screen_util.dart │ ├── scroll_utils.dart │ └── widget_util.dart └── widgets │ ├── popover │ ├── cupertino_popover.dart │ └── cupertino_popover_menu_item.dart │ ├── tables │ └── table.dart │ └── utils │ └── paint_event.dart ├── pub.bat ├── pubspec.lock ├── pubspec.yaml └── test └── cool_ui_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | ios/.generated/ 9 | ios/Flutter/Generated.xcconfig 10 | ios/Runner/GeneratedPluginRegistrant.* 11 | /.idea 12 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2f630446040b9f5b65fbfff9a76be1fe01c45e99 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "Flutter", 10 | "request": "launch", 11 | "type": "dart" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.2.0] 2 | * TODO: 修复了flutter3.0后WidgetsBinding.instance无法判断是否为空导致报错 3 | 4 | ## [1.1.0] 5 | * TODO: 修复了flutter2.5后删除了setMockMessageHandler导致无法使用的问题 6 | 7 | ## [1.0.2] 8 | * TODO: 修复收起虚拟键盘报错 9 | * TODO: 修复因空安全导致的报错 10 | 11 | ## [1.0.1] 12 | * TODO: 修复警告错误 13 | 14 | ## [1.0.0] 15 | * TODO: 升级至flutter 2.0 16 | * TODO: 处理了空安全 17 | 18 | ## [0.6.3] 19 | * TODO: 指定back_button_interceptor依赖 20 | 21 | ## [0.6.2] 22 | * TODO: 指定back_button_interceptor依赖 23 | 24 | ## [0.6.1] 25 | * TODO: CupertinoPopoverDirection添加了left和right 26 | 27 | ## [0.6.0] 28 | * TODO: 重构了键盘弹出的方式,修复长按输入框报错问题 29 | 30 | ## [0.5.5] 31 | * TODO: Popover添加了阴影效果 32 | * TODO: Popover 添加了方向 33 | 34 | ## [0.5.4] 35 | * TODO: Fix Bug Issues:47 36 | 37 | ## [0.5.3] 38 | * TODO: 修复键盘有时候不会收起来的问题 39 | 40 | ## [0.5.0] 41 | * TODO: 修复Flutter1.17版本后无法在打开第二个自定义键盘不显示的问题 42 | 43 | ## [0.4.1] 44 | * TODO: 修复了键盘高度从小变大的时候无法改变的问题, issues: 36 45 | * TODO: 键盘添加了传参功能,可从外部传参到键盘内部 46 | 47 | ## [0.3.3] 48 | * TODO: 修复了键盘高度不同的时候不会改变问题, issues: 36 49 | 50 | ## [0.3.2] 51 | * TODO: 修复页面高度未刷新问题 52 | 53 | 54 | ## [0.3.1] 55 | * TODO: 修复了键盘监听高度未释放的问题 56 | 57 | ## [0.3.0] 58 | * TODO: 优化了键盘的使用方式 59 | 60 | ## [0.2.2] 61 | * TODO: 修复了有默认值时,键盘无法输入数据的问题 62 | 63 | ## [0.2.1] 64 | * TODO: 修复了多个自定义键盘无法切换的问题 65 | 66 | ## [0.2.0] 67 | * TODO: 优化了WeuiToast的效果,添加了WeuiToastConfig,可以全局配置默认设置 68 | * TODO: 添加了Keyboard对android 返回按钮的监听 69 | 70 | ## [0.1.16] 71 | * Updated SDK constraint to support new error message formats 72 | * Updated error message formats 73 | 74 | ## [0.1.14] - TODO:修复了自定义键盘切换回原生键盘无法输入的问题 75 | * 修复了自定义键盘切换回原生键盘无法输入的问题 76 | * Popover可以设置遮罩层颜色 77 | 78 | ## [0.1.13] - TODO:修复hide后pop无效的问题 79 | 80 | ## [0.1.8] - TODO:完善了键盘的文档 81 | * 完善了键盘的文档 82 | * 完善了可发送的TextInputAction的类型 83 | 84 | ## [0.1.7] - TODO:添加了自定义键盘 85 | * TODO:添加了自定义键盘 86 | * TODO:修改了下文档结构 87 | 88 | ## [0.1.6] - TODO:修改了Toast的位置计算方式 89 | * TODO: 修改了Toast的位置计算方式 90 | * TODO: 开放了Toast的位置设置 91 | * TODO: 修改了README.md文档的大小 92 | 93 | ## [0.1.5] - TODO:添加了popoverConstraints参数 94 | * TODO: CupertionPopoverButton添加了CupertionPopoverButton参数,用于设置最大最小宽度,取消必填的高宽 95 | 96 | ## [0.1.4] - TODO:修改了onTap事件 97 | * TODO: CupertionPopoverButton添加了onTap事件,返回True不打开Popover 98 | * TODO: CupertinoPopoverMenuItem修改了onTap事件,返回True不关闭Popover 99 | 100 | ## [0.1.1] - TODO:添加了WeuiToast效果,完善了文档信息 101 | * TODO: 添加了WeuiToast效果 102 | * TODO: 完善了文档信息 103 | * TODO: 添加了PaintEvent事件,具体请看Demo 104 | 105 | ## [0.1.0] - TODO: 改进了CupertionPopover和添加了CupertinoPopoverMenuItem 106 | 107 | * TODO: 改进了CupertionPopover箭头的位置 108 | * TODO: 改进了CupertinoPopoverMenuItem按下的动画,并且添加了onTap 109 | 110 | ## [0.0.9] - TODO: 添加了一些控件,改进了CupertionPopover 111 | 112 | * TODO: 添加了CupertinoPopoverMenuList 113 | * TODO: 添加了CupertinoPopoverMenuItem 114 | * TODO: 修改了CupertionPopover动画, 115 | 116 | ## [0.0.1] - TODO: Add release date. 117 | 118 | * TODO: Describe initial release. 119 | 120 | -------------------------------------------------------------------------------- /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 [yyyy] [name of copyright owner] 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 | # cool_ui [![pub package](https://img.shields.io/pub/v/cool_ui.svg)](https://pub.dartlang.org/packages/cool_ui) 2 | 3 | 用flutter实现一些我认为好看的UI控件
4 | 希望大家提一些觉得不错的控件,我自己一个人想有时候想到的比较有限 5 | 6 | 7 | Usage Add this to your package's pubspec.yaml file: 8 | 9 | Flutter >=3.3 10 | ``` yaml 11 | dependencies: 12 | cool_ui: "^1.3.0" 13 | ``` 14 | 15 | Flutter >=3.0 16 | ``` yaml 17 | dependencies: 18 | cool_ui: "^1.2.0" 19 | ``` 20 | 21 | Flutter >=2.5 22 | ``` yaml 23 | dependencies: 24 | cool_ui: "^1.1.0" 25 | ``` 26 | 27 | 28 | Flutter >=2.0 29 | ``` yaml 30 | dependencies: 31 | cool_ui: "^1.0.2" 32 | ``` 33 | 34 | Flutter >=1.17 35 | ``` yaml 36 | dependencies: 37 | cool_ui: "^0.6.1" 38 | ``` 39 | 40 | Flutter >=1.7 41 | ``` yaml 42 | dependencies: 43 | cool_ui: "^0.4.1" 44 | ``` 45 | 46 | Flutter < 1.7 47 | ``` yaml 48 | dependencies: 49 | cool_ui: "0.1.15" 50 | ``` 51 | 52 | 53 | # 控件 54 | 55 | - [CupertinoPopoverButton](documents/popover.md#CupertinoPopoverButton) 56 | - [CupertinoPopoverMenuList](documents/popover.md#CupertinoPopoverMenuList) 57 | - [CupertinoPopoverMenuItem](documents/popover.md#CupertinoPopoverMenuItem) 58 | - [showWeuiToast](documents/weui_toast.md#showWeuiToast) 59 | - [showWeuiSuccessToast](documents/weui_toast.md#showWeuiSuccessToast) 60 | - [showWeuiLoadingToast](documents/weui_toast.md#showWeuiLoadingToast) 61 | 62 | 63 | # 自定义键盘(Customize Keyboard) 64 | 65 | - [Get started](documents/custom_keyboard.md#Step1) 66 | - [KeyboardController](documents/custom_keyboard.md#KeyboardController) 67 | -------------------------------------------------------------------------------- /android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java: -------------------------------------------------------------------------------- 1 | package io.flutter.plugins; 2 | 3 | import io.flutter.plugin.common.PluginRegistry; 4 | 5 | /** 6 | * Generated file. Do not edit. 7 | */ 8 | public final class GeneratedPluginRegistrant { 9 | public static void registerWith(PluginRegistry registry) { 10 | if (alreadyRegisteredWith(registry)) { 11 | return; 12 | } 13 | } 14 | 15 | private static boolean alreadyRegisteredWith(PluginRegistry registry) { 16 | final String key = GeneratedPluginRegistrant.class.getCanonicalName(); 17 | if (registry.hasPlugin(key)) { 18 | return true; 19 | } 20 | registry.registrarFor(key); 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/local.properties: -------------------------------------------------------------------------------- 1 | sdk.dir=D:\\Android\\SDK 2 | flutter.sdk=D:\\flutter -------------------------------------------------------------------------------- /assets/fonts/CoolUI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/assets/fonts/CoolUI.ttf -------------------------------------------------------------------------------- /assets/images/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/assets/images/loading.png -------------------------------------------------------------------------------- /cool_ui.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /documents/custom_keyboard.md: -------------------------------------------------------------------------------- 1 | # 自定义键盘使用方法快速入门 2 | 3 | ## 效果 4 | 5 | 6 | 7 | ## Flutter 2.5后添加的步骤 8 | 替换runApp为runMockApp 9 | ```dart 10 | void main() { 11 | // runApp(MyApp()); // 旧的 12 | runMockApp(KeyboardRootWidget(child: MyApp())); // 新的 13 | } 14 | ``` 15 | 16 | 17 | ## Step1 18 | 编写个性化的键盘 19 | 20 | ```dart 21 | class NumberKeyboard extends StatelessWidget{ 22 | static const CKTextInputType inputType = const CKTextInputType(name:'CKNumberKeyboard'); //定义InputType类型 23 | static double getHeight(BuildContext ctx){ //编写获取高度的方法 24 | ... 25 | } 26 | 27 | 28 | final KeyboardController controller ; //用于控制键盘输出的Controller 29 | const NumberKeyboard({this.controller}); 30 | 31 | static register(){ //注册键盘的方法 32 | CoolKeyboard.addKeyboard(NumberKeyboard.inputType,KeyboardConfig(builder: (context,controller, params){ // 可通过CKTextInputType传参数到键盘内部 33 | return NumberKeyboard(controller: controller); 34 | },getHeight: NumberKeyboard.getHeight)); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { //键盘的具体内容 39 | ... 40 | return Container( //例子 41 | color: Colors.white, 42 | child: GestureDetector( 43 | behavior: HitTestBehavior.translucent, 44 | child: Center(child: Text('1'),), 45 | onTap: (){ 46 | controller.addText('1'); 往输入框光标位置添加一个1 47 | }, 48 | ), 49 | ) 50 | } 51 | } 52 | ``` 53 | 54 | ## Step2 55 | 注册键盘,并且添加了KeyboardRootWidget 56 | 57 | ```dart 58 | void main(){ 59 | NumberKeyboard.register(); //注册键盘 60 | runApp(KeyboardRootWidget(child: MyApp())); //添加了KeyboardRootWidget 61 | } 62 | ``` 63 | 64 | ## Step3 65 | 给需要使用自定义键盘的页面添加以下代码 66 | 67 | ```dart 68 | @override 69 | Widget build(BuildContext context) { 70 | return KeyboardMediaQuery(//用于键盘弹出的时候页面可以滚动到输入框的位置 71 | child: Page 72 | ); 73 | } 74 | ``` 75 | 76 | 77 | ## Step4 78 | 具体使用 79 | ```dart 80 | TextField( 81 | ... 82 | keyboardType: NumberKeyboard.inputType, 像平常一样设置键盘输入类型一样将Step1编写的inputType传递进去 83 | ... 84 | ) 85 | ``` 86 | 87 | # KeyboardController 88 | 89 | 90 | - [deleteOne](#deleteOne) 91 | - [addText](#addText) 92 | - [doneAction](#doneAction) 93 | - [nextAction](#nextAction) 94 | - [sendPerformAction](#sendPerformAction) 95 | 96 | 97 | ### deleteOne() 98 | 删除一个字符,一般用于键盘的删除键 99 | 100 | ### addText(String insertText) 101 | 在光标位置添加文字,一般用于键盘输入 102 | 103 | ### doneAction() 104 | 触发键盘的完成Action 105 | 106 | ### nextAction() 107 | 触发键盘的下一项Action 108 | 109 | ### newLineAction() 110 | 触发键盘的换行Action 111 | 112 | ### sendPerformAction(TextInputAction action) 113 | ///发送其他Action 114 | 115 | 116 | ## 特别问题讲解: 117 | 1. 报错: CoolKeyboard can only be used in MockBinding
118 | 答: 可能是因为调用了WidgetsFlutterBinding.ensureInitialized()导致的,请将WidgetsFlutterBinding.ensureInitialized() 替换成 MockBinding.ensureInitialized() 119 | -------------------------------------------------------------------------------- /documents/images/custom_keyboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/documents/images/custom_keyboard.gif -------------------------------------------------------------------------------- /documents/images/popover_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/documents/images/popover_demo.gif -------------------------------------------------------------------------------- /documents/images/toast_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/documents/images/toast_demo.gif -------------------------------------------------------------------------------- /documents/popover.md: -------------------------------------------------------------------------------- 1 | 2 | ## CupertinoPopoverButton 3 | 仿iOS的UIPopover效果的 4 | 5 | 用于弹窗的按钮 6 | ```dart 7 | CupertinoPopoverButton({ 8 | this.child, 9 | this.popoverBuild, 10 | this.popoverColor=Colors.white, 11 | this.popoverBoxShadow, 12 | @required this.popoverWidth, 13 | @required this.popoverHeight, 14 | BoxConstraints popoverConstraints, 15 | this.direction = CupertinoPopoverDirection.bottom, 16 | this.onTap, 17 | this.transitionDuration=const Duration(milliseconds: 200), 18 | this.barrierColor = Colors.black54, 19 | this.radius=8.0}); 20 | ``` 21 | 22 | 23 | | Param | Type | Default | Description | 24 | | --- | --- | --- | --- | 25 | | child | Widget | | 按钮的内容 | 26 | | popoverBuild | WidgetBuilder | | 生成弹出框的内容 | 27 | | [popoverWidth] | double | | 弹出框的宽度 | 28 | | [popoverHeight] | double | | 弹出框的高度 | 29 | | [popoverConstraints] | BoxConstraints | maxHeight:123.0 maxWidth:150.0 | 弹出框的最大最小高宽| 30 | | [direction] | CupertinoPopoverDirection | CupertinoPopoverDirection.bottom | 方向| 31 | | [onTap] | BoolCallback | | 按钮点击事件,返回true取消默认反应(不打开Popover) | 32 | | [popoverColor] | Color | 白色 | 弹出框的背景颜色 | 33 | | [popoverBoxShadow] | BoxShadow | | 弹出框的阴影 | 34 | | [barrierColor] | Color | Colors.black54 | 遮罩层的颜色,目前不允许设置透明,如需要透明则使用Color.fromRGBO(0, 0, 0, 0.01)可达到类似效果| 35 | | [transitionDuration] | Duration | 0.2s | 过度动画时间 | 36 | | [radius] | double | 8.0 | 弹出框的圆角弧度 | 37 | 38 | 39 | **Example** 40 | 41 | ```dart 42 | CupertinoPopoverButton( 43 | child: Container( 44 | margin: EdgeInsets.all(20.0), 45 | width: 80.0, 46 | height: 40.0, 47 | decoration: BoxDecoration( 48 | color: Colors.white, 49 | borderRadius: BorderRadius.all(Radius.circular(5.0)), 50 | boxShadow: [BoxShadow(color: Colors.black12,blurRadius: 5.0)] 51 | ), 52 | child: Center(child:Text('左上角')), 53 | ), 54 | popoverBuild:(BuildContext context){ 55 | return Container( 56 | width: 100.0, 57 | height: 100.0, 58 | child: Text('左上角内容'), 59 | ) 60 | }); 61 | ``` 62 | 63 | 64 | 65 | 66 | ## CupertinoPopoverMenuList 67 | Popover弹出的菜单样式列表,一般与[CupertinoPopoverMenuItem](#CupertinoPopoverMenuItem)一起用,会给两个Item加间隔线 68 | ```dart 69 | CupertinoPopoverMenuList({this.children}) 70 | ``` 71 | | Param | Type | Description | 72 | | --- | --- | --- | 73 | | children | List | 子元素,一般是CupertinoPopoverMenuItem | 74 | 75 | 76 | ## CupertinoPopoverMenuItem 77 | 单个菜单项 78 | 79 | ```dart 80 | const CupertinoPopoverMenuItem({ 81 | this.leading, 82 | this.child, 83 | this.onTap, 84 | this.background = Colors.white, 85 | this.activeBackground = const Color(0xFFd9d9d9), 86 | this.isTapClosePopover=true 87 | }); 88 | ``` 89 | | Param | Type | Default | Description | 90 | | --- | --- | --- | --- | 91 | | [leading] | Widget | 菜单左边,一般放图标 | 92 | | [child] | Widget | 菜单内容 | 93 | | [onTap] | BoolCallback | | 按钮点击事件,返回true取消默认反应(不关闭Popover) | 94 | | [activeBackground] | Color | Color(0xFFd9d9d9) | 按下时背景色 | 95 | | [background] | Color | Colors.white | 默认背景色 | 96 | | [isTapClosePopover] | bool | 是否点击关闭 | 97 | 98 | #### 案例核心代码 99 | ```dart 100 | CupertinoPopoverMenuList( 101 | children: [ 102 | CupertinoPopoverMenuItem(leading: Icon(Icons.add),child: Text("新增"),), 103 | CupertinoPopoverMenuItem(leading: Icon(Icons.edit),child: Text("修改"),), 104 | CupertinoPopoverMenuItem(leading: Icon(Icons.delete),child: Text("删除"),) 105 | ], 106 | ) 107 | ``` 108 | 109 | -------------------------------------------------------------------------------- /documents/weui_toast.md: -------------------------------------------------------------------------------- 1 | 2 | ## showWeuiToast 3 | 仿Weui的Toast效果 4 | ```dart 5 | VoidCallback showWeuiToast({ 6 | @required BuildContext context, 7 | @required Widget message, 8 | @required Widget icon, 9 | bool stopEvent = false, 10 | Alignment alignment, 11 | bool backButtonClose}) 12 | ``` 13 | | Param | Type | Default | Description | 14 | | --- | --- | --- | --- | 15 | | [context] | BuildContext | | 上下文 | 16 | | [message] | Widget | | 提示消息 | 17 | | [icon] | Widget | | 图标 | 18 | | [stopEvent] | bool | false | 阻止父页面事件触发 | 19 | | [alignment] | Alignment| 默认是居中偏上 | Toast的位置 | 20 | | [backButtonClose] | bool | | 安卓返回按钮是否关闭Toast | 21 | 22 | 返回参数:VoidCallback,用于关闭Toast 23 | 24 | 25 | 26 | 27 | ## showWeuiSuccessToast 28 | 仿Weui的SuccessToast效果 29 | ```dart 30 | Future showWeuiSuccessToast({ 31 | @required BuildContext context, 32 | Widget message, 33 | bool stopEvent, 34 | bool backButtonClose, 35 | Alignment alignment, 36 | Duration closeDuration 37 | }) 38 | ``` 39 | | Param | Type | Default | Description | 40 | | --- | --- | --- | --- | 41 | | [context] | BuildContext | | 上下文 | 42 | | [alignment] | Alignment| 默认是居中偏上 | Toast的位置 | 43 | | [message] | Widget | 成功| 提示消息 | 44 | | [stopEvent] | bool | false | 阻止父页面事件触发 | 45 | | [closeDuration] | Duration | 3s | 关闭时间 | 46 | | [backButtonClose] | bool | true | 安卓返回按钮是否关闭Toast | 47 | 48 | 返回参数:Future dart 异步操作,代表Toast已关闭 49 | 50 | 51 | ## showWeuiLoadingToast 52 | 仿Weui的LoadingToast效果 53 | ```dart 54 | VoidCallback showWeuiToast({ 55 | @required BuildContext context, 56 | Widget message, 57 | stopEvent = true, 58 | bool backButtonClose, 59 | Alignment alignment 60 | }) 61 | ``` 62 | | Param | Type | Default | Description | 63 | | --- | --- | --- | --- | 64 | | [context] | BuildContext | | 上下文 | 65 | | [message] | Widget | | 提示消息 | 66 | | [stopEvent] | bool | true | 阻止父页面事件触发 | 67 | | [backButtonClose] | bool | false | 安卓返回按钮是否关闭Toast | 68 | | [alignment] | Alignment| 默认是居中偏上 | Toast的位置 | 69 | 70 | 返回参数:VoidCallback,用于关闭Toast 71 | 72 | 73 | ## WeuiToastConfigData 74 | 设置默认Toast效果 75 | ```dart 76 | const WeuiToastConfigData({this.successText = '成功', 77 | this.successDuration = const Duration(seconds: 3), 78 | this.successBackButtonClose = true, 79 | this.loadingText = '加载中', 80 | this.loadingBackButtonClose = false, 81 | this.toastAlignment = const Alignment(0.0, -0.2)}); 82 | ``` 83 | 84 | | Param | Type | Default | Description | 85 | | --- | --- | --- | --- | 86 | | [successText] | String | 成功 | 成功提示消息 | 87 | | [successDuration] | Duration |3s | 成功Toast关闭事件 | 88 | | [successBackButtonClose] | bool | true | 成功安卓返回按钮是否关闭Toast | 89 | | [loadingText] | String | 加载中 | 加载中提示消息 | 90 | | [loadingBackButtonClose] | false | true | 加载中安卓返回按钮是否关闭Toast | 91 | | [alignment] | Alignment| 默认是居中偏上 | Toast的位置 | 92 | 93 | ## WeuiToastConfig 94 | 设置默认Toast效果 配合WeuiToastConfigData使用 95 | ```dart 96 | WeuiToastConfig({Widget child,this.data}) 97 | ``` 98 | | Param | Type | Default | Description | 99 | | --- | --- | --- | --- | 100 | | [child] | Widget | | Widget | 101 | | [data] | WeuiToastConfigData | | Toast配置数据 | 102 | 在Widget中添加了WeuiToastConfig,然后子Widget的context就可以应用到效果了 103 | 例如: 104 | ```dart 105 | main.dart 106 | class MyApp extends StatelessWidget { 107 | // This widget is the root of your application. 108 | @override 109 | Widget build(BuildContext context) { 110 | return WeuiToastConfig( // --关键代码 111 | data: WeuiToastConfigData( // --关键代码 112 | successText: '测试ConfigData' // --关键代码 113 | ), // --关键代码 114 | child:MaterialApp( 115 | title: 'Flutter Demo', 116 | theme: ThemeData( 117 | primarySwatch: Colors.blue 118 | ), 119 | home: MyHomePage(title: 'Flutter Demo Home Page') 120 | )); 121 | } 122 | } 123 | ``` 124 | 代表全局默认配置 125 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 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 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 17 | base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 18 | - platform: windows 19 | create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 20 | base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # cool_ui_example 2 | 3 | Cool UI Example 4 | 5 | ## Getting Started 6 | 7 | For help getting started with Flutter, view our online 8 | [documentation](https://flutter.io/). 9 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.example.example" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/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/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/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/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/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/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/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/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/assets/fonts/CoolUIExample.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/example/assets/fonts/CoolUIExample.ttf -------------------------------------------------------------------------------- /example/lib/cool_u_i_example_icons.dart: -------------------------------------------------------------------------------- 1 | /// Flutter icons CoolUIExample 2 | /// Copyright (C) 2018 by original authors @ fluttericon.com, fontello.com 3 | /// This font was generated by FlutterIcon.com, which is derived from Fontello. 4 | /// 5 | /// To use this font, place it in your fonts/ directory and include the 6 | /// following in your pubspec.yaml 7 | /// 8 | /// flutter: 9 | /// fonts: 10 | /// - family: CoolUIExample 11 | /// fonts: 12 | /// - asset: fonts/CoolUIExample.ttf 13 | /// 14 | /// 15 | /// 16 | import 'package:flutter/widgets.dart'; 17 | 18 | class CoolUIExampleIcon { 19 | CoolUIExampleIcon._(); 20 | 21 | static const _kFontFam = 'CoolUIExample'; 22 | 23 | static const IconData popover = const IconData(0xe801, fontFamily: _kFontFam); 24 | } 25 | -------------------------------------------------------------------------------- /example/lib/keyboards/test_keyboard.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:cool_ui/cool_ui.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class TestKeyboard extends StatelessWidget{ 6 | static const CKTextInputType inputType = const CKTextInputType(name:'CKTestKeyboard'); 7 | static double getHeight(BuildContext ctx){ 8 | MediaQueryData mediaQuery = MediaQuery.of(ctx); 9 | return mediaQuery.size.width / 3 / 2 * 2; 10 | } 11 | final KeyboardController controller ; 12 | const TestKeyboard({required this.controller}); 13 | 14 | static register(){ 15 | CoolKeyboard.addKeyboard(TestKeyboard.inputType,KeyboardConfig(builder: (context,controller, params){ 16 | return TestKeyboard(controller: controller); 17 | },getHeight: TestKeyboard.getHeight)); 18 | } 19 | 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | MediaQueryData mediaQuery = MediaQuery.of(context); 24 | return Material( 25 | child: DefaultTextStyle(style: TextStyle(fontWeight: FontWeight.w500,color: Colors.black,fontSize: 23.0), child: Container( 26 | height:getHeight(context), 27 | width: mediaQuery.size.width, 28 | decoration: BoxDecoration( 29 | color: Color(0xffafafaf), 30 | ), 31 | child: GridView.count( 32 | childAspectRatio: 2/1, 33 | mainAxisSpacing:0.5, 34 | crossAxisSpacing:0.5, 35 | padding: EdgeInsets.all(0.0), 36 | crossAxisCount: 3, 37 | children: [ 38 | buildButton('A'), 39 | buildButton('B'), 40 | buildButton('C'), 41 | buildButton('D'), 42 | buildButton('E'), 43 | buildButton('F'), 44 | buildButton('G'), 45 | buildButton('H'), 46 | buildButton('J'), 47 | Container( 48 | color: Color(0xFFd3d6dd), 49 | child: GestureDetector( 50 | behavior: HitTestBehavior.translucent, 51 | child: Center(child: Icon(Icons.expand_more),), 52 | onTap: (){ 53 | controller.doneAction(); 54 | }, 55 | ), 56 | ), 57 | buildButton('0'), 58 | Container( 59 | color: Color(0xFFd3d6dd), 60 | child: GestureDetector( 61 | behavior: HitTestBehavior.translucent, 62 | child: Center(child: Text('X'),), 63 | onTap: (){ 64 | controller.deleteOne(); 65 | }, 66 | ), 67 | ), 68 | ]), 69 | )), 70 | ); 71 | } 72 | 73 | Widget buildButton(String title,{String? value}){ 74 | return Container( 75 | color: Colors.white, 76 | child: GestureDetector( 77 | behavior: HitTestBehavior.translucent, 78 | child: Center(child: Text(title),), 79 | onTap: (){ 80 | controller.addText(value ?? title); 81 | }, 82 | ), 83 | ); 84 | } 85 | } 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:cool_ui_example/cool_u_i_example_icons.dart'; 2 | import 'package:cool_ui_example/pages/custom_keyboard.dart'; 3 | import 'package:cool_ui_example/pages/paint_event_demo.dart'; 4 | import 'package:cool_ui_example/pages/popover_demo.dart'; 5 | import 'package:cool_ui_example/pages/table_demo.dart'; 6 | import 'package:cool_ui_example/pages/weui_toast_demo.dart'; 7 | import 'package:cool_ui/cool_ui.dart'; 8 | import 'package:flutter/material.dart'; 9 | 10 | import 'keyboards/test_keyboard.dart'; 11 | 12 | void main() { 13 | NumberKeyboard.register(); 14 | TestKeyboard.register(); 15 | runMockApp(KeyboardRootWidget(child: MyApp())); 16 | // runApp(MyApp()); 17 | } 18 | 19 | class MyApp extends StatelessWidget { 20 | // This widget is the root of your application. 21 | @override 22 | Widget build(BuildContext context) { 23 | return WeuiToastConfig( 24 | data: WeuiToastConfigData(successText: '测试ConfigData'), 25 | child: MaterialApp( 26 | title: 'Flutter Demo', 27 | theme: ThemeData( 28 | // This is the theme of your application. 29 | // 30 | // Try running your application with "flutter run". You'll see the 31 | // application has a blue toolbar. Then, without quitting the app, try 32 | // changing the primarySwatch below to Colors.green and then invoke 33 | // "hot reload" (press "r" in the console where you ran "flutter run", 34 | // or press Run > Flutter Hot Reload in IntelliJ). Notice that the 35 | // counter didn't reset back to zero; the application is not restarted. 36 | primarySwatch: Colors.blue), 37 | home: MyHomePage(title: 'Flutter Demo Home Page'))); 38 | } 39 | } 40 | 41 | class MyHomePage extends StatefulWidget { 42 | MyHomePage({Key? key, this.title = ''}) : super(key: key); 43 | 44 | // This widget is the home page of your application. It is stateful, meaning 45 | // that it has a State object (defined below) that contains fields that affect 46 | // how it looks. 47 | 48 | // This class is the configuration for the state. It holds the values (in this 49 | // case the title) provided by the parent (in this case the App widget) and 50 | // used by the build method of the State. Fields in a Widget subclass are 51 | // always marked "final". 52 | 53 | final String title; 54 | 55 | @override 56 | _MyHomePageState createState() => _MyHomePageState(); 57 | } 58 | 59 | class _MyHomePageState extends State { 60 | int _counter = 0; 61 | 62 | void _incrementCounter() { 63 | setState(() { 64 | // This call to setState tells the Flutter framework that something has 65 | // changed in this State, which causes it to rerun the build method below 66 | // so that the display can reflect the updated values. If we changed 67 | // _counter without calling setState(), then the build method would not be 68 | // called again, and so nothing would appear to happen. 69 | _counter++; 70 | }); 71 | } 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | // This method is rerun every time setState is called, for instance as done 76 | // by the _incrementCounter method above. 77 | // 78 | // The Flutter framework has been optimized to make rerunning build methods 79 | // fast, so that you can just rebuild anything that needs updating rather 80 | // than having to individually change instances of widgets. 81 | return Scaffold( 82 | appBar: AppBar( 83 | // Here we take the value from the MyHomePage object that was created by 84 | // the App.build method, and use it to set our appbar title. 85 | title: Text(widget.title), 86 | ), 87 | body: ListView( 88 | children: [ 89 | ListTile( 90 | leading: Icon(CoolUIExampleIcon.popover), 91 | title: Text("Popover"), 92 | onTap: () { 93 | Navigator.of(context).push( 94 | MaterialPageRoute(builder: (context) => PopoverDemo())); 95 | }, 96 | ), 97 | ListTile( 98 | title: Text("PaintEvent"), 99 | onTap: () { 100 | Navigator.of(context).push( 101 | MaterialPageRoute(builder: (context) => PaintEventDemo())); 102 | }, 103 | ), 104 | ListTile( 105 | title: Text("WeuiToastEvent"), 106 | onTap: () { 107 | Navigator.of(context).push( 108 | MaterialPageRoute(builder: (context) => WeuiToastDemo())); 109 | }, 110 | ), 111 | ListTile( 112 | title: Text("CustomKeyboardEvent"), 113 | onTap: () { 114 | Navigator.of(context).push(MaterialPageRoute( 115 | builder: (context) => CustomKeyboardDemo())); 116 | }, 117 | ), 118 | ListTile( 119 | title: Text("TableEvent"), 120 | onTap: () { 121 | Navigator.of(context) 122 | .push(MaterialPageRoute(builder: (context) => TableDemo())); 123 | }, 124 | ) 125 | ], 126 | )); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /example/lib/pages/custom_keyboard.dart: -------------------------------------------------------------------------------- 1 | import 'package:cool_ui_example/keyboards/test_keyboard.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:cool_ui/cool_ui.dart'; 5 | import 'package:flutter/services.dart'; 6 | 7 | class CustomKeyboardDemo extends StatefulWidget { 8 | @override 9 | State createState() { 10 | // TODO: implement createState 11 | return CustomKeyboardDemoState(); 12 | } 13 | } 14 | 15 | class CustomKeyboardDemoState extends State { 16 | TextEditingController textEditingController = 17 | TextEditingController(text: 'test'); 18 | TextEditingController textEditing2Controller = 19 | TextEditingController(text: 'test'); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | // TODO: implement build 24 | return KeyboardMediaQuery(child: Builder(builder: (ctx) { 25 | // CoolKeyboard.init(ctx); 26 | return Scaffold( 27 | appBar: AppBar( 28 | title: Text("Custom Keyboard Demo"), 29 | ), 30 | body: ListView( 31 | children: [ 32 | TextField( 33 | controller: textEditingController, 34 | keyboardType: TextInputType.text, 35 | ), 36 | Container( 37 | height: 300, 38 | ), 39 | MaterialButton( 40 | child: Text('弹出功能演示'), 41 | onPressed: () { 42 | showInputDialogs( 43 | context: context, 44 | messageWidget: Text('弹出输入功能演示'), 45 | keyboardType: NumberKeyboard.inputType); 46 | }, 47 | ), 48 | TextField( 49 | controller: textEditing2Controller, 50 | decoration: InputDecoration(labelText: '演示键盘弹出后滚动'), 51 | keyboardType: NumberKeyboard.inputType, 52 | ), 53 | TextField( 54 | decoration: InputDecoration(labelText: '多个键盘演示'), 55 | keyboardType: NumberKeyboard.inputType, 56 | ) 57 | ], 58 | )); 59 | })); 60 | } 61 | 62 | static Future showInputDialogs( 63 | {required BuildContext context, 64 | Widget? titleWidget, 65 | Widget? messageWidget, 66 | List? inputFormatters, 67 | TextInputType keyboardType = TextInputType.number}) { 68 | String? value; 69 | return showCupertinoDialog( 70 | context: context, 71 | builder: (context) { 72 | // The minimum insets for contents of the Scaffold to keep visible. 73 | List children = []; 74 | if (messageWidget != null) { 75 | children.add(messageWidget); 76 | } 77 | children.add(Form( 78 | child: Container( 79 | padding: EdgeInsets.only(top: 10), 80 | child: Material( 81 | child: Column( 82 | children: [ 83 | TextField( 84 | inputFormatters: inputFormatters, 85 | keyboardType: keyboardType, 86 | autofocus: true, 87 | onChanged: (newValue) { 88 | value = newValue; 89 | }, 90 | ), 91 | TextField( 92 | inputFormatters: inputFormatters, 93 | keyboardType: TextInputType.text, 94 | onChanged: (newValue) { 95 | value = newValue; 96 | }, 97 | ), 98 | TextField( 99 | inputFormatters: inputFormatters, 100 | keyboardType: keyboardType, 101 | onChanged: (newValue) { 102 | value = newValue; 103 | }, 104 | ) 105 | ], 106 | ))))); 107 | return CupertinoAlertDialog( 108 | title: titleWidget, 109 | content: Column( 110 | children: children, 111 | ), 112 | actions: [ 113 | CupertinoDialogAction( 114 | child: Text("取消"), 115 | onPressed: () => Navigator.of(context).pop(), 116 | ), 117 | CupertinoDialogAction( 118 | child: Text("確認"), 119 | onPressed: () { 120 | Navigator.of(context).pop(value ?? ''); 121 | }, 122 | ) 123 | ], 124 | ); 125 | }); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /example/lib/pages/paint_event_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:cool_ui/cool_ui.dart'; 4 | 5 | class PaintEventDemo extends StatefulWidget { 6 | @override 7 | State createState() { 8 | // TODO: implement createState 9 | return PaintEventDemoState(); 10 | } 11 | } 12 | 13 | class PaintEventDemoState extends State { 14 | bool isPaintBackgroud = false; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | // TODO: implement build 19 | return Scaffold( 20 | appBar: AppBar( 21 | title: Text("PaintEvent Demo"), 22 | ), 23 | body: Row( 24 | children: [ 25 | MaterialButton( 26 | onPressed: () { 27 | setState(() { 28 | isPaintBackgroud = !isPaintBackgroud; 29 | }); 30 | }, 31 | child: Text(isPaintBackgroud ? "渲染前填充颜色" : "渲染后填充颜色")), 32 | PaintEvent( 33 | child: Text("子Widget文字"), 34 | paintAfter: (context, offset, size) { 35 | if (!isPaintBackgroud) { 36 | final Paint paint = Paint(); 37 | paint.color = Colors.red; 38 | context.canvas.drawRect(offset & size, paint); 39 | } 40 | }, 41 | paintBefore: (context, offset, size) { 42 | if (isPaintBackgroud) { 43 | final Paint paint = Paint(); 44 | paint.color = Colors.red; 45 | context.canvas.drawRect(offset & size, paint); 46 | } 47 | }, 48 | ) 49 | ], 50 | )); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/lib/pages/popover_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:cool_ui/cool_ui.dart'; 4 | 5 | 6 | class PopoverDemo extends StatefulWidget{ 7 | @override 8 | State createState() { 9 | // TODO: implement createState 10 | return PopoverDemoState(); 11 | } 12 | 13 | } 14 | 15 | class PopoverDemoState extends State{ 16 | @override 17 | Widget build(BuildContext context) { 18 | // TODO: implement build 19 | return Scaffold( 20 | appBar: AppBar( 21 | title: Text("Popover Demo"), 22 | ), 23 | body: Column( 24 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 25 | mainAxisSize: MainAxisSize.max, 26 | children: [ 27 | Row( 28 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 29 | mainAxisSize: MainAxisSize.max, 30 | children: [ 31 | _buildPopoverButton("左上角","左上角内容"), 32 | _buildPopoverButton("中间上方","中间上方内容"), 33 | _buildPopoverButton("右上角","右上角内容") 34 | ], 35 | ), 36 | Row( 37 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 38 | mainAxisSize: MainAxisSize.max, 39 | children:[ 40 | _buildPopoverButton("中间左方","中间左方内容"), 41 | _buildPopoverButton("正中间","正中间内容"), 42 | _buildPopoverButton("中间左方","中间左方内容") 43 | ] 44 | ), 45 | Row( 46 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 47 | mainAxisSize: MainAxisSize.max, 48 | children: [ 49 | _buildPopoverButton("左下角","左下角内容"), 50 | _buildPopoverButton("中间下方","中间下方内容"), 51 | _buildPopoverButton("右下角","右下角内容") 52 | ], 53 | ) 54 | ], 55 | ), 56 | ); 57 | } 58 | 59 | Widget _buildPopoverButton(String btnTitle,String bodyMessage){ 60 | return Padding( 61 | padding: EdgeInsets.all(20.0), 62 | child:CupertinoPopoverButton( 63 | popoverBoxShadow: [ 64 | BoxShadow(color: Colors.black12,blurRadius: 5.0) 65 | ], 66 | barrierColor: Color(0x01FFFFFF), 67 | child: Container( 68 | width: 80.0, 69 | height: 40.0, 70 | decoration: BoxDecoration( 71 | color: Colors.white, 72 | borderRadius: BorderRadius.all(Radius.circular(5.0)), 73 | boxShadow: [BoxShadow(color: Colors.black12,blurRadius: 5.0)] 74 | ), 75 | child: Center(child:Text(btnTitle)), 76 | ), 77 | popoverBuild: (context) { 78 | // return Text("satatastas"); 79 | return CupertinoPopoverMenuList( 80 | children: [ 81 | CupertinoPopoverMenuItem(leading: Icon(Icons.add),child: Text("新增"),), 82 | CupertinoPopoverMenuItem(leading: Icon(Icons.edit),child: Text("修改"),), 83 | CupertinoPopoverMenuItem(leading: Icon(Icons.delete),child: Text("删除"),) 84 | ], 85 | ); 86 | }) 87 | 88 | ); 89 | 90 | } 91 | } -------------------------------------------------------------------------------- /example/lib/pages/table_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:cool_ui/cool_ui.dart'; 4 | 5 | 6 | class TableDemo extends StatefulWidget{ 7 | @override 8 | State createState() { 9 | // TODO: implement createState 10 | return TableDemoState(); 11 | } 12 | 13 | } 14 | 15 | class TableDemoState extends State{ 16 | 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | // TODO: implement build 21 | return Scaffold( 22 | appBar: AppBar( 23 | title: Text("Table Demo"), 24 | ), 25 | body: CoolTable() 26 | ); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /example/lib/pages/weui_toast_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:cool_ui/cool_ui.dart'; 4 | 5 | class WeuiToastDemo extends StatefulWidget { 6 | @override 7 | State createState() { 8 | // TODO: implement createState 9 | return WeuiToastDemoState(); 10 | } 11 | } 12 | 13 | class WeuiToastDemoState extends State { 14 | bool isPaintBackgroud = false; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | // TODO: implement build 19 | return Scaffold( 20 | appBar: AppBar( 21 | title: Text("WeuiToast Demo"), 22 | ), 23 | body: ListView( 24 | padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 72.0), 25 | children: [ 26 | MaterialButton( 27 | color: Colors.blue[400], 28 | child: Text( 29 | "成功", 30 | style: TextStyle(color: Colors.white), 31 | ), 32 | onPressed: () => showWeuiSuccessToast(context: context), 33 | ), 34 | MaterialButton( 35 | color: Colors.blue[400], 36 | child: Text( 37 | "加载中", 38 | style: TextStyle(color: Colors.white), 39 | ), 40 | onPressed: () { 41 | var hide = showWeuiLoadingToast(context: context); 42 | Future.delayed(Duration(seconds: 5), () { 43 | hide(); 44 | }); 45 | }, 46 | ), 47 | ].map((Widget button) { 48 | return Container( 49 | padding: const EdgeInsets.symmetric(vertical: 8.0), 50 | child: button); 51 | }).toList(), 52 | )); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cool_ui_example 2 | description: Cool UI Example 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 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: ">=2.12.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | 19 | # The following adds the Cupertino Icons font to your application. 20 | # Use with the CupertinoIcons class for iOS style icons. 21 | cupertino_icons: ^0.1.2 22 | cool_ui: 23 | path: ../ 24 | 25 | dev_dependencies: 26 | flutter_test: 27 | sdk: flutter 28 | 29 | 30 | # For information on the generic Dart part of this file, see the 31 | # following page: https://www.dartlang.org/tools/pub/pubspec 32 | 33 | # The following section is specific to Flutter. 34 | flutter: 35 | 36 | # The following line ensures that the Material Icons font is 37 | # included with your application, so that you can use the icons in 38 | # the material Icons class. 39 | uses-material-design: true 40 | 41 | # To add assets to your application, add an assets section, like this: 42 | # assets: 43 | # - images/a_dot_burr.jpeg 44 | # - images/a_dot_ham.jpeg 45 | 46 | # An image asset can refer to one or more resolution-specific "variants", see 47 | # https://flutter.io/assets-and-images/#resolution-aware. 48 | 49 | # For details regarding adding assets from package dependencies, see 50 | # https://flutter.io/assets-and-images/#from-packages 51 | 52 | # To add custom fonts to your application, add a fonts section here, 53 | # in this "flutter" section. Each entry in this list should have a 54 | # "family" key with the font family name, and a "fonts" key with a 55 | # list giving the asset and other descriptors for the font. For 56 | # example: 57 | fonts: 58 | - family: CoolUIExample 59 | fonts: 60 | - asset: assets/fonts/CoolUIExample.ttf 61 | # - asset: fonts/Schyler-Italic.ttf 62 | # style: italic 63 | # - family: Trajan Pro 64 | # fonts: 65 | # - asset: fonts/TrajanPro.ttf 66 | # - asset: fonts/TrajanPro_Bold.ttf 67 | # weight: 700 68 | # 69 | # For details regarding fonts from package dependencies, 70 | # see https://flutter.io/custom-fonts/#from-packages 71 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:cool_ui_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(new MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(example LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "example") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(SET CMP0063 NEW) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | # Generated plugin build rules, which manage building the plugins and adding 56 | # them to the application. 57 | include(flutter/generated_plugins.cmake) 58 | 59 | 60 | # === Installation === 61 | # Support files are copied into place next to the executable, so that it can 62 | # run in place. This is done instead of making a separate bundle (as on Linux) 63 | # so that building and running from within Visual Studio will work. 64 | set(BUILD_BUNDLE_DIR "$") 65 | # Make the "install" step default, as it's required to run. 66 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 67 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 68 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 69 | endif() 70 | 71 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 72 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 73 | 74 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 75 | COMPONENT Runtime) 76 | 77 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 78 | COMPONENT Runtime) 79 | 80 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 81 | COMPONENT Runtime) 82 | 83 | if(PLUGIN_BUNDLED_LIBRARIES) 84 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 85 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 86 | COMPONENT Runtime) 87 | endif() 88 | 89 | # Fully re-copy the assets directory on each build to avoid having stale files 90 | # from a previous install. 91 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 92 | install(CODE " 93 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 94 | " COMPONENT Runtime) 95 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 96 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 97 | 98 | # Install the AOT library on non-Debug builds only. 99 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 100 | CONFIGURATIONS Profile;Release 101 | COMPONENT Runtime) 102 | -------------------------------------------------------------------------------- /example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # === Flutter Library === 14 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 15 | 16 | # Published to parent scope for install step. 17 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 18 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 19 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 20 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 21 | 22 | list(APPEND FLUTTER_LIBRARY_HEADERS 23 | "flutter_export.h" 24 | "flutter_windows.h" 25 | "flutter_messenger.h" 26 | "flutter_plugin_registrar.h" 27 | "flutter_texture_registrar.h" 28 | ) 29 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 30 | add_library(flutter INTERFACE) 31 | target_include_directories(flutter INTERFACE 32 | "${EPHEMERAL_DIR}" 33 | ) 34 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 35 | add_dependencies(flutter flutter_assemble) 36 | 37 | # === Wrapper === 38 | list(APPEND CPP_WRAPPER_SOURCES_CORE 39 | "core_implementations.cc" 40 | "standard_codec.cc" 41 | ) 42 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 43 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 44 | "plugin_registrar.cc" 45 | ) 46 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 47 | list(APPEND CPP_WRAPPER_SOURCES_APP 48 | "flutter_engine.cc" 49 | "flutter_view_controller.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 52 | 53 | # Wrapper sources needed for a plugin. 54 | add_library(flutter_wrapper_plugin STATIC 55 | ${CPP_WRAPPER_SOURCES_CORE} 56 | ${CPP_WRAPPER_SOURCES_PLUGIN} 57 | ) 58 | apply_standard_settings(flutter_wrapper_plugin) 59 | set_target_properties(flutter_wrapper_plugin PROPERTIES 60 | POSITION_INDEPENDENT_CODE ON) 61 | set_target_properties(flutter_wrapper_plugin PROPERTIES 62 | CXX_VISIBILITY_PRESET hidden) 63 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 64 | target_include_directories(flutter_wrapper_plugin PUBLIC 65 | "${WRAPPER_ROOT}/include" 66 | ) 67 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 68 | 69 | # Wrapper sources needed for the runner. 70 | add_library(flutter_wrapper_app STATIC 71 | ${CPP_WRAPPER_SOURCES_CORE} 72 | ${CPP_WRAPPER_SOURCES_APP} 73 | ) 74 | apply_standard_settings(flutter_wrapper_app) 75 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 76 | target_include_directories(flutter_wrapper_app PUBLIC 77 | "${WRAPPER_ROOT}/include" 78 | ) 79 | add_dependencies(flutter_wrapper_app flutter_assemble) 80 | 81 | # === Flutter tool backend === 82 | # _phony_ is a non-existent file to force this command to run every time, 83 | # since currently there's no way to get a full input/output list from the 84 | # flutter tool. 85 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 86 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 87 | add_custom_command( 88 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 89 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 90 | ${CPP_WRAPPER_SOURCES_APP} 91 | ${PHONY_OUTPUT} 92 | COMMAND ${CMAKE_COMMAND} -E env 93 | ${FLUTTER_TOOL_ENVIRONMENT} 94 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 95 | windows-x64 $ 96 | VERBATIM 97 | ) 98 | add_custom_target(flutter_assemble DEPENDS 99 | "${FLUTTER_LIBRARY}" 100 | ${FLUTTER_LIBRARY_HEADERS} 101 | ${CPP_WRAPPER_SOURCES_CORE} 102 | ${CPP_WRAPPER_SOURCES_PLUGIN} 103 | ${CPP_WRAPPER_SOURCES_APP} 104 | ) 105 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void RegisterPlugins(flutter::PluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 37 | 38 | # Run the Flutter tool portions of the build. This must not be removed. 39 | add_dependencies(${BINARY_NAME} flutter_assemble) 40 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "example" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "example.exe" "\0" 98 | VALUE "ProductName", "example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.CreateAndShow(L"example", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Im-Kevin/cool_ui/6b5864fec5dda333a4ad5a54dadeeff08383101e/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | std::string utf8_string; 52 | if (target_length == 0 || target_length > utf8_string.max_size()) { 53 | return utf8_string; 54 | } 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | 5 | #include "resource.h" 6 | 7 | namespace { 8 | 9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 10 | 11 | // The number of Win32Window objects that currently exist. 12 | static int g_active_window_count = 0; 13 | 14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 15 | 16 | // Scale helper to convert logical scaler values to physical using passed in 17 | // scale factor 18 | int Scale(int source, double scale_factor) { 19 | return static_cast(source * scale_factor); 20 | } 21 | 22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 23 | // This API is only needed for PerMonitor V1 awareness mode. 24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 25 | HMODULE user32_module = LoadLibraryA("User32.dll"); 26 | if (!user32_module) { 27 | return; 28 | } 29 | auto enable_non_client_dpi_scaling = 30 | reinterpret_cast( 31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 32 | if (enable_non_client_dpi_scaling != nullptr) { 33 | enable_non_client_dpi_scaling(hwnd); 34 | FreeLibrary(user32_module); 35 | } 36 | } 37 | 38 | } // namespace 39 | 40 | // Manages the Win32Window's window class registration. 41 | class WindowClassRegistrar { 42 | public: 43 | ~WindowClassRegistrar() = default; 44 | 45 | // Returns the singleton registar instance. 46 | static WindowClassRegistrar* GetInstance() { 47 | if (!instance_) { 48 | instance_ = new WindowClassRegistrar(); 49 | } 50 | return instance_; 51 | } 52 | 53 | // Returns the name of the window class, registering the class if it hasn't 54 | // previously been registered. 55 | const wchar_t* GetWindowClass(); 56 | 57 | // Unregisters the window class. Should only be called if there are no 58 | // instances of the window. 59 | void UnregisterWindowClass(); 60 | 61 | private: 62 | WindowClassRegistrar() = default; 63 | 64 | static WindowClassRegistrar* instance_; 65 | 66 | bool class_registered_ = false; 67 | }; 68 | 69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 70 | 71 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 72 | if (!class_registered_) { 73 | WNDCLASS window_class{}; 74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 75 | window_class.lpszClassName = kWindowClassName; 76 | window_class.style = CS_HREDRAW | CS_VREDRAW; 77 | window_class.cbClsExtra = 0; 78 | window_class.cbWndExtra = 0; 79 | window_class.hInstance = GetModuleHandle(nullptr); 80 | window_class.hIcon = 81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 82 | window_class.hbrBackground = 0; 83 | window_class.lpszMenuName = nullptr; 84 | window_class.lpfnWndProc = Win32Window::WndProc; 85 | RegisterClass(&window_class); 86 | class_registered_ = true; 87 | } 88 | return kWindowClassName; 89 | } 90 | 91 | void WindowClassRegistrar::UnregisterWindowClass() { 92 | UnregisterClass(kWindowClassName, nullptr); 93 | class_registered_ = false; 94 | } 95 | 96 | Win32Window::Win32Window() { 97 | ++g_active_window_count; 98 | } 99 | 100 | Win32Window::~Win32Window() { 101 | --g_active_window_count; 102 | Destroy(); 103 | } 104 | 105 | bool Win32Window::CreateAndShow(const std::wstring& title, 106 | const Point& origin, 107 | const Size& size) { 108 | Destroy(); 109 | 110 | const wchar_t* window_class = 111 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 112 | 113 | const POINT target_point = {static_cast(origin.x), 114 | static_cast(origin.y)}; 115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 117 | double scale_factor = dpi / 96.0; 118 | 119 | HWND window = CreateWindow( 120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 123 | nullptr, nullptr, GetModuleHandle(nullptr), this); 124 | 125 | if (!window) { 126 | return false; 127 | } 128 | 129 | return OnCreate(); 130 | } 131 | 132 | // static 133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 134 | UINT const message, 135 | WPARAM const wparam, 136 | LPARAM const lparam) noexcept { 137 | if (message == WM_NCCREATE) { 138 | auto window_struct = reinterpret_cast(lparam); 139 | SetWindowLongPtr(window, GWLP_USERDATA, 140 | reinterpret_cast(window_struct->lpCreateParams)); 141 | 142 | auto that = static_cast(window_struct->lpCreateParams); 143 | EnableFullDpiSupportIfAvailable(window); 144 | that->window_handle_ = window; 145 | } else if (Win32Window* that = GetThisFromHandle(window)) { 146 | return that->MessageHandler(window, message, wparam, lparam); 147 | } 148 | 149 | return DefWindowProc(window, message, wparam, lparam); 150 | } 151 | 152 | LRESULT 153 | Win32Window::MessageHandler(HWND hwnd, 154 | UINT const message, 155 | WPARAM const wparam, 156 | LPARAM const lparam) noexcept { 157 | switch (message) { 158 | case WM_DESTROY: 159 | window_handle_ = nullptr; 160 | Destroy(); 161 | if (quit_on_close_) { 162 | PostQuitMessage(0); 163 | } 164 | return 0; 165 | 166 | case WM_DPICHANGED: { 167 | auto newRectSize = reinterpret_cast(lparam); 168 | LONG newWidth = newRectSize->right - newRectSize->left; 169 | LONG newHeight = newRectSize->bottom - newRectSize->top; 170 | 171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 173 | 174 | return 0; 175 | } 176 | case WM_SIZE: { 177 | RECT rect = GetClientArea(); 178 | if (child_content_ != nullptr) { 179 | // Size and position the child window. 180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 181 | rect.bottom - rect.top, TRUE); 182 | } 183 | return 0; 184 | } 185 | 186 | case WM_ACTIVATE: 187 | if (child_content_ != nullptr) { 188 | SetFocus(child_content_); 189 | } 190 | return 0; 191 | } 192 | 193 | return DefWindowProc(window_handle_, message, wparam, lparam); 194 | } 195 | 196 | void Win32Window::Destroy() { 197 | OnDestroy(); 198 | 199 | if (window_handle_) { 200 | DestroyWindow(window_handle_); 201 | window_handle_ = nullptr; 202 | } 203 | if (g_active_window_count == 0) { 204 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 205 | } 206 | } 207 | 208 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 209 | return reinterpret_cast( 210 | GetWindowLongPtr(window, GWLP_USERDATA)); 211 | } 212 | 213 | void Win32Window::SetChildContent(HWND content) { 214 | child_content_ = content; 215 | SetParent(content, window_handle_); 216 | RECT frame = GetClientArea(); 217 | 218 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 219 | frame.bottom - frame.top, true); 220 | 221 | SetFocus(child_content_); 222 | } 223 | 224 | RECT Win32Window::GetClientArea() { 225 | RECT frame; 226 | GetClientRect(window_handle_, &frame); 227 | return frame; 228 | } 229 | 230 | HWND Win32Window::GetHandle() { 231 | return window_handle_; 232 | } 233 | 234 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 235 | quit_on_close_ = quit_on_close; 236 | } 237 | 238 | bool Win32Window::OnCreate() { 239 | // No-op; provided for subclasses. 240 | return true; 241 | } 242 | 243 | void Win32Window::OnDestroy() { 244 | // No-op; provided for subclasses. 245 | } 246 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=D:\flutter" 4 | export "FLUTTER_APPLICATION_PATH=D:\Code\GitHub\cool_ui" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_TARGET=lib\main.dart" 7 | export "FLUTTER_BUILD_DIR=build" 8 | export "FLUTTER_BUILD_NAME=1.2.0" 9 | export "FLUTTER_BUILD_NUMBER=1.2.0" 10 | export "DART_OBFUSCATION=false" 11 | export "TRACK_WIDGET_CREATION=true" 12 | export "TREE_SHAKE_ICONS=false" 13 | export "PACKAGE_CONFIG=.dart_tool/package_config.json" 14 | -------------------------------------------------------------------------------- /ios/Runner/GeneratedPluginRegistrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GeneratedPluginRegistrant_h 8 | #define GeneratedPluginRegistrant_h 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface GeneratedPluginRegistrant : NSObject 15 | + (void)registerWithRegistry:(NSObject*)registry; 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | #endif /* GeneratedPluginRegistrant_h */ 20 | -------------------------------------------------------------------------------- /ios/Runner/GeneratedPluginRegistrant.m: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #import "GeneratedPluginRegistrant.h" 8 | 9 | @implementation GeneratedPluginRegistrant 10 | 11 | + (void)registerWithRegistry:(NSObject*)registry { 12 | } 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /lib/cool_ui.dart: -------------------------------------------------------------------------------- 1 | library cool_ui; 2 | 3 | import 'dart:async'; 4 | import 'dart:ui' as ui; 5 | import 'dart:core'; 6 | 7 | import 'package:back_button_interceptor/back_button_interceptor.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter/scheduler.dart'; 11 | import 'package:flutter/services.dart'; 12 | import 'package:flutter/rendering.dart'; 13 | 14 | part 'utils/widget_util.dart'; 15 | part 'utils/screen_util.dart'; 16 | part 'utils/scroll_utils.dart'; 17 | 18 | part 'icons/cool_ui_icons.dart'; 19 | 20 | part 'widgets/popover/cupertino_popover.dart'; 21 | part 'widgets/popover/cupertino_popover_menu_item.dart'; 22 | 23 | part 'widgets/utils/paint_event.dart'; 24 | 25 | part 'dialogs/weui_toast.dart'; 26 | 27 | part 'keyboards/mocks/mock_binding.dart'; 28 | part 'keyboards/mocks/mock_binary_messenger.dart'; 29 | part 'keyboards/keyboard_manager.dart'; 30 | part 'keyboards/number_keyboard.dart'; 31 | part 'keyboards/keyboard_controller.dart'; 32 | part 'keyboards/keyboard_media_query.dart'; 33 | part 'keyboards/keyboard_root.dart'; 34 | 35 | 36 | part 'widgets/tables/table.dart'; -------------------------------------------------------------------------------- /lib/dialogs/weui_toast.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | typedef HideCallback = Future Function(); 4 | 5 | class WeuiToastWidget extends StatelessWidget { 6 | const WeuiToastWidget({ 7 | Key? key, 8 | required this.stopEvent, 9 | required this.alignment, 10 | required this.icon, 11 | required this.message, 12 | }) : super(key: key); 13 | 14 | final bool stopEvent; 15 | final Alignment alignment; 16 | final Widget icon; 17 | final Widget message; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | var widget = Material( 22 | color: Colors.transparent, 23 | child: Align( 24 | alignment: this.alignment, 25 | child: IntrinsicHeight( 26 | child: Container( 27 | width: 122.0, 28 | decoration: BoxDecoration( 29 | color: Color.fromRGBO(17, 17, 17, 0.7), 30 | borderRadius: BorderRadius.circular(5.0)), 31 | constraints: BoxConstraints( 32 | minHeight: 122.0, 33 | ), 34 | child: Column( 35 | children: [ 36 | Container( 37 | margin: EdgeInsets.only(top: 22.0), 38 | constraints: BoxConstraints(minHeight: 55.0), 39 | child: IconTheme( 40 | data: IconThemeData(color: Colors.white, size: 55.0), 41 | child: icon), 42 | ), 43 | DefaultTextStyle( 44 | style: TextStyle(color: Colors.white, fontSize: 16.0), 45 | child: message, 46 | ), 47 | ], 48 | ), 49 | ), 50 | ), 51 | ), 52 | ); 53 | return IgnorePointer( 54 | ignoring: !stopEvent, 55 | child: widget, 56 | ); 57 | } 58 | } 59 | 60 | class WeuiLoadingIcon extends StatefulWidget { 61 | final double size; 62 | 63 | WeuiLoadingIcon({this.size = 50.0}); 64 | 65 | @override 66 | State createState() => WeuiLoadingIconState(); 67 | } 68 | 69 | class WeuiLoadingIconState extends State 70 | with SingleTickerProviderStateMixin { 71 | AnimationController? _controller ; 72 | Animation? _doubleAnimation; 73 | 74 | @override 75 | void initState() { 76 | super.initState(); 77 | _controller = AnimationController( 78 | vsync: this, duration: Duration(milliseconds: 1000)) 79 | ..repeat(); 80 | _doubleAnimation = Tween(begin: 0.0, end: 360.0).animate(_controller!) 81 | ..addListener(() { 82 | setState(() {}); 83 | }); 84 | } 85 | 86 | @override 87 | void dispose() { 88 | // TODO: implement dispose 89 | _controller!.dispose(); 90 | super.dispose(); 91 | } 92 | 93 | @override 94 | Widget build(BuildContext context) { 95 | return Transform.rotate( 96 | angle: _doubleAnimation!.value ~/ 30 * 30.0 * 0.0174533, 97 | child: Image.asset("assets/images/loading.png", 98 | package: "cool_ui", width: widget.size, height: widget.size)); 99 | } 100 | } 101 | 102 | @immutable 103 | class WeuiToastConfigData{ 104 | final String successText; 105 | final Duration successDuration; 106 | final bool successBackButtonClose; 107 | final String loadingText; 108 | final bool loadingBackButtonClose; 109 | final Alignment toastAlignment; 110 | 111 | const WeuiToastConfigData({this.successText = '成功', 112 | this.successDuration = const Duration(seconds: 3), 113 | this.successBackButtonClose = true, 114 | this.loadingText = '加载中', 115 | this.loadingBackButtonClose = false, 116 | this.toastAlignment = const Alignment(0.0, -0.2)}); 117 | 118 | copyWith({String? successText, Duration? successDuration, String? loadingText, Alignment? toastAlignment}){ 119 | return WeuiToastConfigData( 120 | successText: successText ?? this.successText, 121 | successDuration: successDuration ?? this.successDuration, 122 | loadingText: loadingText ?? this.loadingText, 123 | toastAlignment: toastAlignment ?? this.toastAlignment 124 | ); 125 | } 126 | } 127 | 128 | class WeuiToastConfig extends InheritedWidget{ 129 | final WeuiToastConfigData? data; 130 | WeuiToastConfig({required Widget child,this.data}): super(child:child); 131 | 132 | @override 133 | bool updateShouldNotify(WeuiToastConfig oldWidget) { 134 | // TODO: implement updateShouldNotify 135 | return data != oldWidget.data; 136 | } 137 | 138 | 139 | static WeuiToastConfigData of(BuildContext context) { 140 | var widget = context.dependOnInheritedWidgetOfExactType(); 141 | if(widget is WeuiToastConfig){ 142 | return widget.data ?? WeuiToastConfigData(); 143 | } 144 | return WeuiToastConfigData(); 145 | } 146 | } 147 | 148 | Future showWeuiSuccessToast( 149 | {required BuildContext context, 150 | Widget? message, 151 | stopEvent = false, 152 | bool? backButtonClose, 153 | Alignment? alignment, 154 | Duration? closeDuration}) { 155 | 156 | var config = WeuiToastConfig.of(context); 157 | message = message?? Text(config.successText); 158 | closeDuration = closeDuration?? config.successDuration; 159 | backButtonClose = backButtonClose ?? config.successBackButtonClose; 160 | var hide = showWeuiToast( 161 | context: context, 162 | alignment: alignment, 163 | message: message, 164 | stopEvent: stopEvent, 165 | backButtonClose: backButtonClose, 166 | icon: Icon(CoolUIIcons.success_no_circle)); 167 | 168 | return Future.delayed(closeDuration, () { 169 | hide(); 170 | }); 171 | } 172 | 173 | HideCallback showWeuiLoadingToast( 174 | {required BuildContext context, 175 | Widget? message, 176 | stopEvent = true, 177 | bool? backButtonClose, 178 | Alignment? alignment}) { 179 | var config = WeuiToastConfig.of(context); 180 | message = message?? Text(config.loadingText); 181 | backButtonClose = backButtonClose ?? config.loadingBackButtonClose; 182 | 183 | return showWeuiToast( 184 | context: context, 185 | alignment: alignment, 186 | message: message, 187 | stopEvent: stopEvent, 188 | icon: WeuiLoadingIcon(), 189 | backButtonClose: backButtonClose); 190 | } 191 | 192 | int backButtonIndex = 2; 193 | 194 | HideCallback showWeuiToast( 195 | {required BuildContext context, 196 | required Widget message, 197 | required Widget icon, 198 | bool stopEvent = false, 199 | Alignment? alignment, 200 | bool backButtonClose = false}) { 201 | 202 | var config = WeuiToastConfig.of(context); 203 | alignment = alignment ?? config.toastAlignment; 204 | 205 | Completer result = Completer(); 206 | var backButtonName = 'CoolUI_WeuiToast$backButtonIndex'; 207 | BackButtonInterceptor.add((stopDefaultButtonEvent, routeInfo){ 208 | if(backButtonClose){ 209 | result.future.then((hide){ 210 | hide(); 211 | }); 212 | } 213 | return true; 214 | }, zIndex: backButtonIndex, name: backButtonName); 215 | backButtonIndex++; 216 | 217 | OverlayEntry? overlay = OverlayEntry( 218 | maintainState: true, 219 | builder: (_) => WillPopScope( 220 | onWillPop: () async { 221 | var hide = await result.future; 222 | hide(); 223 | return false; 224 | }, 225 | child: WeuiToastWidget( 226 | alignment: alignment!, 227 | icon: icon, 228 | message: message, 229 | stopEvent: stopEvent, 230 | ), 231 | )); 232 | result.complete((){ 233 | if(overlay == null){ 234 | return; 235 | } 236 | overlay!.remove(); 237 | overlay = null; 238 | BackButtonInterceptor.removeByName(backButtonName); 239 | }); 240 | Overlay.of(context)!.insert(overlay!); 241 | 242 | 243 | return () async { 244 | var hide = await result.future; 245 | hide(); 246 | }; 247 | } 248 | -------------------------------------------------------------------------------- /lib/icons/cool_ui_icons.dart: -------------------------------------------------------------------------------- 1 | /// Flutter icons CoolUI 2 | /// Copyright (C) 2018 by original authors @ fluttericon.com, fontello.com 3 | /// This font was generated by FlutterIcon.com, which is derived from Fontello. 4 | /// 5 | /// To use this font, place it in your fonts/ directory and include the 6 | /// following in your pubspec.yaml 7 | /// 8 | /// flutter: 9 | /// fonts: 10 | /// - family: CoolUI 11 | /// fonts: 12 | /// - asset: fonts/CoolUI.ttf 13 | /// 14 | /// 15 | /// 16 | part of cool_ui; 17 | 18 | class CoolUIIcons { 19 | CoolUIIcons._(); 20 | 21 | static const _kFontFam = 'CoolUIIcons'; 22 | 23 | static const IconData success_no_circle = const IconData(0xea08, fontFamily: _kFontFam,fontPackage: "cool_ui"); 24 | } 25 | -------------------------------------------------------------------------------- /lib/keyboards/keyboard_controller.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | class KeyboardController extends ValueNotifier{ 4 | final InputClient client; 5 | 6 | KeyboardController({TextEditingValue? value,required this.client}) 7 | : super(value == null ? TextEditingValue.empty : value); 8 | 9 | 10 | /// The current string the user is editing. 11 | String get text => value.text; 12 | /// Setting this will notify all the listeners of this [TextEditingController] 13 | /// that they need to update (it calls [notifyListeners]). For this reason, 14 | /// this value should only be set between frames, e.g. in response to user 15 | /// actions, not during the build, layout, or paint phases. 16 | set text(String newText) { 17 | value = value.copyWith(text: newText, 18 | selection: const TextSelection.collapsed(offset: -1), 19 | composing: TextRange.empty); 20 | } 21 | 22 | /// The currently selected [text]. 23 | /// 24 | /// If the selection is collapsed, then this property gives the offset of the 25 | /// cursor within the text. 26 | TextSelection get selection => value.selection; 27 | /// Setting this will notify all the listeners of this [TextEditingController] 28 | /// that they need to update (it calls [notifyListeners]). For this reason, 29 | /// this value should only be set between frames, e.g. in response to user 30 | /// actions, not during the build, layout, or paint phases. 31 | set selection(TextSelection newSelection) { 32 | if (newSelection.start > text.length || newSelection.end > text.length) 33 | throw FlutterError('invalid text selection: $newSelection'); 34 | value = value.copyWith(selection: newSelection, composing: TextRange.empty); 35 | } 36 | 37 | set value(TextEditingValue newValue) { 38 | 39 | newValue = newValue.copyWith( // 修正由于默认值导致的Bug 40 | composing: TextRange( 41 | start: newValue.composing.start < 0 ? 0: newValue.composing.start, 42 | end: newValue.composing.end < 0 ? 0: newValue.composing.end 43 | ), 44 | selection: newValue.selection.copyWith( 45 | baseOffset: newValue.selection.baseOffset < 0 ? 0: newValue.selection.baseOffset, 46 | extentOffset: newValue.selection.extentOffset < 0 ? 0: newValue.selection.extentOffset 47 | ) 48 | ); 49 | 50 | super.value = newValue; 51 | } 52 | 53 | /// Set the [value] to empty. 54 | /// 55 | /// After calling this function, [text] will be the empty string and the 56 | /// selection will be invalid. 57 | /// 58 | /// Calling this will notify all the listeners of this [TextEditingController] 59 | /// that they need to update (it calls [notifyListeners]). For this reason, 60 | /// this method should only be called between frames, e.g. in response to user 61 | /// actions, not during the build, layout, or paint phases. 62 | void clear() { 63 | value = TextEditingValue.empty; 64 | } 65 | 66 | /// Set the composing region to an empty range. 67 | /// 68 | /// The composing region is the range of text that is still being composed. 69 | /// Calling this function indicates that the user is done composing that 70 | /// region. 71 | /// 72 | /// Calling this will notify all the listeners of this [TextEditingController] 73 | /// that they need to update (it calls [notifyListeners]). For this reason, 74 | /// this method should only be called between frames, e.g. in response to user 75 | /// actions, not during the build, layout, or paint phases. 76 | clearComposing() { 77 | value = value.copyWith(composing: TextRange.empty); 78 | } 79 | ///删除一个字符,一般用于键盘的删除键 80 | deleteOne(){ 81 | if(selection.baseOffset == 0) 82 | return; 83 | String newText = ''; 84 | if(selection.baseOffset != selection.extentOffset) 85 | { 86 | newText = selection.textBefore(text) + selection.textAfter(text); 87 | value = TextEditingValue( 88 | text: newText, 89 | selection: selection.copyWith( 90 | baseOffset:selection.baseOffset, 91 | extentOffset: selection.baseOffset) 92 | ); 93 | }else{ 94 | newText = text.substring(0,selection.baseOffset - 1) + selection.textAfter(text); 95 | value = TextEditingValue( 96 | text: newText, 97 | selection: selection.copyWith( 98 | baseOffset:selection.baseOffset - 1, 99 | extentOffset: selection.baseOffset - 1) 100 | ); 101 | } 102 | } 103 | 104 | /// 在光标位置添加文字,一般用于键盘输入 105 | addText(String insertText){ 106 | String newText = selection.textBefore(text) + insertText + selection.textAfter(text); 107 | value = TextEditingValue( 108 | text: newText, 109 | selection: selection.copyWith( 110 | baseOffset:selection.baseOffset + insertText.length, 111 | extentOffset: selection.baseOffset + insertText.length) 112 | ); 113 | } 114 | 115 | /// 完成 116 | doneAction(){ 117 | CoolKeyboard.sendPerformAction(TextInputAction.done); 118 | } 119 | 120 | /// 下一个 121 | nextAction(){ 122 | CoolKeyboard.sendPerformAction(TextInputAction.next); 123 | } 124 | 125 | /// 换行 126 | newLineAction(){ 127 | CoolKeyboard.sendPerformAction(TextInputAction.newline); 128 | } 129 | 130 | ///发送其他Action 131 | sendPerformAction(TextInputAction action){ 132 | CoolKeyboard.sendPerformAction(action); 133 | } 134 | } -------------------------------------------------------------------------------- /lib/keyboards/keyboard_manager.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | typedef GetKeyboardHeight = double Function(BuildContext context); 4 | typedef KeyboardBuilder = Widget Function( 5 | BuildContext context, KeyboardController controller, String? param); 6 | 7 | class CoolKeyboard { 8 | static JSONMethodCodec _codec = const JSONMethodCodec(); 9 | static KeyboardConfig? _currentKeyboard; 10 | static Map _keyboards = {}; 11 | static KeyboardRootState? _root; 12 | static BuildContext? _context; 13 | static KeyboardController? _keyboardController; 14 | static GlobalKey? _pageKey; 15 | static bool isInterceptor = false; 16 | 17 | static ValueNotifier _keyboardHeightNotifier = ValueNotifier(0) 18 | ..addListener(updateKeyboardHeight); 19 | 20 | static String? _keyboardParam; 21 | 22 | static Timer? clearTask; 23 | 24 | static init(KeyboardRootState root, BuildContext context) { 25 | _root = root; 26 | _context = context; 27 | interceptorInput(); 28 | } 29 | 30 | static interceptorInput() { 31 | if (isInterceptor) return; 32 | if (!(ServicesBinding.instance is MockBinding)) { 33 | throw Exception('CoolKeyboard can only be used in MockBinding'); 34 | } 35 | var mockBinding = ServicesBinding.instance as MockBinding; 36 | var mockBinaryMessenger = 37 | mockBinding.defaultBinaryMessenger as MockBinaryMessenger; 38 | mockBinaryMessenger.setMockMessageHandler( 39 | "flutter/textinput", _textInputHanlde); 40 | isInterceptor = true; 41 | } 42 | 43 | static Future _textInputHanlde(ByteData? data) async { 44 | var methodCall = _codec.decodeMethodCall(data); 45 | switch (methodCall.method) { 46 | case 'TextInput.show': 47 | if (_currentKeyboard != null) { 48 | if (clearTask != null) { 49 | clearTask!.cancel(); 50 | clearTask = null; 51 | } 52 | openKeyboard(); 53 | return _codec.encodeSuccessEnvelope(null); 54 | } else { 55 | if (data != null) { 56 | return await _sendPlatformMessage("flutter/textinput", data); 57 | } 58 | } 59 | break; 60 | case 'TextInput.hide': 61 | if (_currentKeyboard != null) { 62 | if (clearTask == null) { 63 | clearTask = new Timer(Duration(milliseconds: 16), 64 | () => hideKeyboard(animation: true)); 65 | } 66 | return _codec.encodeSuccessEnvelope(null); 67 | } else { 68 | if (data != null) { 69 | return await _sendPlatformMessage("flutter/textinput", data); 70 | } 71 | } 72 | break; 73 | case 'TextInput.setEditingState': 74 | var editingState = TextEditingValue.fromJSON(methodCall.arguments); 75 | if (_keyboardController != null) { 76 | _keyboardController!.value = editingState; 77 | return _codec.encodeSuccessEnvelope(null); 78 | } 79 | break; 80 | case 'TextInput.clearClient': 81 | var isShow = _currentKeyboard != null; 82 | if (clearTask == null) { 83 | clearTask = new Timer( 84 | Duration(milliseconds: 16), () => hideKeyboard(animation: true)); 85 | } 86 | clearKeyboard(); 87 | if (isShow) { 88 | return _codec.encodeSuccessEnvelope(null); 89 | } 90 | break; 91 | case 'TextInput.setClient': 92 | var setInputType = methodCall.arguments[1]['inputType']; 93 | InputClient? client; 94 | _keyboards.forEach((inputType, keyboardConfig) { 95 | if (inputType.name == setInputType['name']) { 96 | client = InputClient.fromJSON(methodCall.arguments); 97 | 98 | _keyboardParam = 99 | (client!.configuration.inputType as CKTextInputType).params; 100 | 101 | clearKeyboard(); 102 | _currentKeyboard = keyboardConfig; 103 | _keyboardController = KeyboardController(client: client!) 104 | ..addListener(_updateEditingState); 105 | if (_pageKey != null) { 106 | _pageKey!.currentState?.update(); 107 | } 108 | } 109 | }); 110 | 111 | if (client != null) { 112 | await _sendPlatformMessage("flutter/textinput", 113 | _codec.encodeMethodCall(MethodCall('TextInput.hide'))); 114 | return _codec.encodeSuccessEnvelope(null); 115 | } else { 116 | if (clearTask == null) { 117 | hideKeyboard(animation: false); 118 | } 119 | clearKeyboard(); 120 | } 121 | // break; 122 | } 123 | if (data != null) { 124 | ByteData? response = 125 | await _sendPlatformMessage("flutter/textinput", data); 126 | return response; 127 | } 128 | return null; 129 | } 130 | 131 | static void _updateEditingState() { 132 | var callbackMethodCall = MethodCall("TextInputClient.updateEditingState", [ 133 | _keyboardController!.client.connectionId, 134 | _keyboardController!.value.toJSON() 135 | ]); 136 | WidgetsBinding.instance.defaultBinaryMessenger.handlePlatformMessage( 137 | "flutter/textinput", 138 | _codec.encodeMethodCall(callbackMethodCall), 139 | (data) {}); 140 | } 141 | 142 | static Future _sendPlatformMessage( 143 | String channel, ByteData message) { 144 | final Completer completer = Completer(); 145 | ui.window.sendPlatformMessage(channel, message, (ByteData? reply) { 146 | try { 147 | completer.complete(reply); 148 | } catch (exception, stack) { 149 | FlutterError.reportError(FlutterErrorDetails( 150 | exception: exception, 151 | stack: stack, 152 | library: 'services library', 153 | context: 154 | ErrorDescription('during a platform message response callback'), 155 | )); 156 | } 157 | }); 158 | return completer.future; 159 | } 160 | 161 | static addKeyboard(CKTextInputType inputType, KeyboardConfig config) { 162 | _keyboards[inputType] = config; 163 | } 164 | 165 | static openKeyboard() { 166 | var keyboardHeight = _currentKeyboard!.getHeight(_context!); 167 | _keyboardHeightNotifier.value = keyboardHeight; 168 | if (_root!.hasKeyboard && _pageKey != null) return; 169 | _pageKey = GlobalKey(); 170 | // KeyboardMediaQueryState queryState = _context 171 | // .ancestorStateOfType(const TypeMatcher()) 172 | // as KeyboardMediaQueryState; 173 | // queryState.update(); 174 | 175 | var tempKey = _pageKey; 176 | var isUpdate = false; 177 | _root!.setKeyboard((ctx) { 178 | if (_currentKeyboard != null && _keyboardHeightNotifier.value != 0) { 179 | if (!isUpdate) { 180 | isUpdate = true; 181 | // WidgetsBinding.instance.addPostFrameCallback((_) { 182 | // _keyboardController!.addText('1'); 183 | // }); 184 | } 185 | return KeyboardPage( 186 | key: tempKey, 187 | builder: (ctx) { 188 | return _currentKeyboard?.builder( 189 | ctx, _keyboardController!, _keyboardParam); 190 | }, 191 | height: _keyboardHeightNotifier.value); 192 | } else { 193 | return Container(); 194 | } 195 | }); 196 | 197 | BackButtonInterceptor.add((_, __) { 198 | CoolKeyboard.sendPerformAction(TextInputAction.done); 199 | return true; 200 | }, zIndex: 1, name: 'CustomKeyboard'); 201 | } 202 | 203 | static hideKeyboard({bool animation = true}) { 204 | if (clearTask != null) { 205 | if (clearTask!.isActive) { 206 | clearTask!.cancel(); 207 | } 208 | clearTask = null; 209 | } 210 | BackButtonInterceptor.removeByName('CustomKeyboard'); 211 | if (_root!.hasKeyboard && _pageKey != null) { 212 | // _pageKey.currentState.animationController 213 | // .addStatusListener((AnimationStatus status) { 214 | // if (status == AnimationStatus.dismissed || 215 | // status == AnimationStatus.completed) { 216 | // if (_root.hasKeyboard) { 217 | // _keyboardEntry.remove(); 218 | // _keyboardEntry = null; 219 | // } 220 | // } 221 | // }); 222 | if (animation) { 223 | _pageKey!.currentState?.exitKeyboard(); 224 | Future.delayed(Duration(milliseconds: 116)).then((_) { 225 | _root!.clearKeyboard(); 226 | }); 227 | } else { 228 | _root!.clearKeyboard(); 229 | } 230 | } 231 | _pageKey = null; 232 | _keyboardHeightNotifier.value = 0; 233 | try { 234 | // KeyboardMediaQueryState queryState = _context 235 | // .ancestorStateOfType(const TypeMatcher()) 236 | // as KeyboardMediaQueryState; 237 | // queryState.update(); 238 | } catch (_) {} 239 | } 240 | 241 | static clearKeyboard() { 242 | _currentKeyboard = null; 243 | if (_keyboardController != null) { 244 | _keyboardController!.dispose(); 245 | _keyboardController = null; 246 | } 247 | } 248 | 249 | static sendPerformAction(TextInputAction action) { 250 | var callbackMethodCall = MethodCall("TextInputClient.performAction", 251 | [_keyboardController!.client.connectionId, action.toString()]); 252 | WidgetsBinding.instance.defaultBinaryMessenger.handlePlatformMessage( 253 | "flutter/textinput", 254 | _codec.encodeMethodCall(callbackMethodCall), 255 | (data) {}); 256 | } 257 | 258 | static updateKeyboardHeight() { 259 | if (_pageKey != null && 260 | _pageKey!.currentState != null && 261 | clearTask == null) { 262 | _pageKey!.currentState!.updateHeight(_keyboardHeightNotifier.value); 263 | } 264 | } 265 | } 266 | 267 | class KeyboardConfig { 268 | final KeyboardBuilder builder; 269 | final GetKeyboardHeight getHeight; 270 | const KeyboardConfig({required this.builder, required this.getHeight}); 271 | } 272 | 273 | class InputClient { 274 | final int connectionId; 275 | final TextInputConfiguration configuration; 276 | const InputClient({required this.connectionId, required this.configuration}); 277 | 278 | factory InputClient.fromJSON(List encoded) { 279 | return InputClient( 280 | connectionId: encoded[0], 281 | configuration: TextInputConfiguration( 282 | inputType: CKTextInputType.fromJSON(encoded[1]['inputType']), 283 | obscureText: encoded[1]['obscureText'], 284 | autocorrect: encoded[1]['autocorrect'], 285 | actionLabel: encoded[1]['actionLabel'], 286 | inputAction: _toTextInputAction(encoded[1]['inputAction']), 287 | textCapitalization: 288 | _toTextCapitalization(encoded[1]['textCapitalization']), 289 | keyboardAppearance: 290 | _toBrightness(encoded[1]['keyboardAppearance']))); 291 | } 292 | 293 | static TextInputAction _toTextInputAction(String action) { 294 | switch (action) { 295 | case 'TextInputAction.none': 296 | return TextInputAction.none; 297 | case 'TextInputAction.unspecified': 298 | return TextInputAction.unspecified; 299 | case 'TextInputAction.go': 300 | return TextInputAction.go; 301 | case 'TextInputAction.search': 302 | return TextInputAction.search; 303 | case 'TextInputAction.send': 304 | return TextInputAction.send; 305 | case 'TextInputAction.next': 306 | return TextInputAction.next; 307 | case 'TextInputAction.previuos': 308 | return TextInputAction.previous; 309 | case 'TextInputAction.continue_action': 310 | return TextInputAction.continueAction; 311 | case 'TextInputAction.join': 312 | return TextInputAction.join; 313 | case 'TextInputAction.route': 314 | return TextInputAction.route; 315 | case 'TextInputAction.emergencyCall': 316 | return TextInputAction.emergencyCall; 317 | case 'TextInputAction.done': 318 | return TextInputAction.done; 319 | case 'TextInputAction.newline': 320 | return TextInputAction.newline; 321 | } 322 | throw FlutterError('Unknown text input action: $action'); 323 | } 324 | 325 | static TextCapitalization _toTextCapitalization(String capitalization) { 326 | switch (capitalization) { 327 | case 'TextCapitalization.none': 328 | return TextCapitalization.none; 329 | case 'TextCapitalization.characters': 330 | return TextCapitalization.characters; 331 | case 'TextCapitalization.sentences': 332 | return TextCapitalization.sentences; 333 | case 'TextCapitalization.words': 334 | return TextCapitalization.words; 335 | } 336 | 337 | throw FlutterError('Unknown text capitalization: $capitalization'); 338 | } 339 | 340 | static Brightness _toBrightness(String brightness) { 341 | switch (brightness) { 342 | case 'Brightness.dark': 343 | return Brightness.dark; 344 | case 'Brightness.light': 345 | return Brightness.light; 346 | } 347 | 348 | throw FlutterError('Unknown Brightness: $brightness'); 349 | } 350 | } 351 | 352 | class CKTextInputType extends TextInputType { 353 | final String name; 354 | final String? params; 355 | 356 | const CKTextInputType( 357 | {required this.name, bool? signed, bool? decimal, this.params}) 358 | : super.numberWithOptions(signed: signed, decimal: decimal); 359 | 360 | @override 361 | Map toJson() { 362 | return { 363 | 'name': name, 364 | 'signed': signed, 365 | 'decimal': decimal, 366 | 'params': params 367 | }; 368 | } 369 | 370 | @override 371 | String toString() { 372 | return '$runtimeType(' 373 | 'name: $name, ' 374 | 'signed: $signed, ' 375 | 'decimal: $decimal)'; 376 | } 377 | 378 | bool operator ==(Object target) { 379 | if (target is CKTextInputType) { 380 | if (this.name == target.toString()) { 381 | return true; 382 | } 383 | } 384 | return false; 385 | } 386 | 387 | @override 388 | int get hashCode => this.toString().hashCode; 389 | 390 | factory CKTextInputType.fromJSON(Map encoded) { 391 | return CKTextInputType( 392 | name: encoded['name'], 393 | signed: encoded['signed'], 394 | decimal: encoded['decimal'], 395 | params: encoded['params']); 396 | } 397 | } 398 | 399 | class KeyboardPage extends StatefulWidget { 400 | final Widget? Function(BuildContext context) builder; 401 | final double height; 402 | const KeyboardPage({required this.builder, this.height = 0, Key? key}) 403 | : super(key: key); 404 | 405 | @override 406 | State createState() => KeyboardPageState(); 407 | } 408 | 409 | class KeyboardPageState extends State { 410 | Widget? _lastBuildWidget; 411 | bool isClose = false; 412 | double _height = 0; 413 | 414 | @override 415 | void initState() { 416 | // TODO: implement initState 417 | super.initState(); 418 | 419 | WidgetsBinding.instance.addPostFrameCallback((_) { 420 | _height = widget.height; 421 | setState(() => {}); 422 | }); 423 | } 424 | 425 | @override 426 | Widget build(BuildContext context) { 427 | return AnimatedPositioned( 428 | child: IntrinsicHeight(child: Builder( 429 | builder: (ctx) { 430 | var result = widget.builder(ctx); 431 | if (result != null) { 432 | _lastBuildWidget = result; 433 | } 434 | return ConstrainedBox( 435 | constraints: BoxConstraints( 436 | minHeight: 0, 437 | minWidth: 0, 438 | maxHeight: _height, 439 | maxWidth: _ScreenUtil.getScreenW(context)), 440 | child: _lastBuildWidget, 441 | ); 442 | }, 443 | )), 444 | left: 0, 445 | width: _ScreenUtil.getScreenW(context), 446 | bottom: _height * (isClose ? -1 : 0), 447 | height: _height, 448 | duration: Duration(milliseconds: 100), 449 | ); 450 | } 451 | 452 | @override 453 | void dispose() { 454 | // if (animationController.status == AnimationStatus.forward || 455 | // animationController.status == AnimationStatus.reverse) { 456 | // animationController.notifyStatusListeners(AnimationStatus.dismissed); 457 | // } 458 | // animationController.dispose(); 459 | super.dispose(); 460 | } 461 | 462 | exitKeyboard() { 463 | isClose = true; 464 | } 465 | 466 | update() { 467 | WidgetsBinding.instance.addPostFrameCallback((_) { 468 | setState(() => {}); 469 | }); 470 | } 471 | 472 | updateHeight(double height) { 473 | WidgetsBinding.instance.addPostFrameCallback((_) { 474 | this._height = height; 475 | setState(() => {}); 476 | }); 477 | } 478 | } 479 | -------------------------------------------------------------------------------- /lib/keyboards/keyboard_media_query.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | class KeyboardMediaQuery extends StatefulWidget { 4 | final Widget child; 5 | 6 | KeyboardMediaQuery({required this.child}); 7 | 8 | @override 9 | State createState() => KeyboardMediaQueryState(); 10 | } 11 | 12 | class KeyboardMediaQueryState extends State { 13 | double keyboardHeight = 0; 14 | ValueNotifier keyboardHeightNotifier = 15 | CoolKeyboard._keyboardHeightNotifier; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | CoolKeyboard._keyboardHeightNotifier.addListener(onUpdateHeight); 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | // TODO: implement build 26 | var data = MediaQuery.maybeOf(context); 27 | if (data == null) { 28 | data = MediaQueryData.fromWindow(WidgetsBinding.instance.window); 29 | } 30 | var bottom = CoolKeyboard._keyboardHeightNotifier.value != 0 31 | ? CoolKeyboard._keyboardHeightNotifier.value 32 | : data.viewInsets.bottom; 33 | // TODO: implement build 34 | return MediaQuery( 35 | child: widget.child, 36 | data: data.copyWith( 37 | viewInsets: data.viewInsets.copyWith(bottom: bottom))); 38 | } 39 | 40 | onUpdateHeight() { 41 | SchedulerBinding.instance.addPostFrameCallback((_) { 42 | setState(() => {}); 43 | SchedulerBinding.instance.addPostFrameCallback((_) { 44 | WidgetsBinding.instance.handleMetricsChanged(); 45 | }); 46 | }); 47 | } 48 | 49 | @override 50 | void dispose() { 51 | super.dispose(); 52 | CoolKeyboard._keyboardHeightNotifier.removeListener(onUpdateHeight); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/keyboards/keyboard_root.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | class KeyboardRootWidget extends StatefulWidget { 4 | final Widget child; 5 | 6 | /// The text direction for this subtree. 7 | final TextDirection textDirection; 8 | 9 | const KeyboardRootWidget( 10 | {Key? key, required this.child, this.textDirection = TextDirection.ltr}) 11 | : super(key: key); 12 | 13 | @override 14 | State createState() { 15 | // TODO: implement createState 16 | return KeyboardRootState(); 17 | } 18 | } 19 | 20 | class KeyboardRootState extends State { 21 | WidgetBuilder? _keyboardbuilder; 22 | 23 | bool get hasKeyboard => _keyboardbuilder != null; 24 | // List _initialEntries = []; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | // _initialEntries.add(this.initChild()); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | // TODO: implement build 35 | return KeyboardMediaQuery(child: Builder(builder: (context) { 36 | CoolKeyboard.init(this, context); 37 | 38 | List children = [widget.child]; 39 | if (_keyboardbuilder != null) { 40 | children.add(Builder( 41 | builder: _keyboardbuilder!, 42 | )); 43 | } 44 | return Directionality( 45 | textDirection: widget.textDirection, 46 | child: Stack( 47 | children: children, 48 | )); 49 | })); 50 | } 51 | 52 | setKeyboard(WidgetBuilder keyboardbuilder) { 53 | this._keyboardbuilder = keyboardbuilder; 54 | setState(() {}); 55 | } 56 | 57 | clearKeyboard() { 58 | if (this._keyboardbuilder != null) { 59 | this._keyboardbuilder = null; 60 | setState(() {}); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/keyboards/mocks/mock_binary_messenger.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | //Copy By TestDefaultBinaryMessenger 4 | 5 | class MockBinaryMessenger extends BinaryMessenger { 6 | /// Creates a [MockBinaryMessenger] instance. 7 | /// 8 | MockBinaryMessenger(this.mockBinding); 9 | 10 | /// 用于获取delegate 11 | final MockBinding mockBinding; 12 | 13 | // The handlers for messages from the engine (including fake 14 | // messages sent by handlePlatformMessage). 15 | final Map _inboundHandlers = 16 | {}; 17 | 18 | /// Send a mock message to the framework as if it came from the platform. 19 | /// 20 | /// If a listener has been set using [setMessageHandler], that listener is 21 | /// invoked to handle the message, and this method returns a future that 22 | /// completes with that handler's result. 23 | /// 24 | /// {@template flutter.flutter_test.MockBinaryMessenger.handlePlatformMessage.asyncHandlers} 25 | /// It is strongly recommended that all handlers used with this API be 26 | /// synchronous (not requiring any microtasks to complete), because 27 | /// [testWidgets] tests run in a [FakeAsync] zone in which microtasks do not 28 | /// progress except when time is explicitly advanced (e.g. with 29 | /// [WidgetTester.pump]), which means that `await`ing a [Future] will result 30 | /// in the test hanging. 31 | /// {@endtemplate} 32 | /// 33 | /// If no listener is configured, this method returns right away with null. 34 | /// 35 | /// The `callback` argument, if non-null, will be called just before this 36 | /// method's future completes, either with the result of the listener 37 | /// registered with [setMessageHandler], or with null if no listener has 38 | /// been registered. 39 | /// 40 | /// Messages can also be sent via [ChannelBuffers.push] (see 41 | /// [ServicesBinding.channelBuffers]); the effect is the same, though that API 42 | /// will not wait for a response. 43 | // TODO(ianh): When the superclass `handlePlatformMessage` is removed, 44 | // remove this @override (but leave the method). 45 | @override 46 | Future handlePlatformMessage( 47 | String channel, 48 | ByteData? data, 49 | ui.PlatformMessageResponseCallback? callback, 50 | ) { 51 | Future? result; 52 | if (_inboundHandlers.containsKey(channel)) 53 | result = _inboundHandlers[channel]!(data); 54 | result ??= Future.value(null); 55 | if (callback != null) 56 | result = result.then((ByteData? result) { 57 | callback(result); 58 | return result; 59 | }); 60 | return result; 61 | } 62 | 63 | @override 64 | void setMessageHandler(String channel, MessageHandler? handler) { 65 | if (handler == null) { 66 | _inboundHandlers.remove(channel); 67 | mockBinding._superDefaultBinaryMessenger.setMessageHandler(channel, null); 68 | } else { 69 | _inboundHandlers[channel] = 70 | handler; // used to handle fake messages sent via handlePlatformMessage 71 | mockBinding._superDefaultBinaryMessenger.setMessageHandler( 72 | channel, handler); // used to handle real messages from the engine 73 | } 74 | } 75 | 76 | final List> _pendingMessages = >[]; 77 | 78 | /// The number of incomplete/pending calls sent to the platform channels. 79 | int get pendingMessageCount => _pendingMessages.length; 80 | 81 | // Handlers that intercept and respond to outgoing messages, 82 | // pretending to be the platform. 83 | final Map _outboundHandlers = 84 | {}; 85 | 86 | // The outbound callbacks that were actually registered, so that we 87 | // can implement the [checkMockMessageHandler] method. 88 | final Map _outboundHandlerIdentities = {}; 89 | 90 | @override 91 | Future? send(String channel, ByteData? message) { 92 | final Future? resultFuture; 93 | final MessageHandler? handler = _outboundHandlers[channel]; 94 | if (handler != null) { 95 | resultFuture = handler(message); 96 | } else { 97 | resultFuture = 98 | mockBinding._superDefaultBinaryMessenger.send(channel, message); 99 | } 100 | if (resultFuture != null) { 101 | _pendingMessages.add(resultFuture); 102 | resultFuture.catchError((Object error) { 103 | /* errors are the responsibility of the caller */ 104 | }).whenComplete(() => _pendingMessages.remove(resultFuture)); 105 | } 106 | return resultFuture; 107 | } 108 | 109 | /// Returns a Future that completes after all the platform calls are finished. 110 | /// 111 | /// If a new platform message is sent after this method is called, this new 112 | /// message is not tracked. Use with [pendingMessageCount] to guarantee no 113 | /// pending message calls. 114 | Future get platformMessagesFinished { 115 | return Future.wait(_pendingMessages); 116 | } 117 | 118 | /// Set a callback for intercepting messages sent to the platform on 119 | /// the given channel, without decoding them. 120 | /// 121 | /// Intercepted messages are not forwarded to the platform. 122 | /// 123 | /// The given callback will replace the currently registered 124 | /// callback for that channel, if any. To stop intercepting messages 125 | /// at all, pass null as the handler. 126 | /// 127 | /// The handler's return value, if non-null, is used as a response, 128 | /// unencoded. 129 | /// 130 | /// {@macro flutter.flutter_test.MockBinaryMessenger.handlePlatformMessage.asyncHandlers} 131 | /// 132 | /// The `identity` argument, if non-null, is used to identify the 133 | /// callback when checked by [checkMockMessageHandler]. If null, the 134 | /// `handler` is used instead. (This allows closures to be passed as 135 | /// the `handler` with an alias used as the `identity` so that a 136 | /// reference to the closure need not be used. In practice, this is 137 | /// used by [setMockDecodedMessageHandler] and 138 | /// [setMockMethodCallHandler] to allow [checkMockMessageHandler] to 139 | /// recognize the closures that were passed to those methods even 140 | /// though those methods wrap those closures when passing them to 141 | /// this method.) 142 | /// 143 | /// Registered callbacks are cleared after each test. 144 | /// 145 | /// See also: 146 | /// 147 | /// * [checkMockMessageHandler], which can verify if a handler is still 148 | /// registered, which is useful in tests to ensure that no unexpected 149 | /// handlers are being registered. 150 | /// 151 | /// * [setMockDecodedMessageHandler], which wraps this method but 152 | /// decodes the messages using a [MessageCodec]. 153 | /// 154 | /// * [setMockMethodCallHandler], which wraps this method but decodes 155 | /// the messages using a [MethodCodec]. 156 | void setMockMessageHandler(String channel, MessageHandler? handler, 157 | [Object? identity]) { 158 | if (handler == null) { 159 | _outboundHandlers.remove(channel); 160 | _outboundHandlerIdentities.remove(channel); 161 | } else { 162 | identity ??= handler; 163 | _outboundHandlers[channel] = handler; 164 | _outboundHandlerIdentities[channel] = identity; 165 | } 166 | } 167 | 168 | /// Set a callback for intercepting messages sent to the platform on 169 | /// the given channel. 170 | /// 171 | /// Intercepted messages are not forwarded to the platform. 172 | /// 173 | /// The given callback will replace the currently registered 174 | /// callback for that channel, if any. To stop intercepting messages 175 | /// at all, pass null as the handler. 176 | /// 177 | /// Messages are decoded using the codec of the channel. 178 | /// 179 | /// The handler's return value, if non-null, is used as a response, 180 | /// after encoding it using the channel's codec. 181 | /// 182 | /// {@macro flutter.flutter_test.MockBinaryMessenger.handlePlatformMessage.asyncHandlers} 183 | /// 184 | /// Registered callbacks are cleared after each test. 185 | /// 186 | /// See also: 187 | /// 188 | /// * [checkMockMessageHandler], which can verify if a handler is still 189 | /// registered, which is useful in tests to ensure that no unexpected 190 | /// handlers are being registered. 191 | /// 192 | /// * [setMockMessageHandler], which is similar but provides raw 193 | /// access to the underlying bytes. 194 | /// 195 | /// * [setMockMethodCallHandler], which is similar but decodes 196 | /// the messages using a [MethodCodec]. 197 | void setMockDecodedMessageHandler( 198 | BasicMessageChannel channel, Future Function(T? message)? handler) { 199 | if (handler == null) { 200 | setMockMessageHandler(channel.name, null); 201 | return; 202 | } 203 | setMockMessageHandler(channel.name, (ByteData? message) async { 204 | return channel.codec 205 | .encodeMessage(await handler(channel.codec.decodeMessage(message))); 206 | }, handler); 207 | } 208 | 209 | /// Set a callback for intercepting method calls sent to the 210 | /// platform on the given channel. 211 | /// 212 | /// Intercepted method calls are not forwarded to the platform. 213 | /// 214 | /// The given callback will replace the currently registered 215 | /// callback for that channel, if any. To stop intercepting messages 216 | /// at all, pass null as the handler. 217 | /// 218 | /// Methods are decoded using the codec of the channel. 219 | /// 220 | /// The handler's return value, if non-null, is used as a response, 221 | /// after re-encoding it using the channel's codec. 222 | /// 223 | /// To send an error, throw a [PlatformException] in the handler. 224 | /// Other exceptions are not caught. 225 | /// 226 | /// {@macro flutter.flutter_test.MockBinaryMessenger.handlePlatformMessage.asyncHandlers} 227 | /// 228 | /// Registered callbacks are cleared after each test. 229 | /// 230 | /// See also: 231 | /// 232 | /// * [checkMockMessageHandler], which can verify if a handler is still 233 | /// registered, which is useful in tests to ensure that no unexpected 234 | /// handlers are being registered. 235 | /// 236 | /// * [setMockMessageHandler], which is similar but provides raw 237 | /// access to the underlying bytes. 238 | /// 239 | /// * [setMockDecodedMessageHandler], which is similar but decodes 240 | /// the messages using a [MessageCodec]. 241 | void setMockMethodCallHandler(MethodChannel channel, 242 | Future? Function(MethodCall message)? handler) { 243 | if (handler == null) { 244 | setMockMessageHandler(channel.name, null); 245 | return; 246 | } 247 | setMockMessageHandler(channel.name, (ByteData? message) async { 248 | final MethodCall call = channel.codec.decodeMethodCall(message); 249 | try { 250 | return channel.codec.encodeSuccessEnvelope(await handler(call)); 251 | } on PlatformException catch (error) { 252 | return channel.codec.encodeErrorEnvelope( 253 | code: error.code, 254 | message: error.message, 255 | details: error.details, 256 | ); 257 | } on MissingPluginException { 258 | return null; 259 | } catch (error) { 260 | return channel.codec.encodeErrorEnvelope( 261 | code: 'error', message: '$error', details: null); 262 | } 263 | }, handler); 264 | } 265 | 266 | /// Returns true if the `handler` argument matches the `handler` 267 | /// previously passed to [setMockMessageHandler], 268 | /// [setMockDecodedMessageHandler], or [setMockMethodCallHandler]. 269 | /// 270 | /// Specifically, it compares the argument provided to the `identity` 271 | /// argument provided to [setMockMessageHandler], defaulting to the 272 | /// `handler` argument passed to that method is `identity` was null. 273 | /// 274 | /// This method is useful for tests or test harnesses that want to assert the 275 | /// mock handler for the specified channel has not been altered by a previous 276 | /// test. 277 | /// 278 | /// Passing null for the `handler` returns true if the handler for the 279 | /// `channel` is not set. 280 | /// 281 | /// Registered callbacks are cleared after each test. 282 | bool checkMockMessageHandler(String channel, Object? handler) => 283 | _outboundHandlerIdentities[channel] == handler; 284 | } 285 | -------------------------------------------------------------------------------- /lib/keyboards/mocks/mock_binding.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | class MockBinding extends WidgetsFlutterBinding { 4 | static bool _initFlag = false; 5 | 6 | @override 7 | void initInstances() { 8 | // TODO: implement initInstances 9 | _binaryMessenger = MockBinaryMessenger(this); 10 | super.initInstances(); 11 | _initFlag = true; 12 | } 13 | 14 | static WidgetsBinding ensureInitialized() { 15 | if (!_initFlag) { 16 | MockBinding(); 17 | _initFlag = true; 18 | } 19 | // if (WidgetsBinding.instance == null) MockBinding(); 20 | return WidgetsBinding.instance; 21 | } 22 | 23 | MockBinaryMessenger? _binaryMessenger; 24 | @override 25 | BinaryMessenger get defaultBinaryMessenger { 26 | return _binaryMessenger != null 27 | ? _binaryMessenger! 28 | : super.defaultBinaryMessenger; 29 | } 30 | 31 | BinaryMessenger get _superDefaultBinaryMessenger { 32 | return super.defaultBinaryMessenger; 33 | } 34 | } 35 | 36 | runMockApp(Widget app) { 37 | MockBinding.ensureInitialized() 38 | ..scheduleAttachRootWidget(app) 39 | ..scheduleWarmUpFrame(); 40 | } 41 | -------------------------------------------------------------------------------- /lib/keyboards/number_keyboard.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | class NumberKeyboard extends StatelessWidget{ 4 | static const CKTextInputType inputType = const CKTextInputType(name:'CKNumberKeyboard'); 5 | static double getHeight(BuildContext ctx){ 6 | MediaQueryData mediaQuery = MediaQuery.of(ctx); 7 | return mediaQuery.size.width / 3 / 2 * 4; 8 | } 9 | final KeyboardController controller ; 10 | const NumberKeyboard({required this.controller}); 11 | 12 | static register(){ 13 | CoolKeyboard.addKeyboard(NumberKeyboard.inputType,KeyboardConfig(builder: (context,controller, params){ 14 | return NumberKeyboard(controller: controller); 15 | },getHeight: NumberKeyboard.getHeight)); 16 | } 17 | 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | MediaQueryData mediaQuery = MediaQuery.of(context); 22 | return Material( 23 | child: DefaultTextStyle(style: TextStyle(fontWeight: FontWeight.w500,color: Colors.black,fontSize: 23.0), child: Container( 24 | height:getHeight(context), 25 | width: mediaQuery.size.width, 26 | decoration: BoxDecoration( 27 | color: Color(0xffafafaf), 28 | ), 29 | child: GridView.count( 30 | childAspectRatio: 2/1, 31 | mainAxisSpacing:0.5, 32 | crossAxisSpacing:0.5, 33 | padding: EdgeInsets.all(0.0), 34 | crossAxisCount: 3, 35 | children: [ 36 | buildButton('1'), 37 | buildButton('2'), 38 | buildButton('3'), 39 | buildButton('4'), 40 | buildButton('5'), 41 | buildButton('6'), 42 | buildButton('7'), 43 | buildButton('8'), 44 | buildButton('9'), 45 | Container( 46 | color: Color(0xFFd3d6dd), 47 | child: GestureDetector( 48 | behavior: HitTestBehavior.translucent, 49 | child: Center(child: Icon(Icons.expand_more),), 50 | onTap: (){ 51 | controller.doneAction(); 52 | }, 53 | ), 54 | ), 55 | buildButton('0'), 56 | Container( 57 | color: Color(0xFFd3d6dd), 58 | child: GestureDetector( 59 | behavior: HitTestBehavior.translucent, 60 | child: Center(child: Text('X'),), 61 | onTap: (){ 62 | controller.deleteOne(); 63 | }, 64 | ), 65 | ), 66 | ]), 67 | )), 68 | ); 69 | } 70 | 71 | Widget buildButton(String title,{String? value}){ 72 | return Container( 73 | color: Colors.white, 74 | child: GestureDetector( 75 | behavior: HitTestBehavior.translucent, 76 | child: Center(child: Text(title),), 77 | onTap: (){ 78 | controller.addText(value ?? title); 79 | }, 80 | ), 81 | ); 82 | } 83 | } 84 | 85 | 86 | -------------------------------------------------------------------------------- /lib/utils/screen_util.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | /** 3 | * @Author: thl 4 | * @GitHub: https://github.com/Sky24n 5 | * @JianShu: https://www.jianshu.com/u/cbf2ad25d33a 6 | * @Email: 863764940@qq.com 7 | * @Description: Screen Util. 8 | * @Date: 2018/9/8 9 | */ 10 | 11 | ///默认设计稿尺寸(单位 dp or pt) 12 | double _designW = 360.0; 13 | double _designH = 640.0; 14 | double _designD = 3.0; 15 | 16 | /** 17 | * 配置设计稿尺寸(单位 dp or pt) 18 | * w 宽 19 | * h 高 20 | * density 像素密度 21 | */ 22 | void setDesignWHD(double w, double h, {double density: 3.0}) { 23 | _designW = w; 24 | _designH = h; 25 | _designD = density; 26 | } 27 | 28 | /// Screen Util. 29 | class _ScreenUtil { 30 | double _screenWidth = 0.0; 31 | double _screenHeight = 0.0; 32 | double _screenDensity = 0.0; 33 | double _statusBarHeight = 0.0; 34 | double _bottomBarHeight = 0.0; 35 | double _appBarHeight = 0.0; 36 | double _textScaleFactor = 0.0; 37 | MediaQueryData? _mediaQueryData; 38 | 39 | static final _ScreenUtil _singleton = _ScreenUtil(); 40 | 41 | static _ScreenUtil getInstance() { 42 | _singleton._init(); 43 | return _singleton; 44 | } 45 | 46 | 47 | _init() { 48 | MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window); 49 | if (_mediaQueryData != mediaQuery) { 50 | _mediaQueryData = mediaQuery; 51 | _screenWidth = mediaQuery.size.width; 52 | _screenHeight = mediaQuery.size.height; 53 | _screenDensity = mediaQuery.devicePixelRatio; 54 | _statusBarHeight = mediaQuery.padding.top; 55 | _bottomBarHeight = mediaQuery.padding.bottom; 56 | _textScaleFactor = mediaQuery.textScaleFactor; 57 | _appBarHeight = kToolbarHeight; 58 | } 59 | } 60 | 61 | /// screen width 62 | /// 屏幕 宽 63 | double get screenWidth => _screenWidth; 64 | 65 | /// screen height 66 | /// 屏幕 高 67 | double get screenHeight => _screenHeight; 68 | 69 | /// appBar height 70 | /// appBar 高 71 | double get appBarHeight => _appBarHeight; 72 | 73 | /// screen density 74 | /// 屏幕 像素密度 75 | double get screenDensity => _screenDensity; 76 | 77 | /// status bar Height 78 | /// 状态栏高度 79 | double get statusBarHeight => _statusBarHeight; 80 | 81 | /// bottom bar Height 82 | double get bottomBarHeight => _bottomBarHeight; 83 | 84 | /// media Query Data 85 | MediaQueryData? get mediaQueryData => _mediaQueryData; 86 | 87 | /// screen width 88 | /// 当前屏幕 宽 89 | static double getScreenW(BuildContext context) { 90 | MediaQueryData mediaQuery = MediaQuery.of(context); 91 | return mediaQuery.size.width; 92 | } 93 | 94 | /// screen height 95 | /// 当前屏幕 高 96 | static double getScreenH(BuildContext context) { 97 | MediaQueryData mediaQuery = MediaQuery.of(context); 98 | return mediaQuery.size.width; 99 | } 100 | 101 | /// screen density 102 | /// 当前屏幕 像素密度 103 | static double getScreenDensity(BuildContext context) { 104 | MediaQueryData mediaQuery = MediaQuery.of(context); 105 | return mediaQuery.devicePixelRatio; 106 | } 107 | 108 | /// status bar Height 109 | /// 当前状态栏高度 110 | static double getStatusBarH(BuildContext context) { 111 | MediaQueryData mediaQuery = MediaQuery.of(context); 112 | return mediaQuery.padding.top; 113 | } 114 | 115 | /// status bar Height 116 | /// 当前BottomBar高度 117 | static double getBottomBarH(BuildContext context) { 118 | MediaQueryData mediaQuery = MediaQuery.of(context); 119 | return mediaQuery.padding.bottom; 120 | } 121 | 122 | /// 当前MediaQueryData 123 | static MediaQueryData getMediaQueryData(BuildContext context) { 124 | MediaQueryData mediaQuery = MediaQuery.of(context); 125 | return mediaQuery; 126 | } 127 | 128 | /// 返回根据屏幕宽适配后尺寸(单位 dp or pt) 129 | /// size 单位 dp or pt 130 | static double getScaleW(BuildContext context, double size) { 131 | if (context == null || getScreenW(context) == 0.0) return size; 132 | return size * getScreenW(context) / _designW; 133 | } 134 | 135 | /// 返回根据屏幕高适配后尺寸 (单位 dp or pt) 136 | /// size 单位 dp or pt 137 | static double getScaleH(BuildContext context, double size) { 138 | if (context == null || getScreenH(context) == 0.0) return size; 139 | return size * getScreenH(context) / _designH; 140 | } 141 | 142 | /// 返回根据屏幕宽适配后字体尺寸 143 | /// fontSize 字体尺寸 144 | /// sySystem 是否跟随系统字体大小设置,默认 true。 145 | static double getScaleSp(BuildContext context, double fontSize, 146 | {bool sySystem: true}) { 147 | if (context == null || getScreenW(context) == 0.0) return fontSize; 148 | return (sySystem ? MediaQuery 149 | .of(context) 150 | .textScaleFactor : 1.0) * fontSize * getScreenW(context) / _designW; 151 | } 152 | 153 | /// Orientation 154 | /// 设备方向(portrait, landscape) 155 | static Orientation getOrientation(BuildContext context) { 156 | MediaQueryData mediaQuery = MediaQuery.of(context); 157 | return mediaQuery.orientation; 158 | } 159 | 160 | /// 返回根据屏幕宽适配后尺寸(单位 dp or pt) 161 | /// size 单位 dp or pt 162 | double getWidth(double size) { 163 | return _screenWidth == 0.0 ? size : (size * _screenWidth / _designW); 164 | } 165 | 166 | /// 返回根据屏幕高适配后尺寸 (单位 dp or pt) 167 | /// size 单位 dp or pt 168 | double getHeight(double size) { 169 | return _screenHeight == 0.0 ? size : (size * _screenHeight / _designH); 170 | } 171 | 172 | /// 返回根据屏幕宽适配后尺寸(单位 dp or pt) 173 | /// sizePx 单位px 174 | double getWidthPx(double sizePx) { 175 | return _screenWidth == 0.0 ? (sizePx / _designD) : 176 | (sizePx * _screenWidth / (_designW * _designD)); 177 | } 178 | 179 | /// 返回根据屏幕高适配后尺寸(单位 dp or pt) 180 | /// sizePx 单位px 181 | double getHeightPx(double sizePx) { 182 | return _screenHeight == 0.0 ? (sizePx / _designD) : 183 | (sizePx * _screenHeight / (_designH * _designD)); 184 | } 185 | 186 | /// 返回根据屏幕宽适配后字体尺寸 187 | /// fontSize 字体尺寸 188 | /// sySystem 是否跟随系统字体大小设置,默认 true。 189 | double getSp(double fontSize, {bool sySystem: true}) { 190 | if (_screenWidth == 0.0) return fontSize; 191 | return (sySystem ? _textScaleFactor : 1.0) * 192 | fontSize * 193 | _screenWidth / 194 | _designW; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /lib/utils/scroll_utils.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | connectScroll(ScrollController scroll1, ScrollController scroll2){ 4 | scroll1.addListener(() { 5 | if(scroll2.offset != scroll1.offset){ 6 | scroll2.jumpTo(scroll1.offset); 7 | } 8 | }); 9 | scroll2.addListener(() { 10 | if(scroll1.offset != scroll2.offset){ 11 | scroll1.jumpTo(scroll2.offset); 12 | } 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /lib/utils/widget_util.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | /** 3 | * @Author: thl 4 | * @GitHub: https://github.com/Sky24n 5 | * @JianShu: https://www.jianshu.com/u/cbf2ad25d33a 6 | * @Email: 863764940@qq.com 7 | * @Description: Widget Util. 8 | * @Date: 2018/9/10 9 | */ 10 | 11 | /// Widget Util. 12 | class _WidgetUtil { 13 | bool _hasMeasured = false; 14 | double? _width; 15 | double? _height; 16 | 17 | /// Widget rendering listener. 18 | /// Widget渲染监听. 19 | /// context: Widget context. 20 | /// isOnce: true,Continuous monitoring false,Listen only once. 21 | /// onCallBack: Widget Rect CallBack. 22 | void asyncPrepare( 23 | BuildContext context, bool isOnce, ValueChanged onCallBack) { 24 | if (_hasMeasured) return; 25 | WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { 26 | RenderBox? box = context.findRenderObject() as RenderBox?; 27 | if (box != null && box.semanticBounds != null) { 28 | if (isOnce) _hasMeasured = true; 29 | double width = box.semanticBounds.width; 30 | double height = box.semanticBounds.height; 31 | if (_width != width || _height != height) { 32 | _width = width; 33 | _height = height; 34 | if (onCallBack != null) onCallBack(box.semanticBounds); 35 | } 36 | } 37 | }); 38 | } 39 | 40 | /// Widget渲染监听. 41 | void asyncPrepares(bool isOnce, ValueChanged? onCallBack) { 42 | if (_hasMeasured) return; 43 | WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { 44 | if (isOnce) _hasMeasured = true; 45 | if (onCallBack != null) onCallBack(null); 46 | }); 47 | } 48 | 49 | ///get Widget Bounds (width, height, left, top, right, bottom and so on).Widgets must be rendered completely. 50 | ///获取widget Rect 51 | static Rect getWidgetBounds(BuildContext context) { 52 | RenderBox? box = context.findRenderObject() as RenderBox?; 53 | return (box != null) ? box.semanticBounds : Rect.zero; 54 | } 55 | 56 | ///Get the coordinates of the widget on the screen.Widgets must be rendered completely. 57 | ///获取widget在屏幕上的坐标,widget必须渲染完成 58 | static Offset getWidgetLocalToGlobal(BuildContext context) { 59 | RenderBox? box = context.findRenderObject() as RenderBox?; 60 | return box == null ? Offset.zero : box.localToGlobal(Offset.zero); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/widgets/popover/cupertino_popover.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | enum CupertinoPopoverDirection { top, bottom, left, right } 4 | 5 | typedef BoolCallback = bool Function(); 6 | 7 | class CupertinoPopoverButton extends StatelessWidget { 8 | final Widget child; 9 | final WidgetBuilder? popoverBuild; 10 | final double? popoverWidth; 11 | final double? popoverHeight; 12 | final Color popoverColor; 13 | final List? popoverBoxShadow; 14 | final double radius; 15 | final Duration transitionDuration; 16 | final BoolCallback? onTap; 17 | final BoxConstraints? popoverConstraints; 18 | final Color barrierColor; 19 | final CupertinoPopoverDirection direction; 20 | 21 | CupertinoPopoverButton( 22 | {required this.child, 23 | this.popoverBuild, 24 | this.popoverColor = Colors.white, 25 | this.popoverBoxShadow, 26 | this.popoverWidth, 27 | this.popoverHeight, 28 | BoxConstraints? popoverConstraints, 29 | this.direction = CupertinoPopoverDirection.bottom, 30 | this.onTap, 31 | this.transitionDuration = const Duration(milliseconds: 200), 32 | this.barrierColor = Colors.black54, 33 | this.radius = 8.0}) 34 | : assert(popoverBuild != null), 35 | this.popoverConstraints = 36 | (popoverWidth != null || popoverHeight != null) 37 | ? popoverConstraints?.tighten( 38 | width: popoverWidth, height: popoverHeight) ?? 39 | BoxConstraints.tightFor( 40 | width: popoverWidth, height: popoverHeight) 41 | : popoverConstraints; 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | // TODO: implement build 46 | return GestureDetector( 47 | behavior: HitTestBehavior.translucent, 48 | onTap: () { 49 | if (onTap != null && onTap!()) { 50 | return; 51 | } 52 | var offset = _WidgetUtil.getWidgetLocalToGlobal(context); 53 | var bounds = _WidgetUtil.getWidgetBounds(context); 54 | var body; 55 | showGeneralDialog( 56 | context: context, 57 | pageBuilder: (BuildContext buildContext, Animation animation, 58 | Animation secondaryAnimation) { 59 | return Builder(builder: (BuildContext context) { 60 | return Container(); 61 | }); 62 | }, 63 | barrierDismissible: true, 64 | barrierLabel: 65 | MaterialLocalizations.of(context).modalBarrierDismissLabel, 66 | barrierColor: this.barrierColor, 67 | transitionDuration: transitionDuration, 68 | transitionBuilder: (BuildContext context, Animation animation, 69 | Animation secondaryAnimation, Widget child) { 70 | if (body == null) { 71 | body = popoverBuild!(context); 72 | } 73 | return FadeTransition( 74 | opacity: CurvedAnimation( 75 | parent: animation, 76 | curve: Curves.easeOut, 77 | ), 78 | child: CupertinoPopover( 79 | attachRect: Rect.fromLTWH( 80 | offset.dx, offset.dy, bounds.width, bounds.height), 81 | child: body, 82 | constraints: popoverConstraints, 83 | color: popoverColor, 84 | boxShadow: popoverBoxShadow, 85 | context: context, 86 | radius: radius, 87 | doubleAnimation: animation, 88 | direction: direction, 89 | ), 90 | ); 91 | }, 92 | ); 93 | }, 94 | child: child, 95 | ); 96 | } 97 | } 98 | 99 | // ignore: must_be_immutable 100 | class CupertinoPopover extends StatefulWidget { 101 | final Rect attachRect; 102 | final Widget child; 103 | final Color color; 104 | final List? boxShadow; 105 | final double radius; 106 | final CupertinoPopoverDirection direction; 107 | final Animation doubleAnimation; 108 | BoxConstraints? constraints; 109 | 110 | CupertinoPopover( 111 | {required this.attachRect, 112 | required this.child, 113 | BoxConstraints? constraints, 114 | this.color = Colors.white, 115 | this.boxShadow, 116 | required BuildContext context, 117 | this.direction = CupertinoPopoverDirection.bottom, 118 | required this.doubleAnimation, 119 | this.radius = 8.0}) 120 | : super() { 121 | BoxConstraints temp; 122 | if (constraints != null) { 123 | temp = BoxConstraints(maxHeight: 123.0, maxWidth: 150.0).copyWith( 124 | minWidth: constraints.minWidth.isFinite ? constraints.minWidth : null, 125 | minHeight: 126 | constraints.minHeight.isFinite ? constraints.minHeight : null, 127 | maxWidth: constraints.maxWidth.isFinite ? constraints.maxWidth : null, 128 | maxHeight: 129 | constraints.maxHeight.isFinite ? constraints.maxHeight : null, 130 | ); 131 | } else { 132 | temp = BoxConstraints(maxHeight: 123.0, maxWidth: 150.0); 133 | } 134 | this.constraints = temp.copyWith( 135 | maxHeight: temp.maxHeight + CupertinoPopoverState._arrowHeight); 136 | } 137 | 138 | @override 139 | CupertinoPopoverState createState() => new CupertinoPopoverState(); 140 | 141 | @override 142 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 143 | super.debugFillProperties(properties); 144 | properties.add(DiagnosticsProperty( 145 | 'constraints', constraints, 146 | showName: false)); 147 | properties.add(DiagnosticsProperty('color', color, showName: false)); 148 | properties 149 | .add(DiagnosticsProperty('double', radius, showName: false)); 150 | } 151 | } 152 | 153 | class CupertinoPopoverState extends State 154 | with TickerProviderStateMixin { 155 | static const double _arrowWidth = 12.0; 156 | static const double _arrowHeight = 8.0; 157 | 158 | @override 159 | void initState() { 160 | super.initState(); 161 | } 162 | 163 | @override 164 | Widget build(BuildContext context) { 165 | return Stack( 166 | children: [ 167 | _CupertionPopoverPosition( 168 | attachRect: widget.attachRect, 169 | scale: widget.doubleAnimation, 170 | constraints: widget.constraints, 171 | direction: widget.direction, 172 | child: _CupertionPopoverContext( 173 | attachRect: widget.attachRect, 174 | scale: widget.doubleAnimation, 175 | radius: widget.radius, 176 | color: widget.color, 177 | boxShadow: widget.boxShadow ?? [], 178 | direction: widget.direction, 179 | child: 180 | Material(type: MaterialType.transparency, child: widget.child), 181 | ), 182 | ) 183 | ], 184 | ); 185 | } 186 | } 187 | 188 | class _CupertionPopoverPosition extends SingleChildRenderObjectWidget { 189 | final Rect attachRect; 190 | final Animation scale; 191 | final BoxConstraints? constraints; 192 | final CupertinoPopoverDirection direction; 193 | 194 | _CupertionPopoverPosition( 195 | {required Widget child, 196 | required this.attachRect, 197 | this.constraints, 198 | required this.scale, 199 | required this.direction}) 200 | : super(child: child); 201 | 202 | @override 203 | RenderObject createRenderObject(BuildContext context) => 204 | _CupertionPopoverPositionRenderObject( 205 | attachRect: attachRect, 206 | direction: direction, 207 | constraints: constraints ?? BoxConstraints()); 208 | 209 | @override 210 | void updateRenderObject(BuildContext context, 211 | _CupertionPopoverPositionRenderObject renderObject) { 212 | renderObject 213 | ..attachRect = attachRect 214 | ..direction = direction 215 | ..additionalConstraints = constraints ?? BoxConstraints(); 216 | } 217 | 218 | @override 219 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 220 | super.debugFillProperties(properties); 221 | properties.add(DiagnosticsProperty( 222 | 'constraints', constraints, 223 | showName: false)); 224 | } 225 | } 226 | 227 | class _CupertionPopoverPositionRenderObject extends RenderShiftedBox { 228 | CupertinoPopoverDirection get direction => _direction; 229 | CupertinoPopoverDirection _direction; 230 | set direction(CupertinoPopoverDirection value) { 231 | if (_direction == value) return; 232 | _direction = value; 233 | markNeedsLayout(); 234 | } 235 | 236 | Rect get attachRect => _attachRect; 237 | Rect _attachRect; 238 | set attachRect(Rect value) { 239 | if (_attachRect == value) return; 240 | _attachRect = value; 241 | markNeedsLayout(); 242 | } 243 | 244 | BoxConstraints get additionalConstraints => _additionalConstraints; 245 | BoxConstraints _additionalConstraints; 246 | set additionalConstraints(BoxConstraints value) { 247 | if (_additionalConstraints == value) return; 248 | _additionalConstraints = value; 249 | markNeedsLayout(); 250 | } 251 | 252 | _CupertionPopoverPositionRenderObject( 253 | {RenderBox? child, 254 | required Rect attachRect, 255 | BoxConstraints constraints = const BoxConstraints(), 256 | required CupertinoPopoverDirection direction}) 257 | : 258 | this._attachRect = attachRect, 259 | this._additionalConstraints = constraints, 260 | this._direction = direction, 261 | super(child); 262 | 263 | @override 264 | void performLayout() { 265 | child!.layout(_additionalConstraints.enforce(constraints), 266 | parentUsesSize: true); 267 | size = Size(constraints.maxWidth, constraints.maxHeight); 268 | 269 | final BoxParentData childParentData = child!.parentData as BoxParentData; 270 | 271 | childParentData.offset = calcOffset(child!.size); 272 | } 273 | 274 | Offset calcOffset(Size size) { 275 | CupertinoPopoverDirection calcDirection = 276 | _calcDirection(attachRect, size, direction); 277 | 278 | if (calcDirection == CupertinoPopoverDirection.top || 279 | calcDirection == CupertinoPopoverDirection.bottom) { 280 | double bodyLeft = 0.0; 281 | // 上下 282 | if (attachRect.left > size.width / 2 && 283 | _ScreenUtil.getInstance().screenWidth - attachRect.right > 284 | size.width / 2) { 285 | //判断是否可以在中间 286 | bodyLeft = attachRect.left + attachRect.width / 2 - size.width / 2; 287 | } else if (attachRect.left < size.width / 2) { 288 | //靠左 289 | bodyLeft = 10.0; 290 | } else { 291 | //靠右 292 | bodyLeft = _ScreenUtil.getInstance().screenWidth - 10.0 - size.width; 293 | } 294 | 295 | if (calcDirection == CupertinoPopoverDirection.bottom) { 296 | return Offset(bodyLeft, attachRect.bottom); 297 | } else { 298 | return Offset(bodyLeft, 299 | attachRect.top - size.height); 300 | } 301 | } else { 302 | double bodyTop = 0.0; 303 | if (attachRect.top > size.height / 2 && 304 | _ScreenUtil.getInstance().screenHeight - attachRect.bottom > 305 | size.height / 2) { 306 | //判断是否可以在中间 307 | bodyTop = attachRect.top + attachRect.height / 2 - size.height / 2; 308 | } else if (attachRect.top < size.height / 2) { 309 | //靠左 310 | bodyTop = 10.0; 311 | } else { 312 | //靠右 313 | bodyTop = _ScreenUtil.getInstance().screenHeight - 10.0 - size.height; 314 | } 315 | 316 | if (calcDirection == CupertinoPopoverDirection.right) { 317 | return Offset(attachRect.right, bodyTop); 318 | } else { 319 | return Offset( 320 | attachRect.left - size.width, 321 | bodyTop); 322 | } 323 | } 324 | } 325 | 326 | @override 327 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 328 | super.debugFillProperties(properties); 329 | properties.add(DiagnosticsProperty( 330 | 'additionalConstraints', additionalConstraints)); 331 | } 332 | } 333 | 334 | class _CupertionPopoverContext extends SingleChildRenderObjectWidget { 335 | final Rect attachRect; 336 | final Color color; 337 | final List boxShadow; 338 | final Animation scale; 339 | final double radius; 340 | final CupertinoPopoverDirection direction; 341 | _CupertionPopoverContext( 342 | {required Widget child, 343 | required this.attachRect, 344 | required this.color, 345 | this.boxShadow = const [], 346 | required this.scale, 347 | required this.radius, 348 | required this.direction}) 349 | : super(child: child); 350 | 351 | @override 352 | RenderObject createRenderObject(BuildContext context) => 353 | _CupertionPopoverContextRenderObject( 354 | attachRect: attachRect, 355 | color: color, 356 | boxShadow: boxShadow, 357 | scale: scale.value, 358 | direction: direction, 359 | radius: radius); 360 | 361 | @override 362 | void updateRenderObject( 363 | BuildContext context, _CupertionPopoverContextRenderObject renderObject) { 364 | renderObject 365 | ..attachRect = attachRect 366 | ..color = color 367 | ..boxShadow = boxShadow 368 | ..scale = scale.value 369 | ..direction = direction 370 | ..radius = radius; 371 | } 372 | } 373 | 374 | class _CupertionPopoverContextRenderObject extends RenderShiftedBox { 375 | CupertinoPopoverDirection get direction => _direction; 376 | CupertinoPopoverDirection _direction; 377 | set direction(CupertinoPopoverDirection value) { 378 | if (_direction == value) return; 379 | _direction = value; 380 | markNeedsLayout(); 381 | } 382 | 383 | Rect get attachRect => _attachRect; 384 | Rect _attachRect; 385 | set attachRect(Rect value) { 386 | if (_attachRect == value) return; 387 | _attachRect = value; 388 | markNeedsLayout(); 389 | } 390 | 391 | Color get color => _color; 392 | Color _color; 393 | set color(Color value) { 394 | if (_color == value) return; 395 | _color = value; 396 | markNeedsLayout(); 397 | } 398 | 399 | List get boxShadow => _boxShadow; 400 | List _boxShadow; 401 | set boxShadow(List value) { 402 | if (_boxShadow == value) return; 403 | _boxShadow = value; 404 | markNeedsLayout(); 405 | } 406 | 407 | double get scale => _scale; 408 | double _scale; 409 | set scale(double value) { 410 | // print('scale:${_scale.value}'); 411 | // if (_scale == value) 412 | // return; 413 | _scale = value; 414 | markNeedsLayout(); 415 | } 416 | 417 | double get radius => _radius; 418 | double _radius; 419 | set radius(double value) { 420 | if (_radius == value) return; 421 | _radius = value; 422 | markNeedsLayout(); 423 | } 424 | 425 | _CupertionPopoverContextRenderObject( 426 | {RenderBox? child, 427 | required Rect attachRect, 428 | required Color color, 429 | List boxShadow = const [], 430 | required double scale, 431 | required double radius, 432 | required CupertinoPopoverDirection direction}) 433 | : 434 | this._attachRect = attachRect, 435 | this._color = color, 436 | this._boxShadow = boxShadow, 437 | this._scale = scale, 438 | this._radius = radius, 439 | this._direction = direction,super(child); 440 | 441 | @override 442 | void performLayout() { 443 | assert(constraints.maxHeight.isFinite); 444 | BoxConstraints childConstraints; 445 | 446 | if (direction == CupertinoPopoverDirection.top || 447 | direction == CupertinoPopoverDirection.bottom) { 448 | childConstraints = BoxConstraints( 449 | maxHeight: 450 | constraints.maxHeight - CupertinoPopoverState._arrowHeight) 451 | .enforce(constraints); 452 | } else { 453 | childConstraints = BoxConstraints( 454 | maxWidth: 455 | constraints.maxWidth - CupertinoPopoverState._arrowHeight) 456 | .enforce(constraints); 457 | } 458 | 459 | child!.layout(childConstraints, parentUsesSize: true); 460 | 461 | if (direction == CupertinoPopoverDirection.top || 462 | direction == CupertinoPopoverDirection.bottom) { 463 | size = Size(child!.size.width, 464 | child!.size.height + CupertinoPopoverState._arrowHeight); 465 | } else { 466 | size = Size(child!.size.width + CupertinoPopoverState._arrowHeight, 467 | child!.size.height); 468 | } 469 | CupertinoPopoverDirection calcDirection = 470 | _calcDirection(attachRect, size, direction); 471 | 472 | final BoxParentData childParentData = child!.parentData as BoxParentData; 473 | if (calcDirection == CupertinoPopoverDirection.bottom) { 474 | childParentData.offset = Offset(0.0, CupertinoPopoverState._arrowHeight); 475 | } else if (calcDirection == CupertinoPopoverDirection.right) { 476 | childParentData.offset = Offset(CupertinoPopoverState._arrowHeight, 0.0); 477 | } 478 | } 479 | 480 | @override 481 | void paint(PaintingContext context, Offset offset) { 482 | // TODO: implement paint 483 | Matrix4 transform = Matrix4.identity(); 484 | // 485 | 486 | CupertinoPopoverDirection calcDirection = 487 | _calcDirection(attachRect, size, direction); 488 | 489 | Rect? arrowRect; 490 | Offset? translation; 491 | Rect bodyRect; 492 | 493 | final BoxParentData childParentData = (child!.parentData) as BoxParentData; 494 | bodyRect = childParentData.offset & child!.size; 495 | 496 | var arrowLeft = attachRect.left + // 用于 Top和Bottom 497 | attachRect.width / 2 - 498 | CupertinoPopoverState._arrowWidth / 2 - 499 | offset.dx; 500 | 501 | var arrowTop = attachRect.top + // 用于 Left和Right 502 | attachRect.height / 2 - 503 | CupertinoPopoverState._arrowWidth / 2 - 504 | offset.dy; 505 | 506 | switch (calcDirection) { 507 | case CupertinoPopoverDirection.top: 508 | arrowRect = Rect.fromLTWH( 509 | arrowLeft, 510 | child!.size.height, 511 | CupertinoPopoverState._arrowWidth, 512 | CupertinoPopoverState._arrowHeight); 513 | translation = Offset( 514 | arrowLeft + CupertinoPopoverState._arrowWidth / 2, size.height); 515 | 516 | break; 517 | case CupertinoPopoverDirection.left: 518 | arrowRect = Rect.fromLTWH( 519 | child!.size.width, 520 | arrowTop, 521 | CupertinoPopoverState._arrowHeight, 522 | CupertinoPopoverState._arrowWidth); 523 | translation = Offset( 524 | size.width, arrowTop + CupertinoPopoverState._arrowWidth / 2); 525 | break; 526 | case CupertinoPopoverDirection.bottom: 527 | arrowRect = Rect.fromLTWH( 528 | arrowLeft, 529 | 0, 530 | CupertinoPopoverState._arrowWidth, 531 | CupertinoPopoverState._arrowHeight); 532 | translation = 533 | Offset(arrowLeft + CupertinoPopoverState._arrowWidth / 2, 0); 534 | break; 535 | case CupertinoPopoverDirection.right: 536 | arrowRect = Rect.fromLTWH( 537 | 0, 538 | arrowTop, 539 | CupertinoPopoverState._arrowHeight, 540 | CupertinoPopoverState._arrowWidth); 541 | translation = Offset( 542 | 0, arrowTop + CupertinoPopoverState._arrowWidth / 2); 543 | break; 544 | default: 545 | } 546 | 547 | transform.translate(translation!.dx, translation.dy); 548 | transform.scale(scale, scale, 1.0); 549 | transform.translate(-translation.dx, -translation.dy); 550 | 551 | _paintShadows( 552 | context, transform, offset, calcDirection, arrowRect!, bodyRect); 553 | 554 | Path clipPath = _getClip(calcDirection, arrowRect, bodyRect); 555 | context.pushClipPath(needsCompositing, offset, offset & size, clipPath, 556 | (context, offset) { 557 | context.pushTransform(needsCompositing, offset, transform, 558 | (context, offset) { 559 | final Paint backgroundPaint = Paint(); 560 | backgroundPaint.color = color; 561 | context.canvas.drawRect(offset & size, backgroundPaint); 562 | super.paint(context, offset); 563 | }); 564 | }); 565 | } 566 | 567 | void _paintShadows(PaintingContext context, Matrix4 transform, Offset offset, 568 | CupertinoPopoverDirection direction, Rect arrowRect, Rect bodyRect) { 569 | for (final BoxShadow boxShadow in boxShadow) { 570 | final Paint paint = boxShadow.toPaint(); 571 | arrowRect = arrowRect 572 | .shift(offset) 573 | .shift(boxShadow.offset) 574 | .inflate(boxShadow.spreadRadius); 575 | bodyRect = bodyRect 576 | .shift(offset) 577 | .shift(boxShadow.offset) 578 | .inflate(boxShadow.spreadRadius); 579 | Path path = _getClip(direction, arrowRect, bodyRect); 580 | 581 | context.pushTransform(needsCompositing, offset, transform, 582 | (context, offset) { 583 | context.canvas.drawPath(path, paint); 584 | }); 585 | } 586 | } 587 | 588 | Path _getClip( 589 | CupertinoPopoverDirection direction, Rect arrowRect, Rect bodyRect) { 590 | Path path = new Path(); 591 | 592 | if (direction == CupertinoPopoverDirection.top) { 593 | path.moveTo(arrowRect.left, arrowRect.top); //箭头 594 | path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.bottom); 595 | path.lineTo(arrowRect.right, arrowRect.top); 596 | 597 | path.lineTo(bodyRect.right - radius, bodyRect.bottom); //右下角 598 | path.conicTo(bodyRect.right, bodyRect.bottom, bodyRect.right, 599 | bodyRect.bottom - radius, 1.0); 600 | 601 | path.lineTo(bodyRect.right, bodyRect.top + radius); //右上角 602 | path.conicTo(bodyRect.right, bodyRect.top, bodyRect.right - radius, 603 | bodyRect.top, 1.0); 604 | 605 | path.lineTo(bodyRect.left + radius, bodyRect.top); //左上角 606 | path.conicTo(bodyRect.left, bodyRect.top, bodyRect.left, 607 | bodyRect.top + radius, 1.0); 608 | 609 | path.lineTo(bodyRect.left, bodyRect.bottom - radius); //左下角 610 | path.conicTo(bodyRect.left, bodyRect.bottom, bodyRect.left + radius, 611 | bodyRect.bottom, 1.0); 612 | } else if (direction == CupertinoPopoverDirection.right) { 613 | path.moveTo(arrowRect.right, arrowRect.top); //箭头 614 | path.lineTo(arrowRect.left, arrowRect.top + arrowRect.height / 2); 615 | path.lineTo(arrowRect.right, arrowRect.bottom); 616 | 617 | path.lineTo(bodyRect.left, bodyRect.bottom - radius); //左下角 618 | path.conicTo(bodyRect.left, bodyRect.bottom, bodyRect.left + radius, 619 | bodyRect.bottom, 1.0); 620 | 621 | path.lineTo(bodyRect.right - radius, bodyRect.bottom); //右下角 622 | path.conicTo(bodyRect.right, bodyRect.bottom, bodyRect.right, 623 | bodyRect.bottom - radius, 1.0); 624 | 625 | path.lineTo(bodyRect.right, bodyRect.top + radius); //右上角 626 | path.conicTo(bodyRect.right, bodyRect.top, bodyRect.right - radius, 627 | bodyRect.top, 1.0); 628 | 629 | path.lineTo(bodyRect.left + radius, bodyRect.top); //左上角 630 | path.conicTo(bodyRect.left, bodyRect.top, bodyRect.left, 631 | bodyRect.top + radius, 1.0); 632 | } else if (direction == CupertinoPopoverDirection.left) { 633 | path.moveTo(arrowRect.left, arrowRect.top); //箭头 634 | path.lineTo(arrowRect.right, arrowRect.top + arrowRect.height / 2); 635 | path.lineTo(arrowRect.left, arrowRect.bottom); 636 | 637 | path.lineTo(bodyRect.right, bodyRect.bottom - radius); //右下角 638 | path.conicTo(bodyRect.right, bodyRect.bottom, bodyRect.right - radius, 639 | bodyRect.bottom, 1.0); 640 | 641 | path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角 642 | path.conicTo(bodyRect.left, bodyRect.bottom, bodyRect.left, 643 | bodyRect.bottom - radius, 1.0); 644 | 645 | path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角 646 | path.conicTo(bodyRect.left, bodyRect.top, bodyRect.left + radius, 647 | bodyRect.top, 1.0); 648 | 649 | path.lineTo(bodyRect.right - radius, bodyRect.top); //右上角 650 | path.conicTo(bodyRect.right, bodyRect.top, bodyRect.right, 651 | bodyRect.top + radius, 1.0); 652 | } else { 653 | path.moveTo(arrowRect.left, arrowRect.bottom); //箭头 654 | path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.top); 655 | path.lineTo(arrowRect.right, arrowRect.bottom); 656 | 657 | path.lineTo(bodyRect.right - radius, bodyRect.top); //右上角 658 | path.conicTo(bodyRect.right, bodyRect.top, bodyRect.right, 659 | bodyRect.top + radius, 1.0); 660 | 661 | path.lineTo(bodyRect.right, bodyRect.bottom - radius); //右下角 662 | path.conicTo(bodyRect.right, bodyRect.bottom, bodyRect.right - radius, 663 | bodyRect.bottom, 1.0); 664 | 665 | path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角 666 | path.conicTo(bodyRect.left, bodyRect.bottom, bodyRect.left, 667 | bodyRect.bottom - radius, 1.0); 668 | 669 | path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角 670 | path.conicTo(bodyRect.left, bodyRect.top, bodyRect.left + radius, 671 | bodyRect.top, 1.0); 672 | } 673 | path.close(); 674 | return path; 675 | } 676 | } 677 | 678 | CupertinoPopoverDirection _calcDirection( 679 | Rect attachRect, Size size, CupertinoPopoverDirection direction) { 680 | switch (direction) { 681 | case CupertinoPopoverDirection.top: 682 | return (attachRect.top < size.height + CupertinoPopoverState._arrowHeight) 683 | ? CupertinoPopoverDirection.bottom 684 | : CupertinoPopoverDirection.top; // 判断顶部位置够不够 685 | case CupertinoPopoverDirection.bottom: 686 | return _ScreenUtil.getInstance().screenHeight > 687 | attachRect.bottom + 688 | size.height + 689 | CupertinoPopoverState._arrowHeight 690 | ? CupertinoPopoverDirection.bottom 691 | : CupertinoPopoverDirection.top; 692 | case CupertinoPopoverDirection.left: 693 | return (attachRect.left < size.width + CupertinoPopoverState._arrowHeight) 694 | ? CupertinoPopoverDirection.right 695 | : CupertinoPopoverDirection.left; // 判断顶部位置够不够 696 | case CupertinoPopoverDirection.right: 697 | return _ScreenUtil.getInstance().screenWidth > 698 | attachRect.right + 699 | size.width + 700 | CupertinoPopoverState._arrowHeight 701 | ? CupertinoPopoverDirection.right 702 | : CupertinoPopoverDirection.left; 703 | } 704 | } 705 | -------------------------------------------------------------------------------- /lib/widgets/popover/cupertino_popover_menu_item.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | class CupertinoPopoverMenuList extends StatelessWidget { 4 | final List children; 5 | const CupertinoPopoverMenuList({this.children = const []}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return ListView.builder( 10 | itemCount: children.length * 2 - 1, 11 | shrinkWrap: true, 12 | itemBuilder: (context, int i) { 13 | if (i.isOdd) { 14 | // 在每一列之前,添加一个1像素高的分隔线widget 15 | return const Divider( 16 | height: 1.0, 17 | ); 18 | } 19 | final int index = i ~/ 2; 20 | return children[index]; 21 | }, 22 | padding: EdgeInsets.all(0.0), 23 | ); 24 | } 25 | } 26 | 27 | class CupertinoPopoverMenuItem extends StatefulWidget { 28 | final Widget? leading; 29 | final Widget child; 30 | final BoolCallback? onTap; 31 | final bool isTapClosePopover; 32 | final Color activeBackground; 33 | final Color background; 34 | 35 | const CupertinoPopoverMenuItem( 36 | {this.leading, 37 | required this.child, 38 | this.onTap, 39 | this.background = Colors.white, 40 | this.activeBackground = const Color(0xFFd9d9d9), 41 | this.isTapClosePopover = true}); 42 | 43 | @override 44 | State createState() => CupertinoPopoverMenuItemState(); 45 | } 46 | 47 | class CupertinoPopoverMenuItemState extends State { 48 | bool isDown = false; 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | List widgets = []; 53 | if (widget.leading != null) { 54 | widgets.add(Container( 55 | padding: EdgeInsets.only(left: 5.0, right: 5.0), 56 | width: 35.0, 57 | height: 35.0, 58 | child: IconTheme( 59 | data: IconThemeData(color: Color(0xff007aff), size: 20.0), 60 | child: widget.leading!), 61 | )); 62 | } 63 | widgets.add(Expanded( 64 | child: DefaultTextStyle( 65 | style: TextStyle(color: Color(0xff007aff), fontSize: 17.0), 66 | child: widget.child))); 67 | return GestureDetector( 68 | onTapDown: (detail) { 69 | setState(() { 70 | isDown = true; 71 | }); 72 | }, 73 | onTapUp: (detail) { 74 | if (isDown) { 75 | setState(() { 76 | isDown = false; 77 | }); 78 | if (widget.onTap != null && widget.onTap!()) { 79 | return; 80 | } 81 | if (widget.isTapClosePopover) { 82 | Navigator.of(context).pop(); 83 | } 84 | } 85 | }, 86 | onTapCancel: () { 87 | if (isDown) { 88 | setState(() { 89 | isDown = false; 90 | }); 91 | } 92 | }, 93 | child: Container( 94 | color: isDown ? widget.activeBackground : widget.background, 95 | child: Padding( 96 | padding: EdgeInsets.only(top: 2.5, bottom: 2.5), 97 | child: Row(children: widgets), 98 | ), 99 | ), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/widgets/tables/table.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | class CoolTable extends StatefulWidget{ 4 | @override 5 | State createState() { 6 | // TODO: implement createState 7 | return CoolTableState(); 8 | } 9 | } 10 | 11 | class CoolTableState extends State{ 12 | ScrollController hTitle = ScrollController(); 13 | ScrollController hBody = ScrollController(); 14 | 15 | @override 16 | void initState() { 17 | // TODO: implement initState 18 | super.initState(); 19 | connectScroll(hTitle, hBody); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | // TODO: implement build 25 | return Column( 26 | mainAxisSize: MainAxisSize.min, 27 | children: [ 28 | SizedBox( 29 | height: 50, 30 | child: ListView( 31 | scrollDirection: Axis.horizontal, 32 | shrinkWrap: true, 33 | physics: BouncingScrollPhysics(), 34 | controller: hTitle, 35 | children: [ 36 | Text('1111111111111111111111111111111111111111111111111111111111111111111111111111') 37 | ], 38 | ), 39 | width: 375,), 40 | SizedBox( 41 | height: 50, 42 | child: ListView( 43 | physics: BouncingScrollPhysics(), 44 | scrollDirection: Axis.horizontal, 45 | controller: hBody, 46 | shrinkWrap: true, 47 | children: [ 48 | Text('1111111111111111111111111111111111111111111111111111111111111111111111111111') 49 | ], 50 | )), 51 | ], 52 | ); 53 | } 54 | } 55 | 56 | class CoolColumnInfo{ 57 | final double? flex; 58 | final double? width; 59 | final Widget? title; 60 | 61 | const CoolColumnInfo({this.flex, this.width, this.title}); 62 | } -------------------------------------------------------------------------------- /lib/widgets/utils/paint_event.dart: -------------------------------------------------------------------------------- 1 | part of cool_ui; 2 | 3 | typedef PaintCallback = void Function(PaintingContext context, Offset offset,Size size); 4 | 5 | class PaintEvent extends SingleChildRenderObjectWidget{ 6 | 7 | 8 | final PaintCallback? paintBefore; 9 | final PaintCallback? paintAfter; 10 | 11 | 12 | const PaintEvent({ 13 | Key? key, 14 | this.paintBefore, 15 | this.paintAfter, 16 | Widget? child 17 | }) : 18 | super(key: key, child: child); 19 | @override 20 | PaintEventProxyBox createRenderObject(BuildContext context) { 21 | return PaintEventProxyBox( 22 | paintAfter: paintAfter, 23 | paintBefore: paintBefore 24 | ); 25 | } 26 | 27 | @override 28 | void updateRenderObject(BuildContext context, PaintEventProxyBox renderObject) { 29 | renderObject..paintAfter = paintBefore 30 | ..paintAfter = paintAfter; 31 | } 32 | 33 | } 34 | 35 | 36 | class PaintEventProxyBox extends RenderProxyBox{ 37 | PaintCallback? paintBefore; 38 | PaintCallback? paintAfter; 39 | 40 | PaintEventProxyBox({ 41 | RenderBox? child, 42 | this.paintBefore, 43 | this.paintAfter 44 | }):super(child); 45 | 46 | @override 47 | void detach() { 48 | super.detach(); 49 | markNeedsPaint(); 50 | } 51 | 52 | @override 53 | void paint(PaintingContext context, Offset offset) { 54 | if(this.paintBefore != null){ 55 | this.paintBefore!(context,offset,size); 56 | } 57 | 58 | super.paint(context, offset); 59 | 60 | 61 | 62 | if(this.paintAfter != null){ 63 | this.paintAfter!(context,offset,size); 64 | } 65 | 66 | } 67 | } -------------------------------------------------------------------------------- /pub.bat: -------------------------------------------------------------------------------- 1 | set PUB_HOSTED_URL= 2 | set FLUTTER_STORAGE_BASE_URL= 3 | set http_proxy=http://127.0.0.1:7890 & set https_proxy=http://127.0.0.1:7890 4 | flutter packages pub publish -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.9.0" 11 | back_button_interceptor: 12 | dependency: "direct main" 13 | description: 14 | name: back_button_interceptor 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "6.0.0" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "2.1.0" 25 | characters: 26 | dependency: transitive 27 | description: 28 | name: characters 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.2.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.1" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.16.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.3.1" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "0.12.12" 70 | material_color_utilities: 71 | dependency: transitive 72 | description: 73 | name: material_color_utilities 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "0.1.5" 77 | meta: 78 | dependency: transitive 79 | description: 80 | name: meta 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "1.8.0" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.8.2" 91 | sky_engine: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.99" 96 | source_span: 97 | dependency: transitive 98 | description: 99 | name: source_span 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "1.9.0" 103 | stack_trace: 104 | dependency: transitive 105 | description: 106 | name: stack_trace 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "1.10.0" 110 | stream_channel: 111 | dependency: transitive 112 | description: 113 | name: stream_channel 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "2.1.0" 117 | string_scanner: 118 | dependency: transitive 119 | description: 120 | name: string_scanner 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.1.1" 124 | term_glyph: 125 | dependency: transitive 126 | description: 127 | name: term_glyph 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.2.1" 131 | test_api: 132 | dependency: transitive 133 | description: 134 | name: test_api 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "0.4.12" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.1.2" 145 | sdks: 146 | dart: ">=2.17.0-0 <3.0.0" 147 | flutter: ">=2.5.0" 148 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cool_ui 2 | description: Some practical Widget for flutter,Popover,Weui,Custom Keyboard 3 | version: 1.3.0 4 | author: Kevin 5 | homepage: https://github.com/Im-Kevin/cool_ui 6 | 7 | environment: 8 | sdk: ">=2.12.0 <3.0.0" 9 | flutter: ">=2.5.0 <4.0.0" 10 | 11 | dependencies: 12 | back_button_interceptor: 6.0.0 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 | - assets/images/loading.png 29 | # - images/a_dot_ham.jpeg 30 | # 31 | # For details regarding assets in packages, see 32 | # https://flutter.io/assets-and-images/#from-packages 33 | # 34 | # An image asset can refer to one or more resolution-specific "variants", see 35 | # https://flutter.io/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: CoolUIIcons 44 | fonts: 45 | - asset: assets/fonts/CoolUI.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.io/custom-fonts/#from-packages 56 | -------------------------------------------------------------------------------- /test/cool_ui_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'package:cool_ui/cool_ui.dart'; 4 | 5 | void main() { 6 | 7 | } 8 | --------------------------------------------------------------------------------