├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── WIDGETS.md ├── analysis_options.yaml ├── assets └── images │ ├── ic_empty.png │ ├── ic_error.png │ └── ic_image_error.png ├── example ├── .gitignore ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── azhon │ │ │ │ │ └── todo_flutter │ │ │ │ │ └── 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 │ ├── iconfont │ │ ├── iconfont.json │ │ └── iconfont.ttf │ └── images │ │ ├── ic_clear.png │ │ ├── ic_eye.png │ │ ├── ic_police.png │ │ └── ic_search.png ├── img │ ├── img1.png │ ├── img2.png │ ├── img3.png │ ├── img4.png │ └── img5.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── common │ │ ├── api │ │ │ ├── api_convert.dart │ │ │ └── api_provider.dart │ │ └── route │ │ │ └── module_route.dart │ ├── domain │ │ ├── bloc │ │ │ ├── bloc │ │ │ │ ├── test_bloc.dart │ │ │ │ ├── test_event.dart │ │ │ │ └── test_state.dart │ │ │ └── net │ │ │ │ ├── net_bloc.dart │ │ │ │ ├── net_event.dart │ │ │ │ └── net_state.dart │ │ └── request │ │ │ ├── entity │ │ │ ├── get_entity.dart │ │ │ └── list_data_entity.dart │ │ │ ├── list_request.dart │ │ │ └── net_request.dart │ ├── generated │ │ ├── assets │ │ │ ├── example_assets.dart │ │ │ └── example_icon.dart │ │ ├── json │ │ │ ├── base │ │ │ │ ├── json_convert_content.dart │ │ │ │ └── json_field.dart │ │ │ ├── get_entity.g.dart │ │ │ └── list_data_entity.g.dart │ │ └── route │ │ │ └── example_route.dart │ ├── main.dart │ └── view │ │ ├── app_update_page.dart │ │ ├── bloc_page.dart │ │ ├── button_page.dart │ │ ├── dialog_page.dart │ │ ├── image_page.dart │ │ ├── input_page.dart │ │ ├── net_page.dart │ │ ├── refresh_page.dart │ │ ├── sliver_page.dart │ │ ├── text_page.dart │ │ └── toast_page.dart ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json ├── lib ├── generated │ └── assets │ │ └── todo_flutter_assets.dart ├── src │ ├── I10n │ │ ├── text_delegate.dart │ │ └── text_delegate_en.dart │ ├── base │ │ ├── base_state.dart │ │ ├── base_stateful_widget.dart │ │ ├── base_stateless_widget.dart │ │ ├── bloc │ │ │ ├── base_bloc.dart │ │ │ └── base_event.dart │ │ ├── loading_state.dart │ │ ├── ui_adapter.dart │ │ └── ui_widget.dart │ ├── bloc │ │ ├── data │ │ │ ├── data_change_bloc.dart │ │ │ └── data_change_state.dart │ │ ├── list │ │ │ ├── list_bloc.dart │ │ │ ├── list_event.dart │ │ │ └── list_state.dart │ │ └── load │ │ │ ├── load_bloc.dart │ │ │ ├── load_event.dart │ │ │ └── load_state.dart │ ├── net │ │ ├── base_convert.dart │ │ ├── base_net_engine.dart │ │ ├── base_net_provider.dart │ │ ├── base_request.dart │ │ ├── dio_engine.dart │ │ └── entity │ │ │ └── base_entity.dart │ ├── service │ │ ├── env │ │ │ └── env.dart │ │ ├── error │ │ │ └── domain_exception.dart │ │ ├── event │ │ │ └── global_event_manager.dart │ │ ├── extension │ │ │ └── context_extension.dart │ │ ├── route │ │ │ ├── bundle.dart │ │ │ ├── router_history_stack.dart │ │ │ ├── router_util.dart │ │ │ └── routes.dart │ │ └── theme │ │ │ └── toast_theme_data.dart │ ├── ui │ │ ├── common_button.dart │ │ ├── common_click_widget.dart │ │ ├── common_empty_widget.dart │ │ ├── common_error_widget.dart │ │ ├── common_image.dart │ │ ├── common_input.dart │ │ ├── common_input_area.dart │ │ ├── common_refresh_widget.dart │ │ ├── common_rich_text.dart │ │ ├── common_text.dart │ │ ├── dialog │ │ │ ├── base_dialog.dart │ │ │ ├── common_dialog.dart │ │ │ └── loading_dialog.dart │ │ └── widget │ │ │ ├── amount_text_field_formatter.dart │ │ │ ├── bloc_load_widget.dart │ │ │ ├── circular_progress_widget.dart │ │ │ ├── data_change_widget.dart │ │ │ ├── extended_refresh_indicator.dart │ │ │ ├── loading_dialog_widget.dart │ │ │ ├── remove_ripple_widget.dart │ │ │ ├── saturation_widget.dart │ │ │ └── un_focus_widget.dart │ └── util │ │ ├── calculate_util.dart │ │ ├── log_util.dart │ │ ├── object_util.dart │ │ ├── preference_util.dart │ │ ├── time_util.dart │ │ └── tip_util.dart ├── todo_app.dart ├── todo_flutter.dart └── todo_lib.dart ├── pubspec.lock ├── pubspec.yaml └── test └── todo_flutter_lib_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/ephemeral 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.7 2 | * feat CommonEmptyWidget、NetworkCapture 3 | 4 | ## 0.0.6 5 | * fix TodoApp 6 | 7 | ## 0.0.5 8 | * opt TodoApp 9 | 10 | ## 0.0.4 11 | * fix listBloc init event 12 | * opt context extension 13 | 14 | ## 0.0.3 15 | * opt listBloc result 16 | 17 | ## 0.0.2 18 | * opt listBloc result 19 | 20 | ## 0.0.1 21 | * publish first version -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## [使用例子/Sample](https://github.com/azhon/todo_flutter/tree/main/example) 2 | 3 | ## 基于Bloc状态管理、屏幕适配、路由管理、模块化、封装常用组件的Flutter项目脚手架 4 | - 使用`Flutter 3.13.0`或更高版本 5 | 6 | ### 组件库介绍 7 | 8 | [在使用前请务必先阅读一遍,以避免重复封装](https://github.com/azhon/todo_flutter/blob/main/WIDGETS.md) 9 | 10 | ### 一、依赖本库: 11 | 12 | ```dart 13 | todo_flutter: ^latest_version 14 | ``` 15 | - 初始化 16 | 17 | ```dart 18 | void main() { 19 | runApp(TodoApp(home: MyHomePage())); 20 | } 21 | ``` 22 | ### 二、路由模块: 23 | - 使用[fluro](https://pub.dev/packages/fluro) 24 | - 路由路径生成使用[FlutterResource插件](https://github.com/Xie-Yin/FlutterResource) 25 | 26 | ### 三、屏幕适配: 27 | - 使用[flutter_screenutil](https://pub.dev/packages/flutter_screenutil) 28 | 29 | ### 四、图片资源生成 30 | - 使用[FlutterResource插件](https://github.com/azhon/FlutterResource) 31 | 32 | ### 五、模块化开发 33 | - 使用[ModuleBridge插件](https://github.com/azhon/module_bridge) 34 | 35 | ### 六、网络模块 36 | - 实现http客户端 `BaseNetEngine` 37 | - 实现数据转换器,生成对应实体 `BaseConvert` 38 | - 实现网络请求 `BaseRequest`并指定提供一个`BaseNetProvider` 39 | - json实体插件[FlutterJsonBeanFactory](https://plugins.jetbrains.com/plugin/11415-flutterjsonbeanfactory-only-null-safety-) -------------------------------------------------------------------------------- /WIDGETS.md: -------------------------------------------------------------------------------- 1 | ### 每一个组件的用途如下: 2 | 3 | ```text 4 | . 5 | ├── generated 6 | │ └── assets 7 | │ └── todo_flutter_assets.dart (图片资源索引) 8 | ├── src 9 | │ ├── base 10 | │ │ ├── base_state.dart (有状态组件基类) 11 | │ │ ├── base_stateful_widget.dart (有状态组件基类) 12 | │ │ ├── base_stateless_widget.dart (无状态组件基类) 13 | │ │ ├── bloc 14 | │ │ │ ├── base_bloc.dart (Bloc基类) 15 | │ │ │ └── base_event.dart (Bloc基类) 16 | │ │ ├── loading_state.dart 17 | │ │ ├── ui_adapter.dart (屏幕适配) 18 | │ │ └── ui_widget.dart (屏幕适配代理常用组件) 19 | │ ├── bloc 20 | │ │ ├── data 21 | │ │ │ ├── data_change_bloc.dart (简单Bloc) 22 | │ │ │ └── data_change_state.dart (简单Bloc) 23 | │ │ ├── list 24 | │ │ │ ├── list_bloc.dart (列表Bloc) 25 | │ │ │ ├── list_event.dart (列表Bloc) 26 | │ │ │ └── list_state.dart (列表Bloc) 27 | │ │ └── load 28 | │ │ ├── load_bloc.dart (页面加载Bloc) 29 | │ │ ├── load_event.dart (页面加载Bloc) 30 | │ │ └── load_state.dart (页面加载Bloc) 31 | │ ├── I10n 32 | │ │ ├── text_delegate.dart (国际化-默认中文) 33 | │ │ └── text_delegate_en.dart (国际化-英文) 34 | │ ├── net 35 | │ │ ├── base_convert.dart (数据转换器基类) 36 | │ │ ├── base_net_engine.dart (网络请求基类) 37 | │ │ ├── base_net_provider.dart (网络请求基类) 38 | │ │ ├── base_request.dart (网络请求对象基类) 39 | │ │ ├── dio_engine.dart (Dio网络请求) 40 | │ │ └── entity 41 | │ │ └── base_entity.dart (网络请求基类) 42 | │ ├── service 43 | │ │ ├── env 44 | │ │ │ └── env.dart (App开发环境) 45 | │ │ ├── error 46 | │ │ │ └── domain_exception.dart (异常) 47 | │ │ ├── event 48 | │ │ │ └── global_event_manager.dart (全局事件发送、订阅) 49 | │ │ ├── route 50 | │ │ │ ├── bundle.dart (路由参数包装) 51 | │ │ │ ├── router_history_stack.dart (路由历史栈管理) 52 | │ │ │ ├── router_util.dart (路由工具类) 53 | │ │ │ └── routes.dart (路由) 54 | │ │ └── theme 55 | │ │ └── toast_theme_data.dart (吐司主题) 56 | │ ├── ui 57 | │ │ ├── common_button.dart (按钮组件) 58 | │ │ ├── common_click_widget.dart (点击事件组件) 59 | │ │ ├── common_empty_widget.dart (空视图组件) 60 | │ │ ├── common_error_widget.dart (错误视图组件) 61 | │ │ ├── common_image.dart (图片组件) 62 | │ │ ├── common_input.dart (输入框组件) 63 | │ │ ├── common_input_area.dart (多行输入框组件) 64 | │ │ ├── common_refresh_widget.dart (列表刷新组件) 65 | │ │ ├── common_rich_text.dart (列表刷新组件) 66 | │ │ ├── common_text.dart (文本组件) 67 | │ │ ├── dialog 68 | │ │ │ ├── base_dialog.dart (对话框基类) 69 | │ │ │ ├── common_dialog.dart (对话框) 70 | │ │ │ └── loading_dialog.dart (加载等待对话框) 71 | │ │ └── widget 72 | │ │ ├── amount_text_field_formatter.dart (金额输入框格式化) 73 | │ │ ├── bloc_load_widget.dart (页面加载组件) 74 | │ │ ├── circular_progress_widget.dart (加载组件) 75 | │ │ ├── data_change_widget.dart (简单Bloc组件) 76 | │ │ ├── extended_refresh_indicator.dart (配合ExtendedNestedScrollView刷新组件) 77 | │ │ ├── loading_dialog_widget.dart (全局加载等待组件) 78 | │ │ ├── remove_ripple_widget.dart (移除Android水波纹效果组件) 79 | │ │ ├── saturation_widget.dart (饱和度组件) 80 | │ │ └── un_focus_widget.dart (移除焦点组件) 81 | │ └── util 82 | │ ├── log_util.dart (日志打印工具类) 83 | │ ├── object_util.dart (对象工具类) 84 | │ ├── preference_util.dart (本地数据存储工具类) 85 | │ ├── time_util.dart (时间处理工具类) 86 | │ └── tip_util.dart (吐司工具类) 87 | ├── todo_app.dart (框架初始化) 88 | ├── todo_flutter.dart 89 | └── todo_lib.dart (框架数据包装) 90 | ``` -------------------------------------------------------------------------------- /assets/images/ic_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/assets/images/ic_empty.png -------------------------------------------------------------------------------- /assets/images/ic_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/assets/images/ic_error.png -------------------------------------------------------------------------------- /assets/images/ic_image_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/assets/images/ic_image_error.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/ephemeral 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ### 一、效果图 2 | 3 | 4 | 5 | 6 | ### 二、图片资源路径与路由路径通过[这个插件](https://github.com/Xie-Yin/FlutterPlugin)自动生成 7 | 8 | ### 三、目录结构 9 | 10 | ``` 11 | ./lib 12 | ├── common 13 | │ ├── api(网络请求) 14 | │ │ ├── api_convert.dart 15 | │ │ └── api_provider.dart 16 | │ └── route(页面路由) 17 | │ └── module_route.dart 18 | ├── domain(Bloc、网络请求相关) 19 | │ ├── bloc 20 | │ │ ├── bloc 21 | │ │ │ ├── test_bloc.dart 22 | │ │ │ ├── test_event.dart 23 | │ │ │ └── test_state.dart 24 | │ │ └── net 25 | │ │ ├── net_bloc.dart 26 | │ │ ├── net_event.dart 27 | │ │ └── net_state.dart 28 | │ └── request 29 | │ ├── entity 30 | │ │ ├── get_entity.dart 31 | │ │ └── list_data_entity.dart 32 | │ ├── list_request.dart 33 | │ └── net_request.dart 34 | ├── generated(自动生成代码存放目录) 35 | │ ├── assets 36 | │ │ ├── example_assets.dart 37 | │ │ └── example_icon.dart 38 | │ ├── json 39 | │ │ ├── base 40 | │ │ │ ├── json_convert_content.dart 41 | │ │ │ └── json_field.dart 42 | │ │ ├── get_entity.g.dart 43 | │ │ └── list_data_entity.g.dart 44 | │ └── route 45 | │ └── example_route.dart 46 | ├── main.dart(项目初始化) 47 | └── view(使用示例) 48 | ├── bloc_page.dart 49 | ├── button_page.dart 50 | ├── dialog_page.dart 51 | ├── image_page.dart 52 | ├── input_page.dart 53 | ├── net_page.dart 54 | ├── refresh_page.dart 55 | ├── sliver_page.dart 56 | ├── text_page.dart 57 | └── toast_page.dart 58 | ``` 59 | 60 | ### 运行之Web端 61 | > 增加启动参数:--web-renderer html -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 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 | android { 25 | namespace "com.azhon.todo_flutter" 26 | compileSdk 34 27 | 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | kotlinOptions { 33 | jvmTarget = '1.8' 34 | } 35 | sourceSets { 36 | main.java.srcDirs += 'src/main/kotlin' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.azhon.todo_flutter" 42 | minSdk 21 43 | targetSdk 30 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 12 | 16 | 20 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/azhon/todo_flutter/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.azhon.todo_flutter 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/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/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/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/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/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/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/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/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/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0' 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 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | tasks.register("clean", Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | plugins { 14 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 15 | } 16 | } 17 | 18 | include ":app" 19 | 20 | apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle" 21 | -------------------------------------------------------------------------------- /example/assets/iconfont/iconfont.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3788531", 3 | "name": "todo_flutter", 4 | "font_family": "iconfont", 5 | "css_prefix_text": "icon-", 6 | "description": "", 7 | "glyphs": [ 8 | { 9 | "icon_id": "33036883", 10 | "name": "menu", 11 | "font_class": "menu", 12 | "unicode": "e600", 13 | "unicode_decimal": 58880 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /example/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /example/assets/images/ic_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/assets/images/ic_clear.png -------------------------------------------------------------------------------- /example/assets/images/ic_eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/assets/images/ic_eye.png -------------------------------------------------------------------------------- /example/assets/images/ic_police.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/assets/images/ic_police.png -------------------------------------------------------------------------------- /example/assets/images/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/assets/images/ic_search.png -------------------------------------------------------------------------------- /example/img/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/img/img1.png -------------------------------------------------------------------------------- /example/img/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/img/img2.png -------------------------------------------------------------------------------- /example/img/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/img/img3.png -------------------------------------------------------------------------------- /example/img/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/img/img4.png -------------------------------------------------------------------------------- /example/img/img5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/img/img5.png -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_app_update (0.0.1): 4 | - Flutter 5 | - fluttertoast (0.0.2): 6 | - Flutter 7 | - Toast 8 | - path_provider_foundation (0.0.1): 9 | - Flutter 10 | - FlutterMacOS 11 | - shared_preferences_foundation (0.0.1): 12 | - Flutter 13 | - FlutterMacOS 14 | - Toast (4.0.0) 15 | 16 | DEPENDENCIES: 17 | - Flutter (from `Flutter`) 18 | - flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`) 19 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 20 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) 21 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) 22 | 23 | SPEC REPOS: 24 | trunk: 25 | - Toast 26 | 27 | EXTERNAL SOURCES: 28 | Flutter: 29 | :path: Flutter 30 | flutter_app_update: 31 | :path: ".symlinks/plugins/flutter_app_update/ios" 32 | fluttertoast: 33 | :path: ".symlinks/plugins/fluttertoast/ios" 34 | path_provider_foundation: 35 | :path: ".symlinks/plugins/path_provider_foundation/ios" 36 | shared_preferences_foundation: 37 | :path: ".symlinks/plugins/shared_preferences_foundation/ios" 38 | 39 | SPEC CHECKSUMS: 40 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 41 | flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc 42 | fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0 43 | path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 44 | shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 45 | Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 46 | 47 | PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 48 | 49 | COCOAPODS: 1.11.2 50 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | TodoFlutter 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/common/api/api_convert.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/15 on 14:31 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'dart:io'; 7 | import 'package:todo_flutter/todo_flutter.dart'; 8 | import 'package:todo_flutter_example/generated/json/base/json_convert_content.dart'; 9 | 10 | class ApiConvert extends BaseConvert { 11 | @override 12 | BaseEntity convert(Result result) { 13 | if (result.statusCode == HttpStatus.ok) { 14 | final data = result.data?['data']; 15 | 16 | ///分页数据 17 | if (data is Map && data.containsKey('datas')) { 18 | return BaseEntity( 19 | code: result.data?['errorCode'], 20 | message: result.data?['errorMsg'], 21 | curPage: data['curPage'], 22 | total: data['total'], 23 | totalPage: data['pageCount'], 24 | data: JsonConvert.fromJsonAsT(data['datas']), 25 | ); 26 | } else { 27 | return BaseEntity( 28 | code: result.data?['errorCode'], 29 | message: result.data?['errorMsg'], 30 | data: JsonConvert.fromJsonAsT(data), 31 | ); 32 | } 33 | } else { 34 | ///网络请求 code != HttpStatus.ok 35 | return BaseEntity( 36 | code: result.statusCode ?? BaseEntity.defaultCode, 37 | message: result.statusMessage, 38 | ); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/lib/common/api/api_provider.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/15 on 10:29 2 | /// desc: 单例对象,保证所有请求使用一份数据 3 | /// 4 | /// @author azhon 5 | import 'package:todo_flutter/todo_flutter.dart'; 6 | import 'package:todo_flutter_example/common/api/api_convert.dart'; 7 | 8 | class ApiProvider extends BaseNetProvider { 9 | factory ApiProvider() => _getInstance(); 10 | 11 | static ApiProvider get instance => _getInstance(); 12 | static ApiProvider? _instance; 13 | 14 | static ApiProvider _getInstance() { 15 | _instance ??= ApiProvider._internal(); 16 | return _instance!; 17 | } 18 | 19 | DioEngine dioEngine = DioEngine('http://www.wanandroid.com/'); 20 | ApiConvert apiConvert = ApiConvert(); 21 | 22 | ApiProvider._internal() { 23 | // dioEngine.setProxy('192.168.110.7', 8888); 24 | dioEngine.addInterceptor(TestInterceptor()); 25 | } 26 | 27 | @override 28 | BaseNetEngine get engine => dioEngine; 29 | 30 | @override 31 | BaseConvert get convert => apiConvert; 32 | } 33 | 34 | class TestInterceptor extends InterceptorsWrapper { 35 | @override 36 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 37 | options.headers['test-header'] = 'flutter'; 38 | handler.next(options); 39 | } 40 | } 41 | 42 | abstract class ApiRequest extends BaseRequest { 43 | ApiRequest(Map? params) : super(params); 44 | 45 | @override 46 | String get pageKey => 'page'; 47 | 48 | @override 49 | String get pageSizeKey => 'page_size'; 50 | 51 | @override 52 | BaseNetProvider get netProvider => ApiProvider.instance; 53 | } 54 | -------------------------------------------------------------------------------- /example/lib/common/route/module_route.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/17 on 18:12 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | import 'package:todo_flutter/todo_flutter.dart'; 6 | import 'package:todo_flutter_example/generated/route/example_route.dart'; 7 | import 'package:todo_flutter_example/view/app_update_page.dart'; 8 | import 'package:todo_flutter_example/view/bloc_page.dart'; 9 | import 'package:todo_flutter_example/view/button_page.dart'; 10 | import 'package:todo_flutter_example/view/dialog_page.dart'; 11 | import 'package:todo_flutter_example/view/image_page.dart'; 12 | import 'package:todo_flutter_example/view/input_page.dart'; 13 | import 'package:todo_flutter_example/view/net_page.dart'; 14 | import 'package:todo_flutter_example/view/refresh_page.dart'; 15 | import 'package:todo_flutter_example/view/sliver_page.dart'; 16 | import 'package:todo_flutter_example/view/text_page.dart'; 17 | import 'package:todo_flutter_example/view/toast_page.dart'; 18 | 19 | class ModuleRoute { 20 | ///添加模块路由 21 | static void initRoute() { 22 | RouterUtil.instance.addRoute(ExampleRoute.textPage, 23 | (Map map) { 24 | return const TextPage(); 25 | }); 26 | RouterUtil.instance.addRoute(ExampleRoute.buttonPage, 27 | (Map map) { 28 | return const ButtonPage(); 29 | }); 30 | RouterUtil.instance.addRoute(ExampleRoute.imagePage, 31 | (Map map) { 32 | return const ImagePage(); 33 | }); 34 | RouterUtil.instance.addRoute(ExampleRoute.toastPage, 35 | (Map map) { 36 | return const ToastPage(); 37 | }); 38 | RouterUtil.instance.addRoute(ExampleRoute.inputPage, 39 | (Map map) { 40 | return const InputPage(); 41 | }); 42 | RouterUtil.instance.addRoute(ExampleRoute.dialogPage, 43 | (Map map) { 44 | return const DialogPage(); 45 | }); 46 | RouterUtil.instance.addRoute(ExampleRoute.blocPage, 47 | (Map map) { 48 | return const BlocPage(); 49 | }); 50 | RouterUtil.instance.addRoute(ExampleRoute.netPage, 51 | (Map map) { 52 | return const NetPage(); 53 | }); 54 | RouterUtil.instance.addRoute(ExampleRoute.refreshPage, 55 | (Map map) { 56 | return const RefreshPage(); 57 | }); 58 | RouterUtil.instance.addRoute(ExampleRoute.appUpdatePage, 59 | (Map map) { 60 | return const AppUpdatePage(); 61 | }); 62 | RouterUtil.instance.addRoute(ExampleRoute.sliverPage, 63 | (Map map) { 64 | return const SliverPage(); 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /example/lib/domain/bloc/bloc/test_bloc.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/29 on 17:10 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'dart:math'; 7 | 8 | import 'package:todo_flutter/todo_flutter.dart'; 9 | import 'package:todo_flutter_example/domain/bloc/bloc/test_event.dart'; 10 | import 'package:todo_flutter_example/domain/bloc/bloc/test_state.dart'; 11 | 12 | class TestBloc extends BaseBloc { 13 | DataChangeBloc dataChangeBloc = DataChangeBloc(0); 14 | 15 | TestBloc() : super(TestInitialState(null)); 16 | 17 | void init() { 18 | add(InitEvent()); 19 | } 20 | 21 | void changeData() { 22 | final int random = Random().nextInt(9999); 23 | dataChangeBloc.changeData(random); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/lib/domain/bloc/bloc/test_event.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/29 on 17:11 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'dart:math'; 7 | 8 | import 'package:todo_flutter/todo_flutter.dart'; 9 | import 'package:todo_flutter_example/domain/bloc/bloc/test_bloc.dart'; 10 | import 'package:todo_flutter_example/domain/bloc/bloc/test_state.dart'; 11 | 12 | abstract class TestEvent extends BaseEvent {} 13 | 14 | class InitEvent extends TestEvent { 15 | @override 16 | Future on(TestBloc bloc, TestState currentState) async { 17 | showLoading(); 18 | final result = Random().nextInt(9999).toString(); 19 | await Future.delayed(const Duration(milliseconds: 1500)); 20 | dismissLoading(); 21 | return TestInitialState(result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/lib/domain/bloc/bloc/test_state.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/29 on 17:13 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | abstract class TestState { 7 | final String? data; 8 | 9 | TestState(this.data); 10 | } 11 | 12 | class TestInitialState extends TestState { 13 | TestInitialState(String? data) : super(data); 14 | } 15 | -------------------------------------------------------------------------------- /example/lib/domain/bloc/net/net_bloc.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/15 on 11:54 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:todo_flutter/todo_flutter.dart'; 7 | import 'package:todo_flutter_example/domain/bloc/net/net_event.dart'; 8 | import 'package:todo_flutter_example/domain/bloc/net/net_state.dart'; 9 | 10 | class NetBloc extends BaseBloc { 11 | NetBloc() : super(NetInitialState(null)); 12 | 13 | void get() { 14 | add(GetEvent()); 15 | } 16 | 17 | void post() { 18 | add(PostEvent()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/lib/domain/bloc/net/net_event.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/15 on 11:55 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:todo_flutter/todo_flutter.dart'; 7 | import 'package:todo_flutter_example/domain/bloc/net/net_bloc.dart'; 8 | import 'package:todo_flutter_example/domain/bloc/net/net_state.dart'; 9 | import 'package:todo_flutter_example/domain/request/net_request.dart'; 10 | 11 | abstract class NetEvent extends BaseEvent {} 12 | 13 | class GetEvent extends NetEvent { 14 | @override 15 | Future on(NetBloc bloc, NetState currentState) async { 16 | showLoading(); 17 | final bean = await NetGetRequest().request(); 18 | await Future.delayed(const Duration(seconds: 1)); 19 | dismissLoading(); 20 | bloc.loadDone(); 21 | return NetInitialState(bean.data); 22 | } 23 | } 24 | 25 | class PostEvent extends NetEvent { 26 | @override 27 | Future on(NetBloc bloc, NetState currentState) async { 28 | bloc.loading(); 29 | final bean = await NetPostRequest().request(); 30 | await Future.delayed(const Duration(seconds: 1)); 31 | bloc.loadError(NetworkException(bean)); 32 | return NetInitialState(null); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/lib/domain/bloc/net/net_state.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/15 on 11:55 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:todo_flutter_example/domain/request/entity/get_entity.dart'; 7 | 8 | abstract class NetState { 9 | final List? data; 10 | 11 | NetState(this.data); 12 | } 13 | 14 | class NetInitialState extends NetState { 15 | NetInitialState(List? data) : super(data); 16 | } 17 | -------------------------------------------------------------------------------- /example/lib/domain/request/entity/get_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:todo_flutter_example/generated/json/base/json_field.dart'; 2 | import 'package:todo_flutter_example/generated/json/get_entity.g.dart'; 3 | import 'dart:convert'; 4 | 5 | @JsonSerializable() 6 | class GetEntity { 7 | String? desc; 8 | int? id; 9 | String? imagePath; 10 | int? isVisible; 11 | int? order; 12 | String? title; 13 | int? type; 14 | String? url; 15 | 16 | GetEntity(); 17 | 18 | factory GetEntity.fromJson(Map json) => 19 | $GetEntityFromJson(json); 20 | 21 | Map toJson() => $GetEntityToJson(this); 22 | 23 | @override 24 | String toString() { 25 | return jsonEncode(this); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/lib/domain/request/entity/list_data_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:todo_flutter_example/generated/json/base/json_field.dart'; 2 | import 'package:todo_flutter_example/generated/json/list_data_entity.g.dart'; 3 | import 'dart:convert'; 4 | 5 | @JsonSerializable() 6 | class ListDataEntity { 7 | 8 | bool? adminAdd; 9 | String? apkLink; 10 | int? audit; 11 | String? author; 12 | bool? canEdit; 13 | int? chapterId; 14 | String? chapterName; 15 | bool? collect; 16 | int? courseId; 17 | String? desc; 18 | String? descMd; 19 | String? envelopePic; 20 | bool? fresh; 21 | String? host; 22 | int? id; 23 | bool? isAdminAdd; 24 | String? link; 25 | String? niceDate; 26 | String? niceShareDate; 27 | String? origin; 28 | String? prefix; 29 | String? projectLink; 30 | int? publishTime; 31 | int? realSuperChapterId; 32 | int? selfVisible; 33 | int? shareDate; 34 | String? shareUser; 35 | int? superChapterId; 36 | String? superChapterName; 37 | List? tags; 38 | String? title; 39 | int? type; 40 | int? userId; 41 | int? visible; 42 | int? zan; 43 | 44 | ListDataEntity(); 45 | 46 | factory ListDataEntity.fromJson(Map json) => $ListDataEntityFromJson(json); 47 | 48 | Map toJson() => $ListDataEntityToJson(this); 49 | 50 | @override 51 | String toString() { 52 | return jsonEncode(this); 53 | } 54 | } -------------------------------------------------------------------------------- /example/lib/domain/request/list_request.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2022/07/27 on 10:14 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:todo_flutter/todo_flutter.dart'; 7 | import 'package:todo_flutter_example/common/api/api_provider.dart'; 8 | import 'package:todo_flutter_example/domain/request/entity/list_data_entity.dart'; 9 | 10 | class ListRequest extends ApiRequest> { 11 | ListRequest() : super(null); 12 | 13 | @override 14 | RequestMethod get method => RequestMethod.get; 15 | 16 | @override 17 | String get url => 'article/list/$page/json'; 18 | } 19 | -------------------------------------------------------------------------------- /example/lib/domain/request/net_request.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/15 on 13:40 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:todo_flutter/todo_flutter.dart'; 7 | import 'package:todo_flutter_example/common/api/api_provider.dart'; 8 | import 'package:todo_flutter_example/domain/request/entity/get_entity.dart'; 9 | 10 | class NetGetRequest extends ApiRequest> { 11 | NetGetRequest() : super({'id': '323', 'name': 'azhon'}); 12 | 13 | @override 14 | RequestMethod get method => RequestMethod.get; 15 | 16 | @override 17 | String get url => 'banner/json'; 18 | } 19 | 20 | class NetPostRequest extends ApiRequest { 21 | NetPostRequest() : super({'id': '323', 'name': 'azhon'}); 22 | 23 | @override 24 | RequestMethod get method => RequestMethod.post; 25 | 26 | @override 27 | String get url => 'lg/uncollect_originId/2333/json'; 28 | } 29 | -------------------------------------------------------------------------------- /example/lib/generated/assets/example_assets.dart: -------------------------------------------------------------------------------- 1 | ///This file is automatically generated by the [FlutterResource], your modifications will be lost. 2 | class ExampleAssets { 3 | static const icEye = 'assets/images/ic_eye.png'; 4 | static const icClear = 'assets/images/ic_clear.png'; 5 | static const icPolice = 'assets/images/ic_police.png'; 6 | static const icSearch = 'assets/images/ic_search.png'; 7 | } 8 | -------------------------------------------------------------------------------- /example/lib/generated/assets/example_icon.dart: -------------------------------------------------------------------------------- 1 | ///This file is automatically generated by the [FlutterResource], your modifications will be lost. 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class ExampleIcon { 5 | static const IconData menu = IconData(0xe600, fontFamily: 'todoIcon'); 6 | } 7 | -------------------------------------------------------------------------------- /example/lib/generated/json/base/json_convert_content.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | // ignore_for_file: camel_case_types 3 | // ignore_for_file: prefer_single_quotes 4 | 5 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 6 | import 'package:flutter/material.dart' show debugPrint; 7 | import 'package:todo_flutter_example/domain/request/entity/get_entity.dart'; 8 | import 'package:todo_flutter_example/domain/request/entity/list_data_entity.dart'; 9 | 10 | JsonConvert jsonConvert = JsonConvert(); 11 | typedef JsonConvertFunction = T Function(Map json); 12 | 13 | class JsonConvert { 14 | static final Map _convertFuncMap = { 15 | (GetEntity).toString(): GetEntity.fromJson, 16 | (ListDataEntity).toString(): ListDataEntity.fromJson, 17 | }; 18 | 19 | T? convert(dynamic value) { 20 | if (value == null) { 21 | return null; 22 | } 23 | return asT(value); 24 | } 25 | 26 | List? convertList(List? value) { 27 | if (value == null) { 28 | return null; 29 | } 30 | try { 31 | return value.map((dynamic e) => asT(e)).toList(); 32 | } catch (e, stackTrace) { 33 | debugPrint('asT<$T> $e $stackTrace'); 34 | return []; 35 | } 36 | } 37 | 38 | List? convertListNotNull(dynamic value) { 39 | if (value == null) { 40 | return null; 41 | } 42 | try { 43 | return (value as List).map((dynamic e) => asT(e)!).toList(); 44 | } catch (e, stackTrace) { 45 | debugPrint('asT<$T> $e $stackTrace'); 46 | return []; 47 | } 48 | } 49 | 50 | T? asT(dynamic value) { 51 | if (value is T) { 52 | return value; 53 | } 54 | final String type = T.toString(); 55 | try { 56 | final String valueS = value.toString(); 57 | if (type == "String") { 58 | return valueS as T; 59 | } else if (type == "int") { 60 | final int? intValue = int.tryParse(valueS); 61 | if (intValue == null) { 62 | return double.tryParse(valueS)?.toInt() as T?; 63 | } else { 64 | return intValue as T; 65 | } 66 | } else if (type == "double") { 67 | return double.parse(valueS) as T; 68 | } else if (type == "DateTime") { 69 | return DateTime.parse(valueS) as T; 70 | } else if (type == "bool") { 71 | if (valueS == '0' || valueS == '1') { 72 | return (valueS == '1') as T; 73 | } 74 | return (valueS == 'true') as T; 75 | } else if (type == "Map" || type.startsWith("Map<")) { 76 | return value as T; 77 | } else { 78 | if (_convertFuncMap.containsKey(type)) { 79 | return _convertFuncMap[type]!(value) as T; 80 | } else { 81 | throw UnimplementedError('$type unimplemented'); 82 | } 83 | } 84 | } catch (e, stackTrace) { 85 | debugPrint('asT<$T> $e $stackTrace'); 86 | return null; 87 | } 88 | } 89 | 90 | //list is returned by type 91 | static M? _getListChildType(List> data) { 92 | if([] is M){ 93 | return data.map((Map e) => GetEntity.fromJson(e)).toList() as M; 94 | } 95 | if([] is M){ 96 | return data.map((Map e) => ListDataEntity.fromJson(e)).toList() as M; 97 | } 98 | 99 | debugPrint("${M.toString()} not found"); 100 | 101 | return null; 102 | } 103 | 104 | static M? fromJsonAsT(dynamic json) { 105 | if (json is List) { 106 | return _getListChildType(json.map((e) => e as Map).toList()); 107 | } else { 108 | return jsonConvert.asT(json); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /example/lib/generated/json/base/json_field.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | // ignore_for_file: camel_case_types 3 | // ignore_for_file: prefer_single_quotes 4 | 5 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 6 | 7 | class JsonSerializable{ 8 | const JsonSerializable(); 9 | } 10 | 11 | class JSONField { 12 | //Specify the parse field name 13 | final String? name; 14 | 15 | //Whether to participate in toJson 16 | final bool? serialize; 17 | 18 | //Whether to participate in fromMap 19 | final bool? deserialize; 20 | 21 | const JSONField({this.name, this.serialize, this.deserialize}); 22 | } 23 | -------------------------------------------------------------------------------- /example/lib/generated/json/get_entity.g.dart: -------------------------------------------------------------------------------- 1 | import 'package:todo_flutter_example/generated/json/base/json_convert_content.dart'; 2 | import 'package:todo_flutter_example/domain/request/entity/get_entity.dart'; 3 | 4 | GetEntity $GetEntityFromJson(Map json) { 5 | final GetEntity getEntity = GetEntity(); 6 | final String? desc = jsonConvert.convert(json['desc']); 7 | if (desc != null) { 8 | getEntity.desc = desc; 9 | } 10 | final int? id = jsonConvert.convert(json['id']); 11 | if (id != null) { 12 | getEntity.id = id; 13 | } 14 | final String? imagePath = jsonConvert.convert(json['imagePath']); 15 | if (imagePath != null) { 16 | getEntity.imagePath = imagePath; 17 | } 18 | final int? isVisible = jsonConvert.convert(json['isVisible']); 19 | if (isVisible != null) { 20 | getEntity.isVisible = isVisible; 21 | } 22 | final int? order = jsonConvert.convert(json['order']); 23 | if (order != null) { 24 | getEntity.order = order; 25 | } 26 | final String? title = jsonConvert.convert(json['title']); 27 | if (title != null) { 28 | getEntity.title = title; 29 | } 30 | final int? type = jsonConvert.convert(json['type']); 31 | if (type != null) { 32 | getEntity.type = type; 33 | } 34 | final String? url = jsonConvert.convert(json['url']); 35 | if (url != null) { 36 | getEntity.url = url; 37 | } 38 | return getEntity; 39 | } 40 | 41 | Map $GetEntityToJson(GetEntity entity) { 42 | final Map data = {}; 43 | data['desc'] = entity.desc; 44 | data['id'] = entity.id; 45 | data['imagePath'] = entity.imagePath; 46 | data['isVisible'] = entity.isVisible; 47 | data['order'] = entity.order; 48 | data['title'] = entity.title; 49 | data['type'] = entity.type; 50 | data['url'] = entity.url; 51 | return data; 52 | } -------------------------------------------------------------------------------- /example/lib/generated/route/example_route.dart: -------------------------------------------------------------------------------- 1 | ///This file is automatically generated by the [FlutterResource], your modifications will be lost. 2 | class ExampleRoute { 3 | static const netPage = '/example/netPage'; 4 | static const blocPage = '/example/blocPage'; 5 | static const textPage = '/example/textPage'; 6 | static const imagePage = '/example/imagePage'; 7 | static const inputPage = '/example/inputPage'; 8 | static const toastPage = '/example/toastPage'; 9 | static const buttonPage = '/example/buttonPage'; 10 | static const dialogPage = '/example/dialogPage'; 11 | static const sliverPage = '/example/sliverPage'; 12 | static const refreshPage = '/example/refreshPage'; 13 | static const appUpdatePage = '/example/appUpdatePage'; 14 | } 15 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:todo_flutter/todo_flutter.dart'; 5 | import 'package:todo_flutter/todo_app.dart'; 6 | import 'package:todo_flutter_example/common/route/module_route.dart'; 7 | import 'package:todo_flutter_example/generated/route/example_route.dart'; 8 | 9 | GlobalKey naviKey = GlobalKey(); 10 | 11 | void main() { 12 | WidgetsFlutterBinding.ensureInitialized(); 13 | runApp( 14 | NetworkCaptureApp( 15 | navigatorKey: naviKey, 16 | child: TodoApp( 17 | home: const MyHomePage(), 18 | navigatorKey: naviKey, 19 | theme: ThemeData( 20 | useMaterial3: false, 21 | ), 22 | ), 23 | ), 24 | ); 25 | ModuleRoute.initRoute(); 26 | } 27 | 28 | class MyHomePage extends StatefulWidget { 29 | const MyHomePage({Key? key}) : super(key: key); 30 | 31 | @override 32 | State createState() => _MyHomePageState(); 33 | } 34 | 35 | class _MyHomePageState extends BaseState { 36 | List> routes = [ 37 | ['文本示例', ExampleRoute.textPage], 38 | ['按钮示例', ExampleRoute.buttonPage], 39 | ['图片示例', ExampleRoute.imagePage], 40 | ['Toast示例', ExampleRoute.toastPage], 41 | ['输入框示例', ExampleRoute.inputPage], 42 | ['Dialog示例', ExampleRoute.dialogPage], 43 | ['Bloc示例', ExampleRoute.blocPage], 44 | ['网络示例', ExampleRoute.netPage], 45 | ['下拉刷新示例', ExampleRoute.refreshPage], 46 | ['版本更新示例', ExampleRoute.appUpdatePage], 47 | ['Sliver示例', ExampleRoute.sliverPage], 48 | ]; 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return Scaffold( 53 | appBar: AppBar( 54 | title: const Text('TODO-Flutter'), 55 | ), 56 | body: GridView.builder( 57 | itemCount: routes.length, 58 | padding: all(16), 59 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 60 | crossAxisCount: 2, 61 | childAspectRatio: 2.2, 62 | mainAxisSpacing: setWidth(16), 63 | crossAxisSpacing: setWidth(16), 64 | ), 65 | itemBuilder: (_, index) { 66 | final item = routes[index]; 67 | return CommonClickWidget( 68 | singleClick: () { 69 | RouterUtil.instance 70 | .build(routes[index].last) 71 | .withString('key-s', null) 72 | .withBool('key-b', false) 73 | .withNum('key-n', 3) 74 | .navigate(); 75 | }, 76 | child: Container( 77 | alignment: Alignment.center, 78 | decoration: BoxDecoration( 79 | color: Color.fromRGBO( 80 | Random().nextInt(255), 81 | Random().nextInt(255), 82 | Random().nextInt(255), 83 | 1, 84 | ), 85 | borderRadius: BorderRadius.circular(setRadius(10)), 86 | ), 87 | child: CommonText( 88 | item.first, 89 | color: Colors.white, 90 | fontSize: 16, 91 | fontWeight: FontWeight.w500, 92 | ), 93 | ), 94 | ); 95 | }, 96 | ), 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /example/lib/view/app_update_page.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/18 on 14:58 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/todo_flutter.dart'; 8 | import 'package:flutter_app_update/flutter_app_update.dart'; 9 | 10 | class AppUpdatePage extends StatefulWidget { 11 | const AppUpdatePage({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _AppUpdatePageState(); 15 | } 16 | 17 | class _AppUpdatePageState extends BaseState { 18 | @override 19 | void initState() { 20 | super.initState(); 21 | AzhonAppUpdate.listener((model) { 22 | debugPrint(model.toString()); 23 | }); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | appBar: AppBar(title: const Text('版本更新示例')), 30 | body: Column( 31 | children: [ 32 | CommonButton( 33 | '更新', 34 | width: double.infinity, 35 | color: Colors.blue, 36 | radius: 20, 37 | margin: only(left: 20, right: 20, top: 40), 38 | onPressed: _showUpdateDialog, 39 | ), 40 | ], 41 | ), 42 | ); 43 | } 44 | 45 | void _showUpdateDialog() { 46 | showDialog( 47 | context: context, 48 | builder: (BuildContext context) { 49 | return AlertDialog( 50 | elevation: 0, 51 | title: const Text('发现新版本'), 52 | content: const Text( 53 | '1.支持Android4.1及以上版本\n2.支持自定义下载过程\n3.支持通知栏进度条展示\n4.支持文字国际化\n5.使用Kotlin协程重构'), 54 | actions: [ 55 | TextButton( 56 | child: const Text('取消'), 57 | onPressed: () => Navigator.of(context).pop(), 58 | ), 59 | TextButton( 60 | child: const Text('升级'), 61 | onPressed: () { 62 | _appUpdate(); 63 | Navigator.of(context).pop(); 64 | }, 65 | ), 66 | ], 67 | ); 68 | }, 69 | ); 70 | } 71 | 72 | void _appUpdate() { 73 | final UpdateModel model = UpdateModel( 74 | 'http://s.duapps.com/apks/own/ESFileExplorer-cn.apk', 75 | 'flutterUpdate.apk', 76 | 'ic_launcher', 77 | 'https://itunes.apple.com/cn/app/抖音/id1142110895', 78 | ); 79 | AzhonAppUpdate.update(model).then((value) => debugPrint('$value')); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /example/lib/view/bloc_page.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/29 on 16:24 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/todo_flutter.dart'; 8 | import 'package:todo_flutter_example/domain/bloc/bloc/test_bloc.dart'; 9 | import 'package:todo_flutter_example/domain/bloc/bloc/test_state.dart'; 10 | 11 | class BlocPage extends StatefulWidget { 12 | const BlocPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | State createState() => _BlocPageState(); 16 | } 17 | 18 | class _BlocPageState extends BaseState { 19 | TestBloc get _bloc => getBloc(); 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | addBloc(TestBloc()); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | appBar: AppBar(title: const Text('Bloc示例')), 31 | body: Padding( 32 | padding: all(32), 33 | child: Column( 34 | children: [ 35 | BlocBuilder( 36 | bloc: _bloc, 37 | builder: (BuildContext context, state) { 38 | return CommonText('通过bloc获取的数据:${state.data}'); 39 | }, 40 | ), 41 | CommonButton( 42 | 'Bloc获取数据', 43 | width: double.infinity, 44 | margin: only(top: 16, bottom: 16), 45 | radius: 20, 46 | onPressed: () => _bloc.init(), 47 | ), 48 | DataChangeWidget( 49 | bloc: _bloc.dataChangeBloc, 50 | child: (context, state) { 51 | return CommonText('简单Bloc:$state'); 52 | }, 53 | ), 54 | CommonButton( 55 | '简单Bloc获取数据', 56 | width: double.infinity, 57 | margin: only(top: 16), 58 | radius: 20, 59 | onPressed: () => _bloc.changeData(), 60 | ), 61 | ], 62 | ), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/lib/view/button_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/todo_flutter.dart'; 3 | 4 | /// createTime: 2021/9/17 on 18:14 5 | /// desc: 6 | /// 7 | /// @author azhon 8 | class ButtonPage extends BaseStatefulWidget { 9 | const ButtonPage({Key? key}) : super(key: key); 10 | 11 | @override 12 | State createState() => _ButtonPageState(); 13 | } 14 | 15 | class _ButtonPageState extends BaseState { 16 | @override 17 | Widget build(BuildContext context) { 18 | return Scaffold( 19 | appBar: AppBar(title: const Text('按钮示例')), 20 | body: SingleChildScrollView( 21 | child: Padding( 22 | padding: all(16), 23 | child: Column( 24 | children: [ 25 | Row( 26 | mainAxisAlignment: MainAxisAlignment.center, 27 | children: [ 28 | CommonButton( 29 | '圆形\n按钮', 30 | width: 60, 31 | height: 60, 32 | color: Colors.blue, 33 | radius: 100, 34 | fontSize: 13, 35 | onPressed: () {}, 36 | onLongPress: () {}, 37 | ), 38 | CommonButton( 39 | '圆角按钮', 40 | color: Colors.blue, 41 | radius: 20, 42 | width: 100, 43 | margin: only(left: 10, top: 10), 44 | ), 45 | CommonButton( 46 | '小按钮', 47 | radius: 10, 48 | fontSize: 10, 49 | padding: symmetric(vertical: 4, horizontal: 13), 50 | color: Colors.blue, 51 | margin: only(left: 10, top: 10), 52 | ), 53 | ], 54 | ), 55 | CommonButton( 56 | '不可点击按钮', 57 | width: double.infinity, 58 | color: Colors.grey, 59 | disable: true, 60 | margin: only(top: 10), 61 | ), 62 | CommonButton( 63 | '普通按钮', 64 | width: double.infinity, 65 | margin: only(top: 10), 66 | ), 67 | CommonButton( 68 | '圆角按钮', 69 | width: double.infinity, 70 | color: Colors.blue, 71 | radius: 20, 72 | margin: only(top: 10), 73 | ), 74 | CommonButton( 75 | '两边正圆角按钮', 76 | width: double.infinity, 77 | color: Colors.blue, 78 | radius: 100, 79 | margin: only(top: 10), 80 | ), 81 | CommonButton( 82 | '渐变色按钮', 83 | width: double.infinity, 84 | gradient: const LinearGradient( 85 | colors: [Colors.blue, Colors.red], 86 | ), 87 | margin: only(top: 10), 88 | ), 89 | CommonButton( 90 | '镂空不可点击按钮', 91 | width: double.infinity, 92 | disable: true, 93 | color: Colors.transparent, 94 | borderColor: Colors.red, 95 | textColor: Colors.red, 96 | margin: only(top: 10), 97 | ), 98 | CommonButton( 99 | '镂空按钮', 100 | width: double.infinity, 101 | color: Colors.transparent, 102 | borderColor: Colors.red, 103 | textColor: Colors.red, 104 | margin: only(top: 10), 105 | ), 106 | CommonButton( 107 | '镂空圆角按钮', 108 | width: double.infinity, 109 | color: Colors.transparent, 110 | borderColor: Colors.red, 111 | textColor: Colors.red, 112 | radius: 10, 113 | margin: only(top: 10), 114 | ), 115 | CommonButton( 116 | '镂空两边正圆角按钮', 117 | width: double.infinity, 118 | color: Colors.transparent, 119 | borderColor: Colors.red, 120 | textColor: Colors.red, 121 | radius: 100, 122 | margin: only(top: 10), 123 | ), 124 | ], 125 | ), 126 | ), 127 | ), 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /example/lib/view/dialog_page.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2023/2/23 on 15:51 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/todo_flutter.dart'; 8 | 9 | class DialogPage extends StatefulWidget { 10 | const DialogPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | State createState() => _DialogPageState(); 14 | } 15 | 16 | class _DialogPageState extends BaseState { 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: AppBar(title: const Text('Dialog示例')), 21 | body: Padding( 22 | padding: all(16), 23 | child: Column( 24 | children: [ 25 | CommonButton( 26 | '等待对话框', 27 | width: double.infinity, 28 | margin: only(bottom: 10), 29 | radius: 10, 30 | onPressed: () { 31 | LoadingDialog.show(msg: '请稍后...'); 32 | Future.delayed(const Duration(seconds: 2)).then((value) { 33 | LoadingDialog.show(); 34 | }); 35 | Future.delayed(const Duration(seconds: 4)).then((value) { 36 | LoadingDialog.dismiss(); 37 | }); 38 | }, 39 | ), 40 | CommonButton( 41 | '提示对话框', 42 | width: double.infinity, 43 | margin: only(bottom: 10), 44 | radius: 10, 45 | onPressed: () { 46 | CommonDialog.instance.tipDialog(context, '提示', '你确定要这么操作吗' * 5); 47 | }, 48 | ), 49 | CommonButton( 50 | '提示对话框内存超长', 51 | width: double.infinity, 52 | margin: only(bottom: 10), 53 | radius: 10, 54 | onPressed: () { 55 | CommonDialog.instance 56 | .tipDialog(context, '提示', '你确定要这么操作吗' * 50); 57 | }, 58 | ), 59 | CommonButton( 60 | '无标题对话框', 61 | width: double.infinity, 62 | margin: only(bottom: 10), 63 | radius: 10, 64 | onPressed: () { 65 | CommonDialog.instance.tipDialog(context, null, '你确定要这么操作吗' * 5); 66 | }, 67 | ), 68 | CommonButton( 69 | '单按钮对话框', 70 | width: double.infinity, 71 | margin: only(bottom: 10), 72 | radius: 10, 73 | onPressed: () { 74 | CommonDialog.instance 75 | .singleButtonDialog(context, '提示', '你确定要这么操作吗' * 5); 76 | }, 77 | ), 78 | CommonButton( 79 | '输入框对话框', 80 | width: double.infinity, 81 | margin: only(bottom: 10), 82 | radius: 10, 83 | onPressed: () { 84 | CommonDialog.instance.inputDialog( 85 | context, 86 | '请输入密码', 87 | placeholder: '请输入密码', 88 | ); 89 | }, 90 | ), 91 | ], 92 | ), 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /example/lib/view/image_page.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/22 on 17:19 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | import 'package:flutter/material.dart'; 6 | import 'package:todo_flutter/todo_flutter.dart'; 7 | import 'package:todo_flutter_example/generated/assets/example_assets.dart'; 8 | import 'package:todo_flutter_example/generated/assets/example_icon.dart'; 9 | 10 | class ImagePage extends BaseStatefulWidget { 11 | const ImagePage({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _ImagePageState(); 15 | } 16 | 17 | class _ImagePageState extends BaseState { 18 | final String imgUrl = 19 | 'https://profile-avatar.csdnimg.cn/8bd82632b9c24ebba970cd1d6581d35f_a_zhon.jpg!1'; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: AppBar(title: const Text('图片示例')), 25 | body: SingleChildScrollView( 26 | child: Padding( 27 | padding: all(16), 28 | child: Wrap( 29 | runSpacing: setWidth(10), 30 | spacing: setWidth(10), 31 | children: [ 32 | Icon( 33 | ExampleIcon.menu, 34 | size: setWidth(48), 35 | ), 36 | CommonImage( 37 | network: imgUrl, 38 | width: 80, 39 | height: 80, 40 | ), 41 | CommonImage( 42 | network: imgUrl, 43 | borderRadius: BorderRadius.circular(setRadius(20)), 44 | ), 45 | CommonImage( 46 | network: imgUrl, 47 | circle: true, 48 | size: 150, 49 | ), 50 | CommonImage( 51 | network: imgUrl, 52 | circle: true, 53 | size: 150, 54 | border: Border.all( 55 | color: Colors.pink, 56 | width: setWidth(2), 57 | ), 58 | ), 59 | 60 | ///本地图片 61 | const CommonImage( 62 | asset: ExampleAssets.icPolice, 63 | width: 150, 64 | height: 150, 65 | ), 66 | ], 67 | ), 68 | ), 69 | ), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /example/lib/view/net_page.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/14 on 11:15 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | import 'package:flutter/material.dart'; 6 | import 'package:todo_flutter/todo_flutter.dart'; 7 | import 'package:todo_flutter_example/domain/bloc/net/net_bloc.dart'; 8 | import 'package:todo_flutter_example/domain/bloc/net/net_state.dart'; 9 | 10 | class NetPage extends StatefulWidget { 11 | const NetPage({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _NetPageState(); 15 | } 16 | 17 | class _NetPageState extends BaseState { 18 | NetBloc get _netBloc => getBloc(); 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | addBloc(NetBloc()); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | appBar: AppBar(title: const Text('网络示例')), 30 | body: Column( 31 | children: [ 32 | CommonButton( 33 | 'Get请求(全屏加载框)', 34 | width: double.infinity, 35 | radius: 20, 36 | margin: all(16), 37 | onPressed: () { 38 | _netBloc.get(); 39 | }, 40 | ), 41 | CommonButton( 42 | 'Post请求(局部加载框)', 43 | width: double.infinity, 44 | radius: 20, 45 | margin: all(16), 46 | onPressed: () { 47 | _netBloc.post(); 48 | }, 49 | ), 50 | SizedBox( 51 | height: setWidth(300), 52 | child: BlocLoadWidget( 53 | loadBloc: _netBloc.loadBloc, 54 | reload: () => _netBloc.get(), 55 | child: BlocBuilder( 56 | bloc: _netBloc, 57 | builder: (_, state) { 58 | return CommonText(state.data?.toString() ?? ''); 59 | }, 60 | ), 61 | ), 62 | ), 63 | ], 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /example/lib/view/refresh_page.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2022/7/27 on 11:50 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/todo_flutter.dart'; 8 | import 'package:todo_flutter_example/domain/request/entity/list_data_entity.dart'; 9 | import 'package:todo_flutter_example/domain/request/list_request.dart'; 10 | 11 | class RefreshPage extends StatefulWidget { 12 | const RefreshPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | State createState() => _RefreshPageState(); 16 | } 17 | 18 | class _RefreshPageState extends BaseState { 19 | late ListBloc _listBloc; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | _listBloc = ListBloc(request: ListRequest(), startPageNum: 0); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | appBar: AppBar(title: const Text('下拉刷新示例')), 31 | body: CommonRefreshWidget( 32 | bloc: _listBloc, 33 | child: (context, list) { 34 | return ListView.builder( 35 | itemCount: list.length, 36 | padding: all(16), 37 | itemBuilder: (_, index) { 38 | return Card( 39 | child: ListTile( 40 | title: CommonText(list[index].title ?? ''), 41 | subtitle: Align( 42 | alignment: Alignment.centerRight, 43 | child: CommonText(list[index].niceDate ?? ''), 44 | ), 45 | ), 46 | ); 47 | }, 48 | ); 49 | }, 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/lib/view/sliver_page.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2022/7/28 on 11:21 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'dart:math'; 7 | 8 | import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:todo_flutter/todo_flutter.dart'; 11 | import 'package:todo_flutter_example/generated/assets/example_assets.dart'; 12 | 13 | class SliverPage extends StatefulWidget { 14 | const SliverPage({Key? key}) : super(key: key); 15 | 16 | @override 17 | State createState() => _SliverPageState(); 18 | } 19 | 20 | class _SliverPageState extends BaseState 21 | with TickerProviderStateMixin { 22 | final List _tabs = ['Tab1', 'Tab2', 'Tab3', 'Tab4']; 23 | late TabController _tabController; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | _tabController = TabController(length: _tabs.length, vsync: this); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: AppBar(title: const Text('Sliver示例')), 35 | 36 | ///pull refresh 37 | body: ExtendedRefreshIndicator( 38 | notificationPredicate: (notification) { 39 | return true; 40 | }, 41 | onRefresh: () { 42 | final second = Random().nextInt(2) + 1; 43 | return Future.delayed(Duration(seconds: second), () { 44 | return true; 45 | }); 46 | }, 47 | child: ExtendedNestedScrollView( 48 | headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { 49 | return _createSlivers(); 50 | }, 51 | body: Column( 52 | children: [ 53 | TabBar( 54 | controller: _tabController, 55 | labelColor: Colors.black, 56 | indicatorSize: TabBarIndicatorSize.label, 57 | tabs: _tabs.map((e) { 58 | return Tab(text: e); 59 | }).toList(), 60 | ), 61 | Expanded( 62 | child: TabBarView( 63 | controller: _tabController, 64 | children: _tabs 65 | .map( 66 | (e) => BodyWidget( 67 | tab: e, 68 | key: PageStorageKey(e), 69 | ), 70 | ) 71 | .toList(), 72 | ), 73 | ), 74 | ], 75 | ), 76 | ), 77 | ), 78 | ); 79 | } 80 | 81 | /// 是否需要刷新 82 | List _createSlivers() { 83 | return [ 84 | SliverToBoxAdapter( 85 | child: Column( 86 | children: [ 87 | Container(height: 50, color: Colors.amber), 88 | Padding( 89 | padding: symmetric(vertical: 16), 90 | child: const CommonText( 91 | '头部可以放任意组件', 92 | fontSize: 16, 93 | ), 94 | ), 95 | Container(height: 50, color: Colors.blue), 96 | const CommonImage( 97 | asset: ExampleAssets.icPolice, 98 | width: 150, 99 | height: 150, 100 | ), 101 | Container(height: 50, color: Colors.orange), 102 | Padding( 103 | padding: symmetric(vertical: 16), 104 | child: const CommonText( 105 | '底部Tab带状态保存', 106 | fontSize: 16, 107 | ), 108 | ), 109 | Container(height: 50, color: Colors.indigoAccent), 110 | ], 111 | ), 112 | ), 113 | ]; 114 | } 115 | } 116 | 117 | class BodyWidget extends BaseStatelessWidget { 118 | final String tab; 119 | 120 | const BodyWidget({required this.tab, Key? key}) : super(key: key); 121 | 122 | @override 123 | Widget build(BuildContext context) { 124 | return ListView.separated( 125 | itemCount: 100, 126 | physics: const ClampingScrollPhysics(), 127 | itemBuilder: (_, index) { 128 | return Container( 129 | height: setWidth(50), 130 | color: Colors.blue[200], 131 | child: Center(child: Text('$tab -- $index')), 132 | ); 133 | }, 134 | separatorBuilder: (_, index) { 135 | return sizedBox(height: 8); 136 | }, 137 | ); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /example/lib/view/text_page.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/18 on 14:50 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | import 'package:flutter/material.dart'; 6 | import 'package:todo_flutter/todo_flutter.dart'; 7 | 8 | class TextPage extends BaseStatefulWidget { 9 | const TextPage({Key? key}) : super(key: key); 10 | 11 | @override 12 | State createState() => _TextPageState(); 13 | } 14 | 15 | class _TextPageState extends BaseState { 16 | @override 17 | Widget build(BuildContext context) { 18 | return Scaffold( 19 | appBar: AppBar(title: const Text('文本示例')), 20 | body: SingleChildScrollView( 21 | child: Padding( 22 | padding: all(16), 23 | child: Column( 24 | crossAxisAlignment: CrossAxisAlignment.start, 25 | children: [ 26 | const CommonText('我是文本呀'), 27 | sizedBox(height: 10), 28 | const CommonText( 29 | '我是蓝色文本呀', 30 | color: Colors.blue, 31 | fontSize: 15, 32 | ), 33 | sizedBox(height: 10), 34 | CommonText( 35 | '我是单行文本呀' * 10, 36 | color: Colors.pink, 37 | maxLines: 1, 38 | fontSize: 16, 39 | ), 40 | sizedBox(height: 10), 41 | const CommonText( 42 | '我是有下划线文本呀', 43 | color: Colors.cyanAccent, 44 | fontSize: 17, 45 | decoration: TextDecoration.underline, 46 | ), 47 | sizedBox(height: 10), 48 | const CommonText( 49 | '我是有删除线文本呀', 50 | color: Colors.redAccent, 51 | fontSize: 18, 52 | decoration: TextDecoration.lineThrough, 53 | ), 54 | sizedBox(height: 10), 55 | const CommonText( 56 | '我是W700文本呀', 57 | color: Colors.blueGrey, 58 | fontSize: 19, 59 | fontWeight: FontWeight.w700, 60 | ), 61 | sizedBox(height: 10), 62 | const CommonText( 63 | '我是斜体文本呀', 64 | color: Colors.blueGrey, 65 | fontSize: 20, 66 | fontStyle: FontStyle.italic, 67 | ), 68 | sizedBox(height: 10), 69 | const CommonText( 70 | '我是虚线下划线文本呀', 71 | color: Colors.purple, 72 | fontSize: 21, 73 | decorationStyle: TextDecorationStyle.dashed, 74 | decoration: TextDecoration.underline, 75 | ), 76 | sizedBox(height: 10), 77 | CommonText( 78 | '我是有2倍字间距文本呀' * 2, 79 | color: Colors.indigo, 80 | fontSize: 22, 81 | letterSpacing: 2, 82 | ), 83 | sizedBox(height: 10), 84 | CommonText( 85 | '我是最大三行文本呀' * 20, 86 | color: Colors.green, 87 | maxLines: 3, 88 | fontSize: 23, 89 | overflow: TextOverflow.ellipsis, 90 | ), 91 | ], 92 | ), 93 | ), 94 | ), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /example/lib/view/toast_page.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/18 on 14:58 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/todo_flutter.dart'; 8 | 9 | class ToastPage extends StatefulWidget { 10 | const ToastPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | State createState() => _ToastPageState(); 14 | } 15 | 16 | class _ToastPageState extends BaseState { 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: AppBar(title: const Text('Toast示例')), 21 | body: Column( 22 | children: [ 23 | CommonButton( 24 | '弹出Toast', 25 | width: double.infinity, 26 | color: Colors.blue, 27 | radius: 20, 28 | margin: only(left: 20, right: 20, top: 40), 29 | onPressed: () { 30 | showToast('我是Toast'); 31 | }, 32 | ), 33 | CommonButton( 34 | 'Toast居于底部', 35 | width: double.infinity, 36 | color: Colors.blue, 37 | radius: 20, 38 | margin: only(left: 20, right: 20, top: 20), 39 | onPressed: () { 40 | showToast( 41 | '底部Toast', 42 | data: const ToastThemeData(gravity: ToastGravity.BOTTOM), 43 | ); 44 | }, 45 | ), 46 | ], 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: todo_flutter_example 2 | description: 框架示例代码工程项目 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.15.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^1.0.2 31 | extended_nested_scroll_view: ^6.1.2 32 | # 版本更新 33 | flutter_app_update: ^3.2.0 34 | todo_flutter: 35 | path: ../../todo_flutter 36 | 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | flutter_lints: ^2.0.1 41 | 42 | # For information on the generic Dart part of this file, see the 43 | # following page: https://dart.dev/tools/pub/pubspec 44 | 45 | # The following section is specific to Flutter. 46 | flutter: 47 | 48 | # The following line ensures that the Material Icons font is 49 | # included with your application, so that you can use the icons in 50 | # the material Icons class. 51 | uses-material-design: true 52 | 53 | # To add assets to your application, add an assets section, like this: 54 | assets: 55 | - assets/images/ 56 | fonts: 57 | - family: todoIcon 58 | fonts: 59 | - asset: assets/iconfont/iconfont.ttf 60 | 61 | # An image asset can refer to one or more resolution-specific "variants", see 62 | # https://flutter.dev/assets-and-images/#resolution-aware. 63 | 64 | # For details regarding adding assets from package dependencies, see 65 | # https://flutter.dev/assets-and-images/#from-packages 66 | 67 | # To add custom fonts to your application, add a fonts section here, 68 | # in this "flutter" section. Each entry in this list should have a 69 | # "family" key with the font family name, and a "fonts" key with a 70 | # list giving the asset and other descriptors for the font. For 71 | # example: 72 | # fonts: 73 | # - family: Schyler 74 | # fonts: 75 | # - asset: fonts/Schyler-Regular.ttf 76 | # - asset: fonts/Schyler-Italic.ttf 77 | # style: italic 78 | # - family: Trajan Pro 79 | # fonts: 80 | # - asset: fonts/TrajanPro.ttf 81 | # - asset: fonts/TrajanPro_Bold.ttf 82 | # weight: 700 83 | # 84 | # For details regarding fonts from package dependencies, 85 | # see https://flutter.dev/custom-fonts/#from-packages 86 | flutter_res: 87 | # 当前模块是否是子模块,对生成的图片资源路径有影响 88 | isModule: false 89 | prefix: example -------------------------------------------------------------------------------- /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 | void main() {} 9 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/todo_flutter/7672c91fb7a0cfb38ec23ce32abaddc6fa980bfd/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | test_web 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test_web", 3 | "short_name": "test_web", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /lib/generated/assets/todo_flutter_assets.dart: -------------------------------------------------------------------------------- 1 | ///This file is automatically generated by the [FlutterResource], your modifications will be lost. 2 | class TodoFlutterAssets { 3 | static const icEmpty = 'packages/todo_flutter/assets/images/ic_empty.png'; 4 | static const icError = 'packages/todo_flutter/assets/images/ic_error.png'; 5 | static const icImageError = 'packages/todo_flutter/assets/images/ic_image_error.png'; 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/I10n/text_delegate.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2024/1/30 on 13:34 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | class TextDelegate { 6 | const TextDelegate(); 7 | 8 | String get languageCode => 'zh'; 9 | 10 | String get dialogConfirm => '确定'; 11 | 12 | String get dialogCancel => '取消'; 13 | 14 | String get loading => '加载中...'; 15 | 16 | String get empty => '暂无数据'; 17 | 18 | String get pleaseTryAgain => '请重试'; 19 | 20 | String get placeholder => '请输入'; 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/I10n/text_delegate_en.dart: -------------------------------------------------------------------------------- 1 | import 'package:todo_flutter/src/I10n/text_delegate.dart'; 2 | 3 | /// createTime: 2024/1/30 on 13:34 4 | /// desc: 5 | /// 6 | /// @author azhon 7 | class TextDelegateEn extends TextDelegate { 8 | const TextDelegateEn(); 9 | 10 | @override 11 | String get languageCode => 'en'; 12 | 13 | @override 14 | String get dialogConfirm => 'Confirm'; 15 | 16 | @override 17 | String get dialogCancel => 'Cancel'; 18 | 19 | @override 20 | String get loading => 'Loading...'; 21 | 22 | @override 23 | String get empty => 'Empty'; 24 | 25 | @override 26 | String get pleaseTryAgain => 'Please try again'; 27 | 28 | @override 29 | String get placeholder => 'Please input content'; 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/base/base_state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:todo_flutter/src/base/bloc/base_bloc.dart'; 4 | import 'package:todo_flutter/src/base/loading_state.dart'; 5 | import 'package:todo_flutter/src/base/ui_adapter.dart'; 6 | import 'package:todo_flutter/src/base/ui_widget.dart'; 7 | import 'package:flutter_bloc/flutter_bloc.dart'; 8 | import 'package:todo_flutter/src/service/event/global_event_manager.dart'; 9 | 10 | /// createTime: 2021/9/17 on 21:18 11 | /// desc: 12 | /// 13 | /// @author azhon 14 | 15 | ///UI 16 | abstract class BaseUIState extends State 17 | with UIAdapter, UIWidget {} 18 | 19 | ///bloc 20 | abstract class BaseBlocState extends BaseUIState 21 | with LoadingState { 22 | List? _blocs; 23 | 24 | ///添加bloc进行管理 25 | void addBloc(BaseBloc bloc) { 26 | _blocs ??= []; 27 | _blocs!.add(bloc); 28 | bloc.setState(this); 29 | } 30 | 31 | ///获取bloc进行管理 32 | B getBloc() { 33 | final list = _blocs 34 | ?.where((element) => element.runtimeType.toString() == B.toString()) 35 | .toList(); 36 | if (list == null || list.isEmpty) { 37 | throw Exception('please use addBloc($B()) first...'); 38 | } 39 | return list.first as B; 40 | } 41 | 42 | @override 43 | void sendEventToView(String type, [data]) {} 44 | 45 | @override 46 | BuildContext get buildContext => context; 47 | 48 | @override 49 | void dispose() { 50 | super.dispose(); 51 | 52 | ///组件销毁,释放bloc 53 | _blocs?.forEach((element) { 54 | element.close(); 55 | }); 56 | _blocs?.clear(); 57 | } 58 | } 59 | 60 | ///global event 61 | abstract class BaseGlobalEventState 62 | extends BaseBlocState { 63 | StreamSubscription? _streamSubscription; 64 | 65 | ///only one listener 66 | ///[type] Specify event type 67 | void listenerGlobalEvent({ 68 | required GlobalEventCallBack callBack, 69 | List? type, 70 | }) { 71 | _cancel(); 72 | _streamSubscription = GlobalEventManager.instance.subscribe((global) { 73 | if (type == null) { 74 | callBack.call(global.type, global.data); 75 | } else { 76 | if (type.contains(global.type)) { 77 | callBack.call(global.type, global.data); 78 | } 79 | } 80 | }); 81 | } 82 | 83 | void _cancel() { 84 | if (_streamSubscription != null) { 85 | _streamSubscription?.cancel(); 86 | _streamSubscription = null; 87 | } 88 | } 89 | 90 | @override 91 | void dispose() { 92 | super.dispose(); 93 | _cancel(); 94 | } 95 | } 96 | 97 | abstract class BaseState 98 | extends BaseGlobalEventState {} 99 | -------------------------------------------------------------------------------- /lib/src/base/base_stateful_widget.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/17 on 21:22 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/src/base/ui_adapter.dart'; 8 | 9 | abstract class BaseStatefulWidget extends StatefulWidget with UIAdapter { 10 | const BaseStatefulWidget({Key? key}) : super(key: key); 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/base/base_stateless_widget.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/17 on 17:45 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/src/base/ui_adapter.dart'; 8 | import 'package:todo_flutter/src/base/ui_widget.dart'; 9 | 10 | abstract class BaseStatelessWidget extends StatelessWidget 11 | with UIAdapter, UIWidget { 12 | const BaseStatelessWidget({Key? key}) : super(key: key); 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/base/bloc/base_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:todo_flutter/src/base/bloc/base_event.dart'; 3 | import 'package:todo_flutter/src/base/loading_state.dart'; 4 | import 'package:todo_flutter/src/bloc/load/load_bloc.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:todo_flutter/src/service/error/domain_exception.dart'; 7 | 8 | /// createTime: 2021/9/29 on 16:49 9 | /// desc: 10 | /// 11 | /// @author azhon 12 | 13 | abstract class BaseLoadBloc extends Bloc { 14 | BaseLoadBloc(S initialState) : super(initialState) { 15 | _init(); 16 | } 17 | 18 | @override 19 | void add(E event) { 20 | if (isClosed) { 21 | return; 22 | } 23 | super.add(event); 24 | } 25 | 26 | void _init() { 27 | ///分发至event处理 28 | on((E event, Emitter emit) async { 29 | final S? resultState = await event.on(this, state); 30 | if (resultState != null) { 31 | emit.call(resultState); 32 | } 33 | onStateChange(resultState); 34 | }); 35 | } 36 | 37 | ///状态变更 38 | void onStateChange(S? state) {} 39 | } 40 | 41 | abstract class BaseBloc extends BaseLoadBloc { 42 | LoadingState? _loadingState; 43 | final LoadBloc _loadBloc = LoadBloc(); 44 | 45 | LoadBloc get loadBloc => _loadBloc; 46 | 47 | Future setState(LoadingState state) async { 48 | _loadingState = state; 49 | } 50 | 51 | BaseBloc(S initialState) : super(initialState); 52 | 53 | ///view层接受bloc层事件 54 | void sendEventToView(String type, [data]) { 55 | if (_loadingState == null) { 56 | throw Exception('Please use [BaseState.addBloc()] first...'); 57 | } 58 | _loadingState!.sendEventToView(type, data); 59 | } 60 | 61 | BuildContext get context { 62 | if (_loadingState == null) { 63 | throw Exception('Please use [BaseState.addBloc()] first...'); 64 | } 65 | return _loadingState!.buildContext; 66 | } 67 | 68 | ///配合BlocLoadWidget使用,开始加载 69 | void loading() { 70 | loadBloc.loading(); 71 | } 72 | 73 | ///配合BlocLoadWidget使用,加载完成 74 | void loadDone() { 75 | loadBloc.loadDone(); 76 | } 77 | 78 | ///配合BlocLoadWidget使用,加载失败 79 | void loadError(DomainException exception) { 80 | loadBloc.loadError(exception); 81 | } 82 | 83 | ///是否在加载中 84 | bool isLoading() { 85 | return loadBloc.isLoading; 86 | } 87 | 88 | @override 89 | Future close() async { 90 | await loadBloc.close(); 91 | await super.close(); 92 | _loadingState = null; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/src/base/bloc/base_event.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/29 on 16:49 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | abstract class BaseEvent { 7 | ///event transform to state 8 | ///[bloc] 事件的Bloc 9 | ///[currentState] 当前的状态 10 | Future on(B bloc, S currentState); 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/base/loading_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// createTime: 2021/10/19 on 17:15 4 | /// desc: 5 | /// 6 | /// @author azhon 7 | 8 | mixin LoadingState { 9 | ///获取[BuildContext] 10 | BuildContext get buildContext; 11 | 12 | ///view层接受bloc层事件 13 | ///[type]事件类型 14 | ///[data]事件携带数据 15 | void sendEventToView(String type, [data]); 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/base/ui_adapter.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/17 on 18:53 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 6 | 7 | mixin UIAdapter { 8 | /// 屏幕 宽 9 | double get screenWidth => ScreenUtil().screenWidth; 10 | 11 | /// 屏幕 高 12 | double get screenHeight => ScreenUtil().screenHeight; 13 | 14 | ///状态栏高度 15 | double get statusBarHeight => ScreenUtil().statusBarHeight; 16 | 17 | double setFontSize(double size) { 18 | return size.sp; 19 | } 20 | 21 | ///see [ScreenUtil.setWidth] 22 | double setWidth(double width) { 23 | return width.w; 24 | } 25 | 26 | ///see [ScreenUtil.setHeight] 27 | double setHeight(double height) { 28 | return height.h; 29 | } 30 | 31 | double setRadius(double radius) { 32 | return radius.w; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/base/ui_widget.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/17 on 21:43 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | import 'package:flutter/material.dart'; 6 | import 'package:todo_flutter/src/base/ui_adapter.dart'; 7 | 8 | mixin UIWidget implements UIAdapter { 9 | EdgeInsets all(double value) { 10 | return EdgeInsets.all(setWidth(value)); 11 | } 12 | 13 | EdgeInsets symmetric({double vertical = 0.0, double horizontal = 0.0}) { 14 | return EdgeInsets.symmetric( 15 | vertical: setWidth(vertical), 16 | horizontal: setWidth(horizontal), 17 | ); 18 | } 19 | 20 | EdgeInsets only({ 21 | double left = 0.0, 22 | double top = 0.0, 23 | double right = 0.0, 24 | double bottom = 0.0, 25 | }) { 26 | return EdgeInsets.only( 27 | left: setWidth(left), 28 | top: setWidth(top), 29 | right: setWidth(right), 30 | bottom: setWidth(bottom), 31 | ); 32 | } 33 | 34 | SizedBox sizedBox({ 35 | double? width, 36 | double? height, 37 | Widget? child, 38 | }) { 39 | return SizedBox( 40 | width: width == null ? null : setWidth(width), 41 | height: height == null ? null : setWidth(height), 42 | child: child, 43 | ); 44 | } 45 | 46 | BorderRadius circular(double radius) { 47 | return BorderRadius.circular(setRadius(radius)); 48 | } 49 | 50 | BorderRadius radiusOnly({ 51 | double topLeft = 0, 52 | double topRight = 0, 53 | double bottomLeft = 0, 54 | double bottomRight = 0, 55 | }) { 56 | return BorderRadius.only( 57 | topLeft: Radius.circular(setRadius(topLeft)), 58 | topRight: Radius.circular(setRadius(topRight)), 59 | bottomLeft: Radius.circular(setRadius(bottomLeft)), 60 | bottomRight: Radius.circular(setRadius(bottomRight)), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/bloc/data/data_change_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | import 'package:todo_flutter/src/bloc/data/data_change_state.dart'; 3 | 4 | /// createTime: 2021/9/30 on 10:47 5 | /// desc: 6 | /// 7 | /// @author azhon 8 | class DataChangeBloc extends Cubit> { 9 | DataChangeBloc(T? data) : super(DataChangeState(data)); 10 | 11 | void changeData(T? data) { 12 | if (isClosed) { 13 | return; 14 | } 15 | emit(DataChangeState(data)); 16 | } 17 | 18 | void update() { 19 | changeData(state.data); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/bloc/data/data_change_state.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2023/10/19 on 14:00 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | class DataChangeState { 6 | final T? data; 7 | 8 | DataChangeState(this.data); 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/bloc/list/list_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:todo_flutter/src/base/bloc/base_bloc.dart'; 2 | import 'package:todo_flutter/src/bloc/list/list_event.dart'; 3 | import 'package:todo_flutter/src/bloc/list/list_state.dart'; 4 | import 'package:todo_flutter/src/net/base_request.dart'; 5 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 6 | 7 | class ListBloc extends BaseBloc, ListState> { 8 | ///当前页码 9 | late int pageNum; 10 | 11 | ///初始页码 12 | final int startPageNum; 13 | 14 | ///分页一页数量 15 | final int pageSize; 16 | BaseRequest> request; 17 | final RefreshController controller = RefreshController(); 18 | 19 | ListBloc({ 20 | required this.request, 21 | this.pageSize = 15, 22 | this.startPageNum = 1, 23 | }) : super(InitialState([], null)) { 24 | this.pageNum = startPageNum; 25 | } 26 | 27 | void init() { 28 | pageNum = startPageNum; 29 | _changeParams(); 30 | add(InitEvent()); 31 | } 32 | 33 | void refresh() { 34 | pageNum = startPageNum; 35 | _changeParams(); 36 | add(RefreshEvent()); 37 | } 38 | 39 | void loadMore() { 40 | pageNum++; 41 | _changeParams(); 42 | add(LoadMoreEvent()); 43 | } 44 | 45 | void loadMoreError() { 46 | pageNum--; 47 | _changeParams(); 48 | } 49 | 50 | // ignore: use_setters_to_change_properties 51 | void resetRequest(BaseRequest> request) { 52 | this.request = request; 53 | pageNum = startPageNum; 54 | _changeParams(); 55 | add(InitEvent()); 56 | } 57 | 58 | void updateState(List list) { 59 | add(UpdateEvent(list)); 60 | } 61 | 62 | void _changeParams() { 63 | request.params ??= {}; 64 | 65 | ///处理页码存在url path上 66 | if (request.url.contains(request.page)) { 67 | final realUrl = request.url.replaceAll(request.page, '$pageNum'); 68 | request.params![request.pagingUrlKey] = realUrl; 69 | } else { 70 | request.params![request.pageKey] = pageNum; 71 | } 72 | request.params![request.pageSizeKey] = pageSize; 73 | } 74 | 75 | @override 76 | Future close() async { 77 | controller.dispose(); 78 | await super.close(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/bloc/list/list_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:todo_flutter/src/base/bloc/base_event.dart'; 2 | import 'package:todo_flutter/src/bloc/list/list_bloc.dart'; 3 | import 'package:todo_flutter/src/bloc/list/list_state.dart'; 4 | import 'package:todo_flutter/src/net/entity/base_entity.dart'; 5 | import 'package:todo_flutter/src/service/error/domain_exception.dart'; 6 | 7 | abstract class ListEvent extends BaseEvent, ListState> {} 8 | 9 | class InitEvent extends ListEvent { 10 | @override 11 | Future> on(ListBloc bloc, ListState currentState) async { 12 | bloc.loading(); 13 | final bean = await bloc.request.request(); 14 | if (bean.code == BaseEntity.defaultCode) { 15 | bloc.loadError(NetworkException(bean)); 16 | return InitialState([], bean); 17 | } 18 | final list = bean.data ?? []; 19 | bloc.loadDone(); 20 | if (bean.curPage == bean.totalPage) { 21 | bloc.controller.loadNoData(); 22 | } else { 23 | bloc.controller.resetNoData(); 24 | } 25 | return InitialState(list, bean); 26 | } 27 | } 28 | 29 | class RefreshEvent extends ListEvent { 30 | @override 31 | Future> on(ListBloc bloc, ListState currentState) async { 32 | final bean = await bloc.request.request(); 33 | if (bean.code == BaseEntity.defaultCode) { 34 | bloc.loadError(NetworkException(bean)); 35 | return InitialState([], bean); 36 | } 37 | final list = bean.data ?? []; 38 | bloc.controller.refreshCompleted(); 39 | if (bean.curPage == bean.totalPage) { 40 | bloc.controller.loadNoData(); 41 | } else { 42 | bloc.controller.resetNoData(); 43 | } 44 | return InitialState(list, bean); 45 | } 46 | } 47 | 48 | class LoadMoreEvent extends ListEvent { 49 | @override 50 | Future> on(ListBloc bloc, ListState currentState) async { 51 | final bean = await bloc.request.request(); 52 | if (bean.code == BaseEntity.defaultCode) { 53 | bloc.controller.loadFailed(); 54 | bloc.loadMoreError(); 55 | return currentState; 56 | } 57 | final list = bean.data ?? []; 58 | currentState.data.addAll(list); 59 | if (bean.curPage == bean.totalPage) { 60 | bloc.controller.loadNoData(); 61 | } else { 62 | bloc.controller.loadComplete(); 63 | } 64 | return InitialState(currentState.data, bean); 65 | } 66 | } 67 | 68 | class UpdateEvent extends ListEvent { 69 | final List list; 70 | 71 | UpdateEvent(this.list); 72 | 73 | @override 74 | Future> on(ListBloc bloc, ListState currentState) async { 75 | return InitialState(list, currentState.entity); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/src/bloc/list/list_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:todo_flutter/src/net/entity/base_entity.dart'; 2 | 3 | abstract class ListState { 4 | List data; 5 | BaseEntity? entity; 6 | 7 | ListState(this.data, this.entity); 8 | } 9 | 10 | class InitialState extends ListState { 11 | InitialState(List data, BaseEntity? entity) : super(data, entity); 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/bloc/load/load_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:todo_flutter/src/base/bloc/base_bloc.dart'; 2 | import 'package:todo_flutter/src/bloc/load/load_event.dart'; 3 | import 'package:todo_flutter/src/bloc/load/load_state.dart'; 4 | import 'package:todo_flutter/src/service/error/domain_exception.dart'; 5 | 6 | /// createTime: 2021/10/21 on 14:55 7 | /// desc: 8 | /// 9 | /// @author azhon 10 | class LoadBloc extends BaseLoadBloc { 11 | bool _loading = false; 12 | 13 | LoadBloc() : super(InitialState()); 14 | 15 | void loading() { 16 | _loading = true; 17 | add(LoadingEvent()); 18 | } 19 | 20 | void loadDone() { 21 | _loading = false; 22 | add(InitialEvent()); 23 | } 24 | 25 | void loadError(DomainException exception) { 26 | _loading = false; 27 | add(ErrorEvent(exception)); 28 | } 29 | 30 | bool get isLoading => _loading; 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/bloc/load/load_event.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/21 on 14:56 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:todo_flutter/src/base/bloc/base_event.dart'; 7 | import 'package:todo_flutter/src/bloc/load/load_bloc.dart'; 8 | import 'package:todo_flutter/src/bloc/load/load_state.dart'; 9 | import 'package:todo_flutter/src/service/error/domain_exception.dart'; 10 | 11 | abstract class LoadEvent extends BaseEvent {} 12 | 13 | class InitialEvent extends LoadEvent { 14 | @override 15 | Future on(LoadBloc bloc, LoadState currentState) async { 16 | return InitialState(); 17 | } 18 | } 19 | 20 | class LoadingEvent extends LoadEvent { 21 | @override 22 | Future on(LoadBloc bloc, LoadState currentState) async { 23 | return LoadingState(); 24 | } 25 | } 26 | 27 | class ErrorEvent extends LoadEvent { 28 | final DomainException exception; 29 | 30 | ErrorEvent(this.exception); 31 | 32 | @override 33 | Future on(LoadBloc bloc, LoadState currentState) async { 34 | return ErrorState(exception); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/bloc/load/load_state.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/21 on 14:57 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:todo_flutter/src/service/error/domain_exception.dart'; 7 | 8 | abstract class LoadState {} 9 | 10 | class InitialState extends LoadState {} 11 | 12 | class LoadingState extends LoadState {} 13 | 14 | class ErrorState extends LoadState { 15 | final DomainException exception; 16 | 17 | ErrorState(this.exception); 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/net/base_convert.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/15 on 14:22 2 | /// desc: 数据转换器 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:todo_flutter/src/net/base_net_engine.dart'; 7 | import 'package:todo_flutter/src/net/entity/base_entity.dart'; 8 | 9 | abstract class BaseConvert { 10 | ///[result]中的data json 数据转成对应实体类 11 | BaseEntity convert(Result result); 12 | 13 | ///是否命中此转换器 14 | bool isHit(Result result) => true; 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/net/base_net_engine.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/14 on 11:48 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | class Result { 7 | dynamic data; 8 | String? url; 9 | int? statusCode; 10 | String? statusMessage; 11 | 12 | Result([this.data, this.statusCode, this.statusMessage, this.url]); 13 | } 14 | 15 | abstract class BaseNetEngine { 16 | final String baseUrl; 17 | 18 | BaseNetEngine(this.baseUrl) 19 | : assert(baseUrl.endsWith('/'), 'baseUrl must be end with /'); 20 | 21 | Future get(String url, {Map? params}); 22 | 23 | Future post(String url, {Map? params}); 24 | 25 | Future postJson(String url, {Map? params}); 26 | 27 | Future del(String url, {Map? params}); 28 | 29 | Future put(String url, {Map? params}); 30 | 31 | ///设置连接超时时间 32 | ///[timeout]超时时间 33 | void setConnectTimeout(Duration timeout); 34 | 35 | ///设置接收超时时间 36 | ///[timeout]超时时间 37 | void setReceiveTimeout(Duration timeout); 38 | 39 | ///设置dio代理 40 | ///[ip]代理ip地址 41 | ///[port]代理端口 42 | void setProxy(String ip, int port); 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/net/base_net_provider.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/14 on 14:47 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:todo_flutter/src/net/base_convert.dart'; 7 | import 'package:todo_flutter/src/net/base_net_engine.dart'; 8 | 9 | abstract class BaseNetProvider { 10 | ///创建网络请求客户端 11 | BaseNetEngine get engine; 12 | 13 | ///网络数据转换器 14 | BaseConvert get convert; 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/net/base_request.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/15 on 10:56 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'dart:convert'; 7 | 8 | import 'package:dio/dio.dart'; 9 | import 'package:todo_flutter/src/net/base_convert.dart'; 10 | import 'package:todo_flutter/src/net/base_net_engine.dart'; 11 | import 'package:todo_flutter/src/net/base_net_provider.dart'; 12 | import 'package:todo_flutter/src/net/entity/base_entity.dart'; 13 | import 'package:todo_flutter/src/util/log_util.dart'; 14 | 15 | enum RequestMethod { 16 | get, 17 | post, 18 | postJson, 19 | del, 20 | put, 21 | } 22 | 23 | mixin Paging { 24 | ///用于处理分页参数存在url path上,动态改变Url 25 | final String pagingUrlKey = 'pagingUrlKey'; 26 | 27 | ///用于处理分页参数存在url path上 28 | final String page = '{page}'; 29 | 30 | ///分页 页码字段值 31 | String get pageKey; 32 | 33 | ///分页 一页数量字段值 34 | String get pageSizeKey; 35 | } 36 | 37 | abstract class BaseRequest with Paging { 38 | Map? params; 39 | 40 | BaseRequest(this.params); 41 | 42 | String get url; 43 | 44 | RequestMethod get method => RequestMethod.get; 45 | 46 | BaseNetProvider get netProvider; 47 | 48 | String get _realUrl { 49 | final temp = params?[pagingUrlKey] ?? url; 50 | params?.remove(pagingUrlKey); 51 | return temp; 52 | } 53 | 54 | ///请求数据 55 | Future> request() async { 56 | final BaseNetEngine engine = netProvider.engine; 57 | final BaseConvert convert = netProvider.convert; 58 | Result result = Result(); 59 | final url = _realUrl; 60 | final logUrl = _printLog(engine); 61 | try { 62 | switch (method) { 63 | case RequestMethod.get: 64 | result = await engine.get(url, params: params); 65 | break; 66 | case RequestMethod.post: 67 | result = await engine.post(url, params: params); 68 | break; 69 | case RequestMethod.postJson: 70 | result = await engine.postJson(url, params: params); 71 | break; 72 | case RequestMethod.del: 73 | result = await engine.del(url, params: params); 74 | break; 75 | case RequestMethod.put: 76 | result = await engine.put(url, params: params); 77 | break; 78 | } 79 | } catch (e) { 80 | _parseError(result, e); 81 | LogUtil.d('BaseRequest:[request error] $logUrl\n${result.statusMessage}'); 82 | } 83 | return convert.convert(result); 84 | } 85 | 86 | String _printLog(BaseNetEngine engine) { 87 | String paramsStr = params.toString(); 88 | try { 89 | paramsStr = jsonEncode(params); 90 | } catch (e) { 91 | ///ignore 92 | } 93 | final logUrl = 'url:${engine.baseUrl}$url\nparams:$paramsStr'; 94 | LogUtil.d('BaseRequest:[request start]\n$logUrl\nmethod:$method'); 95 | return logUrl; 96 | } 97 | 98 | ///获取异常信息 99 | void _parseError(Result result, e) { 100 | if (e is DioException) { 101 | if (e.response != null) { 102 | result.statusCode = e.response!.statusCode; 103 | result.statusMessage = e.response!.statusMessage; 104 | } else { 105 | result.statusMessage = e.message ?? ''; 106 | } 107 | } else { 108 | result.statusMessage = e.toString(); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/src/net/dio_engine.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/14 on 13:59 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'dart:io'; 7 | import 'package:dio/dio.dart'; 8 | import 'package:dio/io.dart'; 9 | import 'package:todo_flutter/src/net/base_net_engine.dart'; 10 | 11 | class DioEngine extends BaseNetEngine { 12 | late Dio _dio; 13 | 14 | DioEngine(String baseUrl) : super(baseUrl) { 15 | _dio = Dio(); 16 | _dio.options = BaseOptions( 17 | baseUrl: baseUrl, 18 | connectTimeout: const Duration(seconds: 30), 19 | receiveTimeout: const Duration(seconds: 30), 20 | sendTimeout: const Duration(seconds: 30), 21 | ); 22 | } 23 | 24 | @override 25 | Future get(String url, {Map? params}) async { 26 | final Response response = await _dio.get(url, queryParameters: params); 27 | return Result( 28 | response.data, 29 | response.statusCode, 30 | response.statusMessage, 31 | url, 32 | ); 33 | } 34 | 35 | @override 36 | Future post(String url, {Map? params}) async { 37 | final Response response = 38 | await _dio.post(url, data: FormData.fromMap(params ?? {})); 39 | return Result( 40 | response.data, 41 | response.statusCode, 42 | response.statusMessage, 43 | url, 44 | ); 45 | } 46 | 47 | @override 48 | Future postJson(String url, {Map? params}) async { 49 | final Response response = await _dio.post(url, data: params); 50 | return Result( 51 | response.data, 52 | response.statusCode, 53 | response.statusMessage, 54 | url, 55 | ); 56 | } 57 | 58 | @override 59 | Future del(String url, {Map? params}) async { 60 | final Response response = await _dio.delete(url, data: params); 61 | return Result( 62 | response.data, 63 | response.statusCode, 64 | response.statusMessage, 65 | url, 66 | ); 67 | } 68 | 69 | @override 70 | Future put(String url, {Map? params}) async { 71 | final Response response = await _dio.put(url, data: params); 72 | return Result( 73 | response.data, 74 | response.statusCode, 75 | response.statusMessage, 76 | url, 77 | ); 78 | } 79 | 80 | ///设置连接超时时间 81 | ///[timeout]超时时间ms 82 | @override 83 | void setConnectTimeout(Duration timeout) { 84 | _dio.options.connectTimeout = timeout; 85 | } 86 | 87 | ///设置接收超时时间 88 | ///[timeout]超时时间ms 89 | @override 90 | void setReceiveTimeout(Duration timeout) { 91 | _dio.options.receiveTimeout = timeout; 92 | } 93 | 94 | ///设置dio代理 95 | ///[ip]代理ip地址 96 | ///[port]代理端口 97 | @override 98 | void setProxy(String ip, int port) { 99 | _dio.httpClientAdapter = IOHttpClientAdapter( 100 | createHttpClient: () { 101 | final client = HttpClient(); 102 | client.findProxy = (uri) { 103 | return 'PROXY $ip:$port'; 104 | }; 105 | client.badCertificateCallback = (cert, host, port) => true; 106 | return client; 107 | }, 108 | ); 109 | } 110 | 111 | ///添加拦截器 112 | void addInterceptor(Interceptor interceptor) { 113 | _dio.interceptors.add(interceptor); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/src/net/entity/base_entity.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/15 on 14:15 2 | /// desc: 请求接口基础类 3 | /// 4 | /// @author azhon 5 | 6 | class BaseEntity { 7 | static const defaultCode = -1; 8 | 9 | T? data; 10 | int code = defaultCode; 11 | String? message; 12 | String? traceId; 13 | 14 | ///分页接口 15 | int? curPage; 16 | int? total; 17 | int? totalPage; 18 | 19 | BaseEntity({ 20 | required this.code, 21 | this.data, 22 | this.message, 23 | this.curPage, 24 | this.total, 25 | this.totalPage, 26 | this.traceId, 27 | }); 28 | 29 | @override 30 | String toString() { 31 | return ''' 32 | BaseEntity { 33 | code: $code, 34 | message: $message, 35 | traceId: $traceId, 36 | data: $data, 37 | data: $data, 38 | curPage: $curPage, 39 | total: $total, 40 | } 41 | '''; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/service/env/env.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2022/8/26 on 11:48 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | class Env { 6 | final String name; 7 | 8 | const Env._(this.name); 9 | 10 | static const Env dev = Env._('dev'); 11 | static const Env test = Env._('test'); 12 | static const Env pre = Env._('pre'); 13 | static const Env release = Env._('release'); 14 | static const List values = [dev, test, pre, release]; 15 | 16 | static Env env = Env.release; 17 | 18 | static bool get isDev => env.name == Env.dev.name; 19 | 20 | static bool get isTest => env.name == Env.test.name; 21 | 22 | static bool get isPre => env.name == Env.pre.name; 23 | 24 | static bool get isRelease => env.name == Env.release.name; 25 | 26 | static Env fromEnv(String env) { 27 | return Env._(env); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/service/error/domain_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:todo_flutter/src/net/entity/base_entity.dart'; 2 | 3 | /// createTime: 2021/10/22 on 11:02 4 | /// desc: 网络请求异常 5 | /// 6 | /// @author azhon 7 | 8 | abstract class DomainException implements Exception { 9 | final BaseEntity entity; 10 | 11 | DomainException(this.entity); 12 | 13 | @override 14 | String toString() { 15 | return '$runtimeType: $entity'; 16 | } 17 | } 18 | 19 | ///网络错误异常 20 | class NetworkException extends DomainException { 21 | NetworkException(BaseEntity entity) : super(entity); 22 | } 23 | 24 | ///服务器返回错误 25 | class ApiException extends DomainException { 26 | ApiException(BaseEntity entity) : super(entity); 27 | } 28 | 29 | ///其他未知错误 30 | class UnknownException extends DomainException { 31 | UnknownException(String? message) 32 | : super(BaseEntity(code: BaseEntity.defaultCode, message: message)); 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/service/event/global_event_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | /// createTime: 2023/12/1 on 16:56 6 | /// desc: app global events 7 | /// 8 | /// @author azhon 9 | 10 | typedef GlobalEventCallBack = Function(String type, String? data); 11 | 12 | class GlobalEventManager { 13 | factory GlobalEventManager() => _getInstance(); 14 | 15 | GlobalEventManager._internal(); 16 | 17 | static GlobalEventManager get instance => _getInstance(); 18 | static GlobalEventManager? _instance; 19 | 20 | // ignore: close_sinks 21 | final _stateController = StreamController.broadcast(); 22 | 23 | static GlobalEventManager _getInstance() { 24 | _instance ??= GlobalEventManager._internal(); 25 | return _instance!; 26 | } 27 | 28 | /// 29 | StreamSubscription subscribe(ValueChanged listener) { 30 | return _stateController.stream.listen(listener); 31 | } 32 | 33 | ///notify all listener 34 | void notifyAll(String type, [String? data]) { 35 | _stateController.sink.add(GlobalEvent(type, data)); 36 | } 37 | } 38 | 39 | class GlobalEvent { 40 | final String type; 41 | final String? data; 42 | 43 | GlobalEvent(this.type, [this.data]); 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/service/extension/context_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | /// createTime: 2024/11/18 on 11:22 4 | /// desc: 5 | /// 6 | /// @author azhon 7 | extension BuildContextEx on BuildContext { 8 | /// 9 | double get top => MediaQuery.of(this).padding.top; 10 | 11 | /// 12 | double get bottom => MediaQuery.of(this).padding.bottom; 13 | 14 | /// 15 | Size get screenSize => MediaQuery.of(this).size; 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/service/route/bundle.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_returning_this 2 | /// createTime: 2022/6/27 on 21:04 3 | /// desc: 4 | /// 5 | /// @author azhon 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:todo_flutter/src/service/route/router_util.dart'; 9 | import 'package:todo_flutter/src/service/route/routes.dart'; 10 | 11 | class Bundle { 12 | final String route; 13 | final Map _data = {}; 14 | 15 | Bundle([this.route = '']); 16 | 17 | ///便捷存储 18 | Bundle withAll(String key, value) { 19 | if (value is String) { 20 | withString(key, value); 21 | } 22 | if (value is num) { 23 | withNum(key, value); 24 | } 25 | if (value is bool) { 26 | withBool(key, value); 27 | } 28 | return this; 29 | } 30 | 31 | ///存储字符串 32 | Bundle withString(String key, String? value) { 33 | _data[key] = value; 34 | return this; 35 | } 36 | 37 | ///存储int double 38 | Bundle withNum(String key, num? value) { 39 | _data[key] = value; 40 | return this; 41 | } 42 | 43 | ///存储布尔值 44 | Bundle withBool(String key, bool? value) { 45 | _data[key] = value; 46 | return this; 47 | } 48 | 49 | String? getString(String key) { 50 | return _data[key]; 51 | } 52 | 53 | num? getNum(String key) { 54 | return _data[key]; 55 | } 56 | 57 | bool? getBool(String key) { 58 | return _data[key]; 59 | } 60 | 61 | ///对象[_data]内容拷贝 62 | Bundle copyWith(Bundle? bundle) { 63 | ///清除旧数据 64 | _data.clear(); 65 | if (bundle == null) { 66 | return this; 67 | } 68 | bundle._data.forEach((key, value) { 69 | _data[key] = value; 70 | }); 71 | return this; 72 | } 73 | 74 | String toUri() { 75 | if (_data.isEmpty) { 76 | return ''; 77 | } 78 | String params = '?'; 79 | _data.forEach((key, value) { 80 | params += '$key=${_paramsType(value)}&'; 81 | }); 82 | return params.substring(0, params.length - 1); 83 | } 84 | 85 | ///参数类型标记 86 | String _paramsType(value) { 87 | if (value == null) { 88 | return RouteKeyFlag.flagNull; 89 | } 90 | String flag = ''; 91 | if (value is String) { 92 | flag = RouteKeyFlag.flagString; 93 | } else if (value is int) { 94 | flag = RouteKeyFlag.flagInt; 95 | } else if (value is double) { 96 | flag = RouteKeyFlag.flagDouble; 97 | } else if (value is bool) { 98 | flag = RouteKeyFlag.flagBool; 99 | } 100 | if (value is String) { 101 | ///中文需要uri编码 102 | return '$flag${Uri.encodeComponent(value)}'; 103 | } else { 104 | return '$flag$value'; 105 | } 106 | } 107 | 108 | Future navigate() { 109 | return RouterUtil.instance.navigate(this); 110 | } 111 | 112 | Future navigateReplace() { 113 | return RouterUtil.instance.navigateReplace(this); 114 | } 115 | 116 | Future navigateClear() { 117 | return RouterUtil.instance.navigateClear(this); 118 | } 119 | 120 | Future navigatePopUntil(String untilRoute) { 121 | return RouterUtil.instance.navigatePopUntil(untilRoute, this); 122 | } 123 | 124 | Future navigateResult(ValueChanged result) { 125 | return RouterUtil.instance.navigateResult(result, this); 126 | } 127 | 128 | void popUntil(String untilRoute) { 129 | return RouterUtil.instance.popUntil(untilRoute, this); 130 | } 131 | 132 | void pop() { 133 | return RouterUtil.instance.pop(this); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lib/src/service/route/router_history_stack.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:todo_flutter/src/service/route/bundle.dart'; 5 | import 'package:todo_flutter/src/util/object_util.dart'; 6 | 7 | /// createTime: 2022/8/30 on 11:41 8 | /// desc: 9 | /// 10 | /// @author azhon 11 | class RouterHistoryStack { 12 | final Queue _queue = Queue(); 13 | 14 | factory RouterHistoryStack() => _getInstance(); 15 | 16 | RouterHistoryStack._internal(); 17 | 18 | static RouterHistoryStack get instance => _getInstance(); 19 | static RouterHistoryStack? _instance; 20 | 21 | static RouterHistoryStack _getInstance() { 22 | _instance ??= RouterHistoryStack._internal(); 23 | return _instance!; 24 | } 25 | 26 | ///dialog 默认没有路由名称,需主动设置 27 | void _push(Route route) { 28 | if (ObjectUtil.isNotEmpty(route.settings.name)) { 29 | _queue.add(route); 30 | } 31 | } 32 | 33 | void _replace(Route newRoute, Route oldRoute) { 34 | _pop(oldRoute); 35 | _push(newRoute); 36 | } 37 | 38 | void _pop(Route route) { 39 | _queue.removeWhere((element) => element == route); 40 | } 41 | 42 | ///获取栈底路由 43 | Route get firstRoute => _queue.first; 44 | 45 | ///获取栈底路由名称 46 | String get firstRouteName => firstRoute.settings.name ?? ''; 47 | 48 | ///获取栈底路由参数 49 | Bundle? get firstRouteParams { 50 | final arg = firstRoute.settings.arguments; 51 | if (arg is Bundle) { 52 | return arg; 53 | } 54 | return null; 55 | } 56 | 57 | ///获取栈顶路由 58 | Route get lastRoute => _queue.last; 59 | 60 | ///获取栈顶路由名称 61 | String get lastRouteName => lastRoute.settings.name ?? ''; 62 | 63 | ///获取栈顶路由参数 64 | Bundle? get lastRouteParams { 65 | final arg = lastRoute.settings.arguments; 66 | if (arg is Bundle) { 67 | return arg; 68 | } 69 | return null; 70 | } 71 | 72 | ///栈中是否存在当前[route]路由 73 | bool exist(String route) { 74 | return _queue 75 | .where((element) => (element.settings.name ?? '') == route) 76 | .isNotEmpty; 77 | } 78 | 79 | ///获取当前[route]路由存在多少个 80 | int count(String route) { 81 | return _queue 82 | .where((element) => (element.settings.name ?? '') == route) 83 | .length; 84 | } 85 | 86 | ///根据[route]路由名称,获取对应路由;同时从历史栈中删除 87 | Route? get(String route) { 88 | final list = 89 | _queue.where((element) => (element.settings.name ?? '') == route); 90 | if (list.isNotEmpty) { 91 | final r = list.first; 92 | _pop(r); 93 | return r; 94 | } 95 | return null; 96 | } 97 | 98 | @override 99 | String toString() { 100 | int line = 1; 101 | String result = ''; 102 | _queue.forEach((element) { 103 | final settings = element.settings; 104 | result += '${'-' * line}>RouteSettings("${settings.name}", ' 105 | '${settings.arguments})\n'; 106 | line++; 107 | }); 108 | return result; 109 | } 110 | } 111 | 112 | class RouterHistoryObserver extends NavigatorObserver { 113 | @override 114 | void didPush(Route route, Route? previousRoute) { 115 | super.didPush(route, previousRoute); 116 | RouterHistoryStack.instance._push(route); 117 | } 118 | 119 | @override 120 | void didReplace({Route? newRoute, Route? oldRoute}) { 121 | super.didReplace(newRoute: newRoute, oldRoute: oldRoute); 122 | RouterHistoryStack.instance._replace(newRoute!, oldRoute!); 123 | } 124 | 125 | @override 126 | void didPop(Route route, Route? previousRoute) { 127 | super.didPop(route, previousRoute); 128 | RouterHistoryStack.instance._pop(route); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/src/service/route/router_util.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/17 on 17:58 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'dart:io'; 7 | 8 | import 'package:fluro/fluro.dart'; 9 | import 'package:flutter/foundation.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:todo_flutter/src/service/route/bundle.dart'; 12 | import 'package:todo_flutter/src/service/route/router_history_stack.dart'; 13 | import 'package:todo_flutter/src/service/route/routes.dart'; 14 | import 'package:todo_flutter/todo_lib.dart'; 15 | 16 | typedef RouterInterceptor = String? Function(String route); 17 | 18 | class RouterUtil { 19 | factory RouterUtil() => _getInstance(); 20 | 21 | static RouterUtil get instance => _getInstance(); 22 | static RouterUtil? _instance; 23 | 24 | late FluroRouter _router; 25 | 26 | ///拦截器 27 | RouterInterceptor? routerInterceptor; 28 | 29 | static RouterUtil _getInstance() { 30 | _instance ??= RouterUtil._internal(); 31 | return _instance!; 32 | } 33 | 34 | RouterUtil._internal() { 35 | _init(); 36 | } 37 | 38 | ///初始化 39 | void _init() { 40 | _router = FluroRouter(); 41 | Routes.define(_router); 42 | } 43 | 44 | ///添加路由 45 | ///[route] 路由名称 46 | ///[handler] 路由处理器 47 | void addRoute(String route, RouteHandler handler) { 48 | Routes.addRoute(_router, route, handler); 49 | } 50 | 51 | RouteFactory? generator() { 52 | return _router.generator; 53 | } 54 | 55 | Bundle build([String route = '']) { 56 | return Bundle(route); 57 | } 58 | 59 | ///直接跳转 60 | Future navigate(Bundle bundle) { 61 | return navigateTo(bundle); 62 | } 63 | 64 | ///替换当前路由跳转 65 | Future navigateReplace(Bundle bundle) { 66 | return navigateTo(bundle, replace: true); 67 | } 68 | 69 | ///清除之前所有路由跳转 70 | Future navigateClear(Bundle bundle) { 71 | return navigateTo(bundle, clearStack: true); 72 | } 73 | 74 | ///清除路由为[untilRoute]之上的路由,然后在打开新的路由[bundle]里的route 75 | Future navigatePopUntil(String untilRoute, Bundle bundle) { 76 | popUntil(untilRoute); 77 | return navigate(bundle); 78 | } 79 | 80 | ///直接跳转同时接收页面返回值 81 | Future navigateResult(ValueChanged result, Bundle bundle) { 82 | return navigateTo(bundle).then((value) => result(value ?? Bundle())); 83 | } 84 | 85 | ///返回页面 86 | void pop([Bundle? bundle]) { 87 | Navigator.of(TodoLib.navigatorKey.currentContext!).pop(bundle); 88 | } 89 | 90 | ///返回页面直到路由为[untilRoute]时停止 91 | void popUntil(String untilRoute, [Bundle? bundle]) { 92 | Navigator.of(TodoLib.navigatorKey.currentContext!).popUntil((route) { 93 | if (route.settings.name == untilRoute) { 94 | final arguments = route.settings.arguments; 95 | if (arguments != null && arguments is Bundle) { 96 | arguments.copyWith(bundle); 97 | } 98 | return true; 99 | } 100 | return false; 101 | }); 102 | } 103 | 104 | ///返回到App指定路由,如果不存在则返回到根路由 105 | void popToMain(String route, [Bundle? bundle]) { 106 | if (RouterHistoryStack.instance.exist(route)) { 107 | popUntil(route, bundle); 108 | } else { 109 | popUntil(Navigator.defaultRouteName, bundle); 110 | } 111 | } 112 | 113 | ///移除指定[route]路由名称的路由 114 | bool removeRoute(String route) { 115 | final Route? history = RouterHistoryStack.instance.get(route); 116 | if (history == null) { 117 | return false; 118 | } else { 119 | Navigator.of(TodoLib.navigatorKey.currentContext!).removeRoute(history); 120 | return true; 121 | } 122 | } 123 | 124 | ///跳转 125 | Future navigateTo( 126 | Bundle bundle, { 127 | bool replace = false, 128 | bool clearStack = false, 129 | bool rootNavigator = false, 130 | bool maintainState = true, 131 | TransitionType transition = TransitionType.material, 132 | Duration? transitionDuration, 133 | RouteTransitionsBuilder? transitionBuilder, 134 | }) { 135 | final redirectRoute = routerInterceptor?.call(bundle.route) ?? bundle.route; 136 | 137 | ///arguments: 用于当使用popUntil时携带返回参数 138 | final RouteSettings routeSettings = RouteSettings( 139 | name: redirectRoute, 140 | arguments: bundle, 141 | ); 142 | final route = redirectRoute + bundle.toUri(); 143 | if (!kIsWeb && Platform.isIOS) { 144 | transition = TransitionType.cupertino; 145 | } 146 | return _router.navigateTo( 147 | TodoLib.navigatorKey.currentContext!, 148 | route, 149 | replace: replace, 150 | clearStack: clearStack, 151 | maintainState: maintainState, 152 | rootNavigator: rootNavigator, 153 | transition: transition, 154 | transitionDuration: transitionDuration, 155 | transitionBuilder: transitionBuilder, 156 | routeSettings: routeSettings, 157 | ); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /lib/src/service/route/routes.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/17 on 17:57 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:fluro/fluro.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:todo_flutter/src/ui/common_text.dart'; 9 | 10 | typedef RouteHandler = Widget Function(Map map); 11 | 12 | class RouteKeyFlag { 13 | static const String flagString = 'str>'; 14 | static const String flagInt = 'int>'; 15 | static const String flagDouble = 'double>'; 16 | static const String flagBool = 'bool>'; 17 | static const String flagNull = 'null>'; 18 | } 19 | 20 | class Routes { 21 | ///定义路由 22 | static void define(FluroRouter router) { 23 | router.notFoundHandler = Handler( 24 | handlerFunc: (BuildContext? context, Map> params) { 25 | final settings = context?.settings; 26 | return Scaffold( 27 | body: Center( 28 | child: CommonText('Route:${settings?.name}\nNot Found!'), 29 | ), 30 | ); 31 | }, 32 | ); 33 | } 34 | 35 | static void addRoute(FluroRouter router, String route, RouteHandler handler) { 36 | router.define( 37 | route, 38 | handler: Handler( 39 | handlerFunc: (BuildContext? context, Map> map) { 40 | return handler.call(convertType(map)); 41 | }, 42 | ), 43 | ); 44 | } 45 | 46 | static Map convertType(Map> map) { 47 | final Map resultMap = {}; 48 | map.forEach((key, value) { 49 | value.forEach((element) { 50 | resultMap[key] = _parseType(element); 51 | }); 52 | }); 53 | return resultMap; 54 | } 55 | 56 | ///数据转型 57 | static dynamic _parseType(String value) { 58 | if (value.startsWith(RouteKeyFlag.flagNull)) { 59 | return null; 60 | } 61 | if (value.startsWith(RouteKeyFlag.flagString)) { 62 | return value.replaceAll(RouteKeyFlag.flagString, ''); 63 | } 64 | if (value.startsWith(RouteKeyFlag.flagInt)) { 65 | final intStr = value.replaceAll(RouteKeyFlag.flagInt, ''); 66 | return int.parse(intStr); 67 | } 68 | if (value.startsWith(RouteKeyFlag.flagDouble)) { 69 | final doubleStr = value.replaceAll(RouteKeyFlag.flagDouble, ''); 70 | return double.parse(doubleStr); 71 | } 72 | if (value.startsWith(RouteKeyFlag.flagBool)) { 73 | final boolStr = value.replaceAll(RouteKeyFlag.flagBool, ''); 74 | return boolStr == 'true'; 75 | } 76 | return value; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/service/theme/toast_theme_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | 5 | class ToastThemeData with Diagnosticable { 6 | final double? fontSize; 7 | final Color? backgroundColor; 8 | final Color? textColor; 9 | final Toast toastLength; 10 | final ToastGravity gravity; 11 | 12 | const ToastThemeData({ 13 | this.fontSize, 14 | this.backgroundColor, 15 | this.textColor, 16 | this.toastLength = Toast.LENGTH_SHORT, 17 | this.gravity = ToastGravity.CENTER, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/ui/common_button.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/17 on 17:44 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/src/base/base_stateless_widget.dart'; 8 | import 'package:todo_flutter/todo_lib.dart'; 9 | import 'package:todo_flutter/src/ui/common_text.dart'; 10 | 11 | class CommonButton extends BaseStatelessWidget { 12 | final String text; 13 | final double radius; 14 | final double? width; 15 | final double? height; 16 | final double? fontSize; 17 | final String? fontFamily; 18 | final FontWeight? fontWeight; 19 | final Color? color; 20 | final Color? textColor; 21 | final Color borderColor; 22 | final bool disable; 23 | final double borderWidth; 24 | final EdgeInsets? margin; 25 | final EdgeInsets? padding; 26 | final Gradient? gradient; 27 | final VoidCallback? onPressed; 28 | final VoidCallback? onLongPress; 29 | final List? fontFamilyFallback; 30 | final TextAlign? textAlign; 31 | final TextOverflow? overflow; 32 | 33 | const CommonButton( 34 | this.text, { 35 | Key? key, 36 | this.color, 37 | this.width, 38 | this.height, 39 | this.radius = 0, 40 | this.fontSize, 41 | this.fontFamily, 42 | this.fontWeight, 43 | this.margin, 44 | this.padding, 45 | this.onPressed, 46 | this.onLongPress, 47 | this.gradient, 48 | this.textAlign, 49 | this.overflow, 50 | this.borderWidth = 1, 51 | this.disable = false, 52 | this.fontFamilyFallback, 53 | this.borderColor = Colors.transparent, 54 | this.textColor = Colors.white, 55 | }) : super(key: key); 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | final primaryColor = Theme.of(context).primaryColor; 60 | final double defaultButtonHeight = TodoLib.of(context).buttonHeight; 61 | final defaultFontFamily = TodoLib.of(context).fontFamily; 62 | final defaultFontFamilyFallback = TodoLib.of(context).fontFamilyFallback; 63 | 64 | return IgnorePointer( 65 | ignoring: disable, 66 | child: Container( 67 | width: width == null ? null : setWidth(width!), 68 | height: height == null 69 | ? (padding == null ? setWidth(defaultButtonHeight) : null) 70 | : setWidth(height!), 71 | margin: margin, 72 | decoration: BoxDecoration( 73 | gradient: gradient, 74 | borderRadius: BorderRadius.circular(setRadius(radius)), 75 | ), 76 | child: TextButton( 77 | onPressed: () => onPressed?.call(), 78 | onLongPress: () => onLongPress?.call(), 79 | style: TextButton.styleFrom( 80 | foregroundColor: Colors.black, 81 | backgroundColor: gradient == null ? (color ?? primaryColor) : null, 82 | side: BorderSide(color: borderColor, width: setWidth(borderWidth)), 83 | shape: RoundedRectangleBorder( 84 | borderRadius: BorderRadius.circular(setRadius(radius)), 85 | ), 86 | minimumSize: Size.zero, 87 | padding: padding ?? EdgeInsets.zero, 88 | ), 89 | child: CommonText( 90 | text, 91 | color: textColor, 92 | fontWeight: fontWeight, 93 | fontSize: fontSize, 94 | textAlign: textAlign, 95 | overflow: overflow, 96 | fontFamily: fontFamily ?? defaultFontFamily, 97 | fontFamilyFallback: fontFamilyFallback ?? defaultFontFamilyFallback, 98 | ), 99 | ), 100 | ), 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/src/ui/common_click_widget.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/17 on 21:12 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/src/base/base_state.dart'; 8 | import 'package:todo_flutter/src/base/base_stateful_widget.dart'; 9 | import 'package:todo_flutter/todo_lib.dart'; 10 | 11 | ///点击事件拦截,返回false则中断点击 12 | typedef ClickHandler = Future Function(); 13 | 14 | class CommonClickWidget extends BaseStatefulWidget { 15 | final Widget child; 16 | final ClickHandler? clickHandler; 17 | final GestureTapCallback? onTap; 18 | final GestureTapCallback? singleClick; 19 | final GestureLongPressCallback? onLongPress; 20 | 21 | const CommonClickWidget({ 22 | required this.child, 23 | Key? key, 24 | this.onTap, 25 | this.onLongPress, 26 | this.singleClick, 27 | this.clickHandler, 28 | }) : super(key: key); 29 | 30 | @override 31 | State createState() { 32 | return _CommonClickState(); 33 | } 34 | } 35 | 36 | class _CommonClickState extends BaseState { 37 | int _lastClick = 0; 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return GestureDetector( 42 | behavior: HitTestBehavior.opaque, 43 | child: widget.child, 44 | onLongPress: () => _checkHandler(2), 45 | onTap: () => _interceptClick(TodoLib.of(context).clickInterceptInterval), 46 | ); 47 | } 48 | 49 | ///重复多次点击截流处理 50 | void _interceptClick(int clickInterceptInterval) { 51 | final int nowTime = DateTime.now().millisecondsSinceEpoch; 52 | if ((nowTime - _lastClick).abs() > clickInterceptInterval) { 53 | if (widget.singleClick != null) { 54 | _checkHandler(0); 55 | } else { 56 | _checkHandler(1); 57 | } 58 | _lastClick = nowTime; 59 | } else { 60 | _checkHandler(1); 61 | } 62 | } 63 | 64 | ///检测是否中断 65 | Future _checkHandler(int type) async { 66 | final pass = await widget.clickHandler?.call(); 67 | if (false == pass) { 68 | return; 69 | } 70 | if (type == 0) { 71 | widget.singleClick!.call(); 72 | } else if (type == 1) { 73 | widget.onTap?.call(); 74 | } else if (type == 2) { 75 | widget.onLongPress?.call(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/ui/common_empty_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/generated/assets/todo_flutter_assets.dart'; 3 | import 'package:todo_flutter/src/base/base_stateless_widget.dart'; 4 | import 'package:todo_flutter/src/ui/common_image.dart'; 5 | import 'package:todo_flutter/src/ui/common_text.dart'; 6 | import 'package:todo_flutter/todo_lib.dart'; 7 | 8 | /// createTime: 2023/4/23 on 11:13 9 | /// desc: 10 | /// 11 | /// @author azhon 12 | class CommonEmptyWidget extends BaseStatelessWidget { 13 | final double pt; 14 | 15 | const CommonEmptyWidget({ 16 | Key? key, 17 | this.pt = 112, 18 | }) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Container( 23 | width: double.infinity, 24 | color: Colors.white, 25 | padding: only(top: pt), 26 | child: Column( 27 | children: [ 28 | const CommonImage( 29 | asset: TodoFlutterAssets.icEmpty, 30 | size: 86, 31 | ), 32 | Padding( 33 | padding: only(top: 4), 34 | child: CommonText( 35 | TodoLib.delegate(context).empty, 36 | fontSize: 12, 37 | color: const Color(0xFF161619), 38 | fontWeight: FontWeight.w300, 39 | ), 40 | ), 41 | ], 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/ui/common_error_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/generated/assets/todo_flutter_assets.dart'; 3 | import 'package:todo_flutter/todo_flutter.dart'; 4 | 5 | /// createTime: 2023/4/23 on 11:13 6 | /// desc: 7 | /// 8 | /// @author azhon 9 | class CommonErrorWidget extends BaseStatelessWidget { 10 | final VoidCallback? onPressed; 11 | 12 | const CommonErrorWidget({this.onPressed, Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | width: double.infinity, 18 | color: Colors.white, 19 | padding: only(top: 112), 20 | child: Column( 21 | children: [ 22 | const CommonImage( 23 | asset: TodoFlutterAssets.icError, 24 | size: 86, 25 | ), 26 | CommonButton( 27 | TodoLib.delegate(context).pleaseTryAgain, 28 | fontSize: 12, 29 | textColor: const Color(0xFF161619), 30 | fontWeight: FontWeight.w300, 31 | borderColor: const Color(0xFF161619), 32 | color: Colors.white, 33 | padding: symmetric(vertical: 8, horizontal: 12), 34 | margin: only(top: 33), 35 | onPressed: onPressed, 36 | ), 37 | ], 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/ui/common_refresh_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/src/base/base_state.dart'; 3 | import 'package:todo_flutter/src/bloc/list/list_bloc.dart'; 4 | import 'package:todo_flutter/src/bloc/list/list_state.dart'; 5 | import 'package:todo_flutter/src/net/entity/base_entity.dart'; 6 | import 'package:todo_flutter/src/ui/common_empty_widget.dart'; 7 | import 'package:todo_flutter/src/ui/widget/bloc_load_widget.dart'; 8 | import 'package:flutter_bloc/flutter_bloc.dart'; 9 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 10 | 11 | typedef RefreshChild = Widget Function(BuildContext context, List list); 12 | 13 | class CommonRefreshWidget extends StatefulWidget { 14 | final ListBloc bloc; 15 | final RefreshChild child; 16 | final bool enablePullDown; 17 | final bool enablePullUp; 18 | final Widget? emptyWidget; 19 | final bool wantKeepAlive; 20 | final bool autoInit; 21 | final ValueChanged? result; 22 | 23 | const CommonRefreshWidget({ 24 | required this.bloc, 25 | required this.child, 26 | this.emptyWidget, 27 | this.result, 28 | this.enablePullDown = true, 29 | this.enablePullUp = true, 30 | this.wantKeepAlive = false, 31 | this.autoInit = true, 32 | Key? key, 33 | }) : super(key: key); 34 | 35 | @override 36 | State createState() => _CommonRefreshWidgetState(); 37 | } 38 | 39 | class _CommonRefreshWidgetState extends BaseState> 40 | with AutomaticKeepAliveClientMixin { 41 | @override 42 | void initState() { 43 | super.initState(); 44 | if (widget.autoInit) { 45 | widget.bloc.init(); 46 | } 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | super.build(context); 52 | return BlocBuilder, ListState>( 53 | bloc: widget.bloc, 54 | builder: (BuildContext context, ListState state) { 55 | widget.result?.call(state.entity); 56 | return BlocLoadWidget( 57 | loadBloc: widget.bloc.loadBloc, 58 | reload: () => widget.bloc.init(), 59 | child: SmartRefresher( 60 | onRefresh: () => widget.bloc.refresh(), 61 | onLoading: () => widget.bloc.loadMore(), 62 | controller: widget.bloc.controller, 63 | enablePullDown: widget.enablePullDown, 64 | enablePullUp: widget.enablePullUp, 65 | physics: const ClampingScrollPhysics(), 66 | header: const MaterialClassicHeader(), 67 | child: state.data.isEmpty 68 | ? _emptyView() 69 | : widget.child.call(context, state.data), 70 | ), 71 | ); 72 | }, 73 | ); 74 | } 75 | 76 | Widget _emptyView() { 77 | return Center( 78 | child: widget.emptyWidget ?? const CommonEmptyWidget(), 79 | ); 80 | } 81 | 82 | @override 83 | bool get wantKeepAlive => widget.wantKeepAlive; 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/ui/common_rich_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:todo_flutter/src/base/base_state.dart'; 4 | import 'package:todo_flutter/src/base/base_stateful_widget.dart'; 5 | import 'package:todo_flutter/todo_lib.dart'; 6 | 7 | class CommonRichText extends BaseStatefulWidget { 8 | final List params; 9 | final int maxLines; 10 | final String? fontFamily; 11 | final TextOverflow? overflow; 12 | final List? fontFamilyFallback; 13 | 14 | const CommonRichText( 15 | this.params, { 16 | Key? key, 17 | this.fontFamily, 18 | this.maxLines = 1, 19 | this.fontFamilyFallback, 20 | this.overflow = TextOverflow.ellipsis, 21 | }) : super(key: key); 22 | 23 | @override 24 | State createState() => _CommonRichTextState(); 25 | } 26 | 27 | class _CommonRichTextState extends BaseState { 28 | List _getItem() { 29 | final defaultFontFamily = TodoLib.of(context).fontFamily; 30 | final defaultFontFamilyFallback = TodoLib.of(context).fontFamilyFallback; 31 | final List list = []; 32 | 33 | widget.params.forEach((element) { 34 | list.add( 35 | TextSpan( 36 | text: element.text, 37 | recognizer: TapGestureRecognizer() 38 | ..onTap = () => element.click?.call(), 39 | style: TextStyle( 40 | color: element.color, 41 | fontWeight: element.fontWeight, 42 | fontSize: setFontSize(element.size), 43 | fontFamily: widget.fontFamily ?? defaultFontFamily, 44 | fontFamilyFallback: 45 | widget.fontFamilyFallback ?? defaultFontFamilyFallback, 46 | ), 47 | ), 48 | ); 49 | }); 50 | return list; 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | return Text.rich( 56 | TextSpan( 57 | children: _getItem(), 58 | ), 59 | maxLines: widget.maxLines, 60 | overflow: widget.overflow, 61 | ); 62 | } 63 | } 64 | 65 | class RichTextParams { 66 | final String text; 67 | final Color color; 68 | final double size; 69 | final FontWeight? fontWeight; 70 | final VoidCallback? click; 71 | 72 | RichTextParams( 73 | this.text, 74 | this.size, 75 | this.color, { 76 | this.fontWeight, 77 | this.click, 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/ui/common_text.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/17 on 17:48 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/src/base/base_stateless_widget.dart'; 8 | import 'package:todo_flutter/todo_lib.dart'; 9 | 10 | class CommonText extends BaseStatelessWidget { 11 | final String text; 12 | final double? fontSize; 13 | final Color? color; 14 | final String? fontFamily; 15 | final FontWeight? fontWeight; 16 | final double? height; 17 | final double? lineHeight; 18 | final double? letterSpacing; 19 | final int? maxLines; 20 | final TextOverflow? overflow; 21 | final TextDecoration? decoration; 22 | final FontStyle fontStyle; 23 | final TextAlign? textAlign; 24 | final StrutStyle? strutStyle; 25 | final List? fontFamilyFallback; 26 | final TextDecorationStyle? decorationStyle; 27 | 28 | const CommonText( 29 | this.text, { 30 | Key? key, 31 | this.fontSize, 32 | this.color, 33 | this.fontFamily, 34 | this.fontWeight, 35 | this.height, 36 | this.lineHeight, 37 | this.letterSpacing, 38 | this.maxLines, 39 | this.decoration, 40 | this.decorationStyle, 41 | this.overflow, 42 | this.textAlign, 43 | this.strutStyle, 44 | this.fontFamilyFallback, 45 | this.fontStyle = FontStyle.normal, 46 | }) : super(key: key); 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | final defaultFonSize = fontSize ?? TodoLib.of(context).textSize; 51 | final defaultFontFamily = TodoLib.of(context).fontFamily; 52 | final defaultFontFamilyFallback = TodoLib.of(context).fontFamilyFallback; 53 | 54 | ///calculate height 55 | double? fontHeight = height; 56 | if (lineHeight != null) { 57 | fontHeight = lineHeight! / defaultFonSize; 58 | } 59 | return Text( 60 | text, 61 | maxLines: maxLines, 62 | overflow: overflow, 63 | textAlign: textAlign, 64 | strutStyle: strutStyle, 65 | style: TextStyle( 66 | color: color, 67 | fontSize: setFontSize(defaultFonSize), 68 | fontFamily: fontFamily ?? defaultFontFamily, 69 | fontWeight: fontWeight, 70 | height: fontHeight, 71 | letterSpacing: letterSpacing, 72 | decoration: decoration, 73 | decorationStyle: decorationStyle, 74 | fontStyle: fontStyle, 75 | fontFamilyFallback: fontFamilyFallback ?? defaultFontFamilyFallback, 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/ui/dialog/common_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/src/base/ui_adapter.dart'; 3 | import 'package:todo_flutter/src/base/ui_widget.dart'; 4 | import 'package:todo_flutter/src/ui/dialog/base_dialog.dart'; 5 | 6 | /// createTime: 2023/2/23 on 16:01 7 | /// desc: 8 | /// 9 | /// @author azhon 10 | class CommonDialog with BaseDialog, UIWidget, UIAdapter { 11 | /// 12 | factory CommonDialog() => _getInstance(); 13 | 14 | static CommonDialog get instance => _getInstance(); 15 | static CommonDialog? _instance; 16 | 17 | static CommonDialog _getInstance() { 18 | _instance ??= CommonDialog._internal(); 19 | return _instance!; 20 | } 21 | 22 | CommonDialog._internal(); 23 | 24 | ///提示对话框 25 | ///return [bool] false:取消,true:确定 26 | Future tipDialog( 27 | BuildContext context, 28 | String? title, 29 | String message, { 30 | bool canceledOutside = true, 31 | String confirmText = '确定', 32 | Color confirmColor = Colors.blue, 33 | String cancelText = '取消', 34 | Color cancelColor = const Color(0xFF666666), 35 | }) { 36 | return baseDialog( 37 | context, 38 | title: title, 39 | message: message, 40 | canceledOutside: canceledOutside, 41 | confirmText: confirmText, 42 | confirmColor: confirmColor, 43 | cancelText: cancelText, 44 | cancelColor: cancelColor, 45 | ); 46 | } 47 | 48 | ///只显示一个按钮 49 | ///return [bool] true:确定 50 | Future singleButtonDialog( 51 | BuildContext context, 52 | String? title, 53 | String message, { 54 | bool canceledOutside = true, 55 | String confirmText = '确定', 56 | Color confirmColor = Colors.blue, 57 | }) { 58 | return baseDialog( 59 | context, 60 | title: title, 61 | message: message, 62 | singleButton: true, 63 | canceledOutside: canceledOutside, 64 | confirmText: confirmText, 65 | confirmColor: confirmColor, 66 | ); 67 | } 68 | 69 | ///输入框对话框 70 | ///return [String] 文本框内容 71 | Future inputDialog( 72 | BuildContext context, 73 | String? title, { 74 | String placeholder = '', 75 | bool canceledOutside = true, 76 | bool obscureText = false, 77 | String confirmText = '确定', 78 | Color confirmColor = Colors.blue, 79 | String cancelText = '取消', 80 | Color cancelColor = const Color(0xFF666666), 81 | }) { 82 | return baseInputDialog( 83 | context, 84 | title: title, 85 | placeholder: placeholder, 86 | canceledOutside: canceledOutside, 87 | obscureText: obscureText, 88 | confirmText: confirmText, 89 | confirmColor: confirmColor, 90 | cancelText: cancelText, 91 | cancelColor: cancelColor, 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/src/ui/dialog/loading_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/src/ui/widget/loading_dialog_widget.dart'; 3 | import 'package:todo_flutter/todo_flutter.dart'; 4 | 5 | /// createTime: 2023/4/4 on 20:14 6 | /// desc: 7 | /// 8 | /// @author azhon 9 | /// 10 | 11 | class LoadingDialogInit extends BaseStatefulWidget { 12 | final Widget child; 13 | 14 | const LoadingDialogInit({required this.child, Key? key}) : super(key: key); 15 | 16 | @override 17 | State createState() => _LoadingDialogState(); 18 | } 19 | 20 | class _LoadingDialogState extends State { 21 | late OverlayEntry _overlayEntry; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _overlayEntry = OverlayEntry( 27 | builder: (_) => 28 | LoadingDialog.instance.currWidget ?? const SizedBox.shrink(), 29 | ); 30 | LoadingDialog.instance.overlayEntry = _overlayEntry; 31 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 32 | LoadingDialog.instance.customLoading = TodoLib.of(context).loadingWidget; 33 | }); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return Directionality( 39 | textDirection: TextDirection.ltr, 40 | child: Material( 41 | child: Overlay( 42 | initialEntries: [ 43 | OverlayEntry(builder: (_) => widget.child), 44 | _overlayEntry, 45 | ], 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | class LoadingDialog { 53 | factory LoadingDialog() => instance; 54 | static final LoadingDialog instance = LoadingDialog._internal(); 55 | OverlayEntry? overlayEntry; 56 | Widget? currWidget; 57 | Widget? customLoading; 58 | 59 | LoadingDialog._internal(); 60 | 61 | ///显示对话框 62 | static void show({String? msg}) { 63 | instance._show(msg: msg); 64 | } 65 | 66 | ///隐藏对话框 67 | static void dismiss() { 68 | instance._dismiss(); 69 | } 70 | 71 | ///是否在显示 72 | static bool isShow() { 73 | return instance.currWidget == null; 74 | } 75 | 76 | void _show({String? msg}) { 77 | currWidget = customLoading ?? LoadingDialogWidget(msg: msg); 78 | overlayEntry?.markNeedsBuild(); 79 | } 80 | 81 | void _dismiss() { 82 | if (currWidget == null) { 83 | return; 84 | } 85 | currWidget = null; 86 | overlayEntry?.markNeedsBuild(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/ui/widget/amount_text_field_formatter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | /// createTime: 2023/11/29 on 13:50 4 | /// desc: 金额输入框 5 | /// 允许输入`,`因为在iOS设备中数字键盘根据地区来有可能是`,`或者`.` 6 | /// 7 | /// @author azhon 8 | class AmountTextFieldFormatter extends FilteringTextInputFormatter { 9 | final int digit; 10 | final String _decimalComma = ','; 11 | final String _decimalDot = '.'; 12 | String _oldText = ''; 13 | 14 | AmountTextFieldFormatter({ 15 | this.digit = 2, 16 | bool allow = true, 17 | }) : super(RegExp('[0-9.,]'), allow: allow); 18 | 19 | @override 20 | TextEditingValue formatEditUpdate( 21 | TextEditingValue oldValue, 22 | TextEditingValue newValue, 23 | ) { 24 | ///替换`,`为`.` 25 | if (newValue.text.contains(_decimalComma)) { 26 | newValue = newValue.copyWith( 27 | text: newValue.text.replaceAll(_decimalComma, _decimalDot), 28 | ); 29 | } 30 | final handlerValue = super.formatEditUpdate(oldValue, newValue); 31 | String value = handlerValue.text; 32 | int selectionIndex = handlerValue.selection.end; 33 | 34 | ///如果输入框内容为.直接将输入框赋值为0. 35 | if (value == _decimalDot) { 36 | value = '0.'; 37 | selectionIndex++; 38 | } 39 | if (_getValueDigit(value) > digit || _pointCount(value) > 1) { 40 | value = _oldText; 41 | selectionIndex = _oldText.length; 42 | } 43 | _oldText = value; 44 | return TextEditingValue( 45 | text: value, 46 | selection: TextSelection.collapsed(offset: selectionIndex), 47 | ); 48 | } 49 | 50 | ///输入多个小数点的情况 51 | int _pointCount(String value) { 52 | int count = 0; 53 | value.split('').forEach((e) { 54 | if (e == _decimalDot) { 55 | count++; 56 | } 57 | }); 58 | return count; 59 | } 60 | 61 | ///获取目前的小数位数 62 | int _getValueDigit(String value) { 63 | if (value.contains(_decimalDot)) { 64 | return value.split(_decimalDot)[1].length; 65 | } else { 66 | return -1; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/ui/widget/bloc_load_widget.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/21 on 15:37 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_bloc/flutter_bloc.dart'; 8 | import 'package:todo_flutter/src/base/base_stateless_widget.dart'; 9 | import 'package:todo_flutter/src/bloc/load/load_bloc.dart'; 10 | import 'package:todo_flutter/src/bloc/load/load_state.dart'; 11 | import 'package:todo_flutter/src/service/error/domain_exception.dart'; 12 | import 'package:todo_flutter/src/ui/common_error_widget.dart'; 13 | import 'package:todo_flutter/src/ui/widget/circular_progress_widget.dart'; 14 | 15 | typedef ErrorWidgetBuilder = Widget? Function( 16 | BuildContext context, 17 | DomainException exception, 18 | ); 19 | 20 | class BlocLoadWidget extends BaseStatelessWidget { 21 | final Color color; 22 | final Widget child; 23 | final LoadBloc loadBloc; 24 | final VoidCallback? reload; 25 | final ErrorWidgetBuilder? errorBuilder; 26 | 27 | const BlocLoadWidget({ 28 | required this.child, 29 | required this.loadBloc, 30 | required this.reload, 31 | this.errorBuilder, 32 | this.color = const Color(0xFF161619), 33 | Key? key, 34 | }) : super(key: key); 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return BlocBuilder( 39 | bloc: loadBloc, 40 | builder: (BuildContext context, LoadState state) { 41 | if (state is LoadingState) { 42 | return SizedBox.expand( 43 | child: Column( 44 | mainAxisAlignment: MainAxisAlignment.center, 45 | children: [ 46 | CircularProgressWidget(color: color), 47 | ], 48 | ), 49 | ); 50 | } 51 | if (state is ErrorState) { 52 | return _errorWidget(context, state.exception); 53 | } 54 | return child; 55 | }, 56 | ); 57 | } 58 | 59 | Widget _errorWidget(BuildContext context, DomainException exception) { 60 | final w = errorBuilder?.call(context, exception); 61 | if (w == null) { 62 | return CommonErrorWidget( 63 | onPressed: reload, 64 | ); 65 | } 66 | return w; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/src/ui/widget/circular_progress_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/src/base/base_stateless_widget.dart'; 3 | 4 | /// createTime: 2023/4/28 on 16:36 5 | /// desc: 6 | /// 7 | /// @author azhon 8 | class CircularProgressWidget extends BaseStatelessWidget { 9 | final Color color; 10 | 11 | const CircularProgressWidget({ 12 | Key? key, 13 | this.color = const Color(0xFF161619), 14 | }) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Transform.scale( 19 | scale: 0.8, 20 | child: CircularProgressIndicator( 21 | color: color, 22 | strokeWidth: setWidth(2), 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/ui/widget/data_change_widget.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/9/30 on 10:55 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:todo_flutter/src/base/base_stateless_widget.dart'; 8 | import 'package:todo_flutter/src/bloc/data/data_change_bloc.dart'; 9 | import 'package:flutter_bloc/flutter_bloc.dart'; 10 | import 'package:todo_flutter/src/bloc/data/data_change_state.dart'; 11 | 12 | class DataChangeWidget extends BaseStatelessWidget { 13 | final DataChangeBloc bloc; 14 | final BlocWidgetBuilder child; 15 | 16 | const DataChangeWidget({ 17 | required this.bloc, 18 | required this.child, 19 | Key? key, 20 | }) : super(key: key); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return BlocBuilder, DataChangeState>( 25 | bloc: bloc, 26 | builder: (_, state) { 27 | return child.call(_, state.data); 28 | }, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/ui/widget/loading_dialog_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/src/base/base_stateless_widget.dart'; 3 | import 'package:todo_flutter/src/ui/common_text.dart'; 4 | import 'package:todo_flutter/src/ui/dialog/loading_dialog.dart'; 5 | import 'package:todo_flutter/todo_lib.dart'; 6 | 7 | /// createTime: 2021/10/19 on 17:24 8 | /// desc: 9 | /// 10 | /// @author azhon 11 | 12 | class LoadingDialogWidget extends BaseStatelessWidget { 13 | final String? msg; 14 | final bool canceledOutside; 15 | 16 | const LoadingDialogWidget({ 17 | this.msg, 18 | this.canceledOutside = false, 19 | Key? key, 20 | }) : super(key: key); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return GestureDetector( 25 | behavior: HitTestBehavior.opaque, 26 | onTap: () { 27 | if (canceledOutside) { 28 | LoadingDialog.dismiss(); 29 | } 30 | }, 31 | child: Center( 32 | child: Container( 33 | width: setWidth(100), 34 | height: setWidth(100), 35 | decoration: BoxDecoration( 36 | color: const Color(0x99000000), 37 | borderRadius: BorderRadius.circular(setRadius(8)), 38 | ), 39 | child: Column( 40 | mainAxisSize: MainAxisSize.min, 41 | children: [ 42 | sizedBox(height: 20), 43 | const CircularProgressIndicator(color: Colors.white), 44 | sizedBox(height: 12), 45 | Padding( 46 | padding: symmetric(horizontal: 10), 47 | child: CommonText( 48 | msg ?? TodoLib.delegate(context).loading, 49 | color: Colors.white, 50 | maxLines: 2, 51 | fontSize: 12, 52 | textAlign: TextAlign.center, 53 | overflow: TextOverflow.ellipsis, 54 | ), 55 | ), 56 | ], 57 | ), 58 | ), 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/ui/widget/remove_ripple_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:todo_flutter/src/base/base_stateless_widget.dart'; 6 | 7 | /// createTime: 2022/9/6 on 16:53 8 | /// desc: 去除安卓水波纹效果 9 | /// 10 | /// @author azhon 11 | class RemoveRippleWidget extends BaseStatelessWidget { 12 | final Widget child; 13 | 14 | const RemoveRippleWidget({required this.child, Key? key}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return ScrollConfiguration(behavior: _RemoveRippleBehavior(), child: child); 19 | } 20 | } 21 | 22 | class _RemoveRippleBehavior extends ScrollBehavior { 23 | @override 24 | Widget buildOverscrollIndicator( 25 | BuildContext context, 26 | Widget child, 27 | ScrollableDetails details, 28 | ) { 29 | if (!kIsWeb && Platform.isAndroid) { 30 | return child; 31 | } 32 | return super.buildOverscrollIndicator(context, child, details); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/ui/widget/saturation_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/src/base/base_stateless_widget.dart'; 3 | 4 | /// createTime: 2022/12/1 on 10:59 5 | /// desc: The app is black and white to commemorate important events and people 6 | /// 7 | /// @author azhon 8 | 9 | class SaturationWidget extends BaseStatelessWidget { 10 | final Widget child; 11 | 12 | ///value [0,1] 13 | final double saturation; 14 | 15 | const SaturationWidget({ 16 | required this.child, 17 | this.saturation = 0, 18 | Key? key, 19 | }) : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return ColorFiltered( 24 | colorFilter: ColorFilter.matrix(_saturation(saturation)), 25 | child: child, 26 | ); 27 | } 28 | 29 | ///Default matrix 30 | List get _matrix => [ 31 | 1, 0, 0, 0, 0, //R 32 | 0, 1, 0, 0, 0, //G 33 | 0, 0, 1, 0, 0, //B 34 | 0, 0, 0, 1, 0, //A 35 | ]; 36 | 37 | ///Generate a matrix of specified saturation 38 | ///[sat] A value of 0 maps the color to gray-scale. 1 is identity. 39 | List _saturation(double sat) { 40 | final m = _matrix; 41 | final double invSat = 1 - sat; 42 | final double R = 0.213 * invSat; 43 | final double G = 0.715 * invSat; 44 | final double B = 0.072 * invSat; 45 | m[0] = R + sat; 46 | m[1] = G; 47 | m[2] = B; 48 | m[5] = R; 49 | m[6] = G + sat; 50 | m[7] = B; 51 | m[10] = R; 52 | m[11] = G; 53 | m[12] = B + sat; 54 | return m; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/ui/widget/un_focus_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/src/base/base_stateless_widget.dart'; 3 | import 'package:todo_flutter/src/ui/common_click_widget.dart'; 4 | 5 | /// createTime: 2022/9/2 on 15:30 6 | /// desc: 7 | /// 8 | /// @author azhon 9 | class UnFocusWidget extends BaseStatelessWidget { 10 | final Widget child; 11 | 12 | const UnFocusWidget({required this.child, Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return CommonClickWidget( 17 | onTap: () => FocusManager.instance.primaryFocus?.unfocus(), 18 | child: child, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/util/calculate_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:decimal/decimal.dart'; 2 | 3 | /// createTime: 2024/4/28 on 14:33 4 | /// desc: 5 | /// 6 | /// @author azhon 7 | class CalculateUtil { 8 | ///加法 9 | static Decimal plus(double a, double b) { 10 | return Decimal.parse(a.toString()) + Decimal.parse(b.toString()); 11 | } 12 | 13 | ///减法 14 | static Decimal minus(double a, double b) { 15 | return Decimal.parse(a.toString()) - Decimal.parse(b.toString()); 16 | } 17 | 18 | ///乘法 19 | static Decimal multiply(double a, double b) { 20 | return Decimal.parse(a.toString()) * Decimal.parse(b.toString()); 21 | } 22 | 23 | ///除法 24 | ///[scaleOnInfinitePrecision] 保留几位小数 25 | static Decimal divide( 26 | double a, 27 | double b, [ 28 | int scaleOnInfinitePrecision = 2, 29 | ]) { 30 | final result = Decimal.parse(a.toString()) / Decimal.parse(b.toString()); 31 | return result.toDecimal(scaleOnInfinitePrecision: scaleOnInfinitePrecision); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/util/log_util.dart: -------------------------------------------------------------------------------- 1 | /// createTime: 2021/10/18 on 17:25 2 | /// desc: 3 | /// 4 | /// @author azhon 5 | 6 | import 'package:logger/logger.dart'; 7 | 8 | class LogUtil { 9 | static Logger _logger = Logger(printer: PrettyPrinter(printEmojis: false)); 10 | 11 | static void init({ 12 | LogFilter? filter, 13 | LogPrinter? printer, 14 | LogOutput? output, 15 | Level? level, 16 | }) { 17 | _logger = Logger( 18 | filter: filter, 19 | output: output, 20 | level: level, 21 | printer: printer ?? PrettyPrinter(printEmojis: false), 22 | ); 23 | } 24 | 25 | static void v(message, {Object? error, StackTrace? stackTrace}) { 26 | _logger.log(Level.trace, message, error: error, stackTrace: stackTrace); 27 | } 28 | 29 | static void d(message, {Object? error, StackTrace? stackTrace}) { 30 | _logger.log(Level.debug, message, error: error, stackTrace: stackTrace); 31 | } 32 | 33 | static void i(message, {Object? error, StackTrace? stackTrace}) { 34 | _logger.log(Level.info, message, error: error, stackTrace: stackTrace); 35 | } 36 | 37 | static void w(message, {Object? error, StackTrace? stackTrace}) { 38 | _logger.log(Level.warning, message, error: error, stackTrace: stackTrace); 39 | } 40 | 41 | static void e(message, {Object? error, StackTrace? stackTrace}) { 42 | _logger.log(Level.error, message, error: error, stackTrace: stackTrace); 43 | } 44 | 45 | static void wtf(message, {Object? error, StackTrace? stackTrace}) { 46 | _logger.log(Level.fatal, message, error: error, stackTrace: stackTrace); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/util/object_util.dart: -------------------------------------------------------------------------------- 1 | class ObjectUtil { 2 | ///对象是不为空 3 | ///[obj] 任意对象 4 | static bool isNotEmpty(Object? obj) { 5 | return !isEmpty(obj); 6 | } 7 | 8 | ///对象是否为空 9 | ///[obj] 任意对象 10 | static bool isEmpty(Object? obj) { 11 | if (obj == null) { 12 | return true; 13 | } 14 | if (obj is String) { 15 | return obj.isEmpty; 16 | } 17 | if (obj is List) { 18 | return obj.isEmpty; 19 | } 20 | if (obj is Map) { 21 | return obj.isEmpty; 22 | } 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/util/preference_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class PreferencesUtil { 4 | static SharedPreferences? _preferences; 5 | 6 | static Future init() async { 7 | _preferences = await SharedPreferences.getInstance(); 8 | } 9 | 10 | static void putString(String key, String value) { 11 | _preferences?.setString(key, value); 12 | } 13 | 14 | static String getString(String key, {String defaultValue = ''}) { 15 | return _preferences?.getString(key) ?? defaultValue; 16 | } 17 | 18 | static void putBool(String key, bool value) { 19 | _preferences?.setBool(key, value); 20 | } 21 | 22 | static bool getBool(String key, {bool defaultValue = false}) { 23 | return _preferences?.getBool(key) ?? defaultValue; 24 | } 25 | 26 | static void putInt(String key, int value) { 27 | _preferences?.setInt(key, value); 28 | } 29 | 30 | static int getInt(String key, {int defaultValue = 0}) { 31 | return _preferences?.getInt(key) ?? defaultValue; 32 | } 33 | 34 | static void putDouble(String key, double value) { 35 | _preferences?.setDouble(key, value); 36 | } 37 | 38 | static double getDouble(String key, {double defaultValue = 0.0}) { 39 | return _preferences?.getDouble(key) ?? defaultValue; 40 | } 41 | 42 | static Future remove(String key) { 43 | return _preferences?.remove(key) ?? Future.value(false); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/util/time_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart'; 2 | 3 | /// createTime: 2023/4/14 on 19:34 4 | /// desc: 5 | /// 6 | /// @author azhon 7 | class TimeUtil { 8 | ///获取当前时间 9 | static DateTime now() { 10 | return DateTime.now(); 11 | } 12 | 13 | static int nowUnix() { 14 | return DateTime.now().millisecondsSinceEpoch; 15 | } 16 | 17 | static String year(int timestamp) { 18 | return formatTime(timestamp, 'yyyy'); 19 | } 20 | 21 | static String month(int timestamp) { 22 | return formatTime(timestamp, 'MM'); 23 | } 24 | 25 | static String day(int timestamp) { 26 | return formatTime(timestamp, 'dd'); 27 | } 28 | 29 | static String yyyyMMdd(int timestamp) { 30 | return formatTime(timestamp, 'yyyy-MM-dd'); 31 | } 32 | 33 | static String hour(int timestamp) { 34 | return formatTime(timestamp, 'HH'); 35 | } 36 | 37 | static String minutes(int timestamp) { 38 | return formatTime(timestamp, 'mm'); 39 | } 40 | 41 | static String seconds(int timestamp) { 42 | return formatTime(timestamp, 'ss'); 43 | } 44 | 45 | static String hhmmss(int timestamp) { 46 | return formatTime(timestamp, 'HH:mm:ss'); 47 | } 48 | 49 | ///格式化时间 50 | static String formatTime( 51 | int timestamp, [ 52 | String format = 'yyyy-MM-dd HH:mm:ss', 53 | ]) { 54 | if (timestamp <= 0) { 55 | return ''; 56 | } 57 | return DateFormat(format) 58 | .format(DateTime.fromMillisecondsSinceEpoch(timestamp)); 59 | } 60 | 61 | ///格式化时间 62 | static String formatDate( 63 | DateTime date, [ 64 | String format = 'yyyy-MM-dd HH:mm:ss', 65 | ]) { 66 | return formatTime(date.millisecondsSinceEpoch, format); 67 | } 68 | 69 | static int parseTimestamp(String? time) { 70 | return parseDate(time)?.millisecondsSinceEpoch ?? 0; 71 | } 72 | 73 | ///解析时间 74 | static DateTime? parseDate(String? time) { 75 | try { 76 | if (time == null || time.isEmpty) { 77 | throw Exception('time is empty.'); 78 | } 79 | return DateTime.parse(time); 80 | } catch (e) { 81 | return null; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/util/tip_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluttertoast/fluttertoast.dart'; 2 | import 'package:todo_flutter/src/service/theme/toast_theme_data.dart'; 3 | import 'package:todo_flutter/src/ui/dialog/loading_dialog.dart'; 4 | import 'package:todo_flutter/src/util/object_util.dart'; 5 | import 'package:todo_flutter/todo_lib.dart'; 6 | 7 | /// createTime: 2023/4/14 on 20:38 8 | /// desc: 9 | /// 10 | /// @author azhon 11 | 12 | ///显示toast 13 | void showToast(String msg, {ToastThemeData? data}) { 14 | if (ObjectUtil.isEmpty(msg)) { 15 | return; 16 | } 17 | final ToastThemeData themeData = 18 | data ?? TodoLib.of(TodoLib.navigatorKey.currentContext!).toastThemeData; 19 | cancelToast(); 20 | Fluttertoast.showToast( 21 | msg: msg, 22 | fontSize: themeData.fontSize, 23 | textColor: themeData.textColor, 24 | backgroundColor: themeData.backgroundColor, 25 | toastLength: themeData.toastLength, 26 | gravity: themeData.gravity, 27 | ); 28 | } 29 | 30 | ///取消所有toast 31 | void cancelToast() { 32 | Fluttertoast.cancel(); 33 | } 34 | 35 | ///显示等待对话框 36 | void showLoading([String? msg]) { 37 | LoadingDialog.show(msg: msg); 38 | } 39 | 40 | ///关闭等待对话框 41 | void dismissLoading() { 42 | LoadingDialog.dismiss(); 43 | } 44 | -------------------------------------------------------------------------------- /lib/todo_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/src/service/route/router_history_stack.dart'; 3 | import 'package:todo_flutter/src/service/route/router_util.dart'; 4 | import 'package:todo_flutter/src/ui/dialog/loading_dialog.dart'; 5 | import 'package:todo_flutter/todo_lib.dart'; 6 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 7 | 8 | /// createTime: 2022/7/26 on 10:16 9 | /// desc: 10 | /// 11 | /// @author azhon 12 | 13 | class TodoApp extends StatelessWidget { 14 | final String title; 15 | final ThemeData? theme; 16 | final ThemeData? darkTheme; 17 | final Widget home; 18 | final Locale? locale; 19 | final Size designSize; 20 | final TodoLibData? libData; 21 | final TransitionBuilder? builder; 22 | final bool debugShowCheckedModeBanner; 23 | final Iterable supportedLocales; 24 | final GlobalKey? navigatorKey; 25 | final List navigatorObservers; 26 | final Iterable>? localizationsDelegates; 27 | 28 | const TodoApp({ 29 | required this.home, 30 | this.title = '', 31 | this.theme, 32 | this.darkTheme, 33 | this.libData, 34 | this.locale, 35 | this.builder, 36 | this.navigatorKey, 37 | this.localizationsDelegates, 38 | this.debugShowCheckedModeBanner = true, 39 | this.designSize = const Size(375, 667), 40 | this.navigatorObservers = const [], 41 | this.supportedLocales = const [Locale('en', 'US')], 42 | Key? key, 43 | }) : super(key: key); 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | TodoLib.navigatorKey = navigatorKey ?? TodoLib.navigatorKey; 48 | return ScreenUtilInit( 49 | designSize: designSize, 50 | builder: (_, child) { 51 | return TodoLib( 52 | data: libData, 53 | child: MaterialApp( 54 | home: home, 55 | title: title, 56 | theme: theme, 57 | locale: locale, 58 | darkTheme: darkTheme, 59 | supportedLocales: supportedLocales, 60 | navigatorKey: TodoLib.navigatorKey, 61 | navigatorObservers: navigatorObservers + [RouterHistoryObserver()], 62 | localizationsDelegates: localizationsDelegates, 63 | debugShowCheckedModeBanner: debugShowCheckedModeBanner, 64 | onGenerateRoute: RouterUtil.instance.generator(), 65 | builder: (BuildContext context, Widget? widget) { 66 | final child = builder?.call(context, widget); 67 | return LoadingDialogInit( 68 | child: MediaQuery( 69 | data: MediaQuery.of(context).copyWith( 70 | textScaler: TextScaler.noScaling, 71 | ), 72 | child: child ?? widget ?? Container(), 73 | ), 74 | ); 75 | }, 76 | ), 77 | ); 78 | }, 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/todo_flutter.dart: -------------------------------------------------------------------------------- 1 | library todo_flutter; 2 | 3 | export 'src/base/base_state.dart'; 4 | export 'src/base/ui_adapter.dart'; 5 | export 'src/base/ui_widget.dart'; 6 | export 'src/base/base_stateful_widget.dart'; 7 | export 'src/base/base_stateless_widget.dart'; 8 | export 'src/base/bloc/base_bloc.dart'; 9 | export 'src/base/bloc/base_event.dart'; 10 | 11 | /// 12 | export 'src/I10n/text_delegate.dart'; 13 | 14 | /// 15 | export 'src/bloc/data/data_change_bloc.dart'; 16 | export 'src/bloc/data/data_change_state.dart'; 17 | export 'src/bloc/list/list_bloc.dart'; 18 | 19 | /// 20 | export 'src/net/base_net_engine.dart'; 21 | export 'src/net/base_net_provider.dart'; 22 | export 'src/net/base_request.dart'; 23 | export 'src/net/dio_engine.dart'; 24 | export 'src/net/base_convert.dart'; 25 | export 'src/net/entity/base_entity.dart'; 26 | 27 | /// 28 | export 'src/ui/common_button.dart'; 29 | export 'src/ui/common_image.dart'; 30 | export 'src/ui/common_input.dart'; 31 | export 'src/ui/common_input_area.dart'; 32 | export 'src/ui/common_text.dart'; 33 | export 'src/ui/common_rich_text.dart'; 34 | export 'src/ui/common_click_widget.dart'; 35 | export 'src/ui/common_refresh_widget.dart'; 36 | export 'src/ui/common_error_widget.dart'; 37 | export 'src/ui/common_empty_widget.dart'; 38 | export 'src/ui/widget/bloc_load_widget.dart'; 39 | export 'src/ui/widget/data_change_widget.dart'; 40 | export 'src/ui/widget/un_focus_widget.dart'; 41 | export 'src/ui/widget/saturation_widget.dart'; 42 | export 'src/ui/widget/remove_ripple_widget.dart'; 43 | export 'src/ui/widget/extended_refresh_indicator.dart'; 44 | export 'src/ui/widget/amount_text_field_formatter.dart'; 45 | export 'src/ui/widget/circular_progress_widget.dart'; 46 | export 'src/ui/dialog/common_dialog.dart'; 47 | export 'src/ui/dialog/loading_dialog.dart'; 48 | 49 | /// 50 | export 'src/service/theme/toast_theme_data.dart'; 51 | export 'src/service/error/domain_exception.dart'; 52 | export 'src/service/route/router_util.dart'; 53 | export 'src/service/route/bundle.dart'; 54 | export 'src/service/route/router_history_stack.dart'; 55 | export 'src/service/env/env.dart'; 56 | export 'src/service/extension/context_extension.dart'; 57 | export 'src/service/event/global_event_manager.dart' hide GlobalEvent; 58 | 59 | /// 60 | export 'src/util/log_util.dart'; 61 | export 'src/util/object_util.dart'; 62 | export 'src/util/preference_util.dart'; 63 | export 'src/util/time_util.dart'; 64 | export 'src/util/tip_util.dart'; 65 | export 'src/util/calculate_util.dart'; 66 | 67 | export 'todo_lib.dart'; 68 | 69 | ///other library 70 | export 'package:dio/dio.dart'; 71 | export 'package:decimal/decimal.dart'; 72 | export 'package:flutter_bloc/flutter_bloc.dart'; 73 | export 'package:fluttertoast/fluttertoast.dart'; 74 | export 'package:module_bridge/module_bridge.dart'; 75 | export 'package:pull_to_refresh/pull_to_refresh.dart'; 76 | export 'package:network_capture/network_capture.dart'; 77 | -------------------------------------------------------------------------------- /lib/todo_lib.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_flutter/src/I10n/text_delegate.dart'; 3 | import 'package:todo_flutter/src/I10n/text_delegate_en.dart'; 4 | import 'package:todo_flutter/src/service/theme/toast_theme_data.dart'; 5 | 6 | /// createTime: 2021/9/29 on 13:54 7 | /// desc: 框架初始化 8 | /// 9 | /// @author azhon 10 | 11 | class TodoLib extends InheritedWidget { 12 | /// 用于导航的key(从而简化跳转需要context),需要主动进行初始化 13 | static GlobalKey navigatorKey = GlobalKey(); 14 | 15 | final TodoLibData? data; 16 | 17 | const TodoLib({ 18 | required Widget child, 19 | this.data, 20 | Key? key, 21 | }) : super(key: key, child: child); 22 | 23 | ///获取库配置数据 24 | static TodoLibData of(BuildContext context) { 25 | final TodoLib? todoLib = 26 | context.dependOnInheritedWidgetOfExactType(); 27 | if (todoLib == null) { 28 | throw Exception('Please init [TodoLib] first...'); 29 | } 30 | return todoLib.data ?? TodoLibData(); 31 | } 32 | 33 | ///获取当前语言 34 | static TextDelegate delegate(BuildContext context) { 35 | final locale = Localizations.maybeLocaleOf(context); 36 | if (locale == null) { 37 | return const TextDelegate(); 38 | } 39 | final languageCode = locale.languageCode.toLowerCase(); 40 | return of(context).textDelegates![languageCode] ?? const TextDelegate(); 41 | } 42 | 43 | @override 44 | bool updateShouldNotify(TodoLib oldWidget) => data != oldWidget.data; 45 | } 46 | 47 | class TodoLibData { 48 | ///默认按钮高度 49 | final double buttonHeight; 50 | 51 | ///点击事件截流间隔时长 ms 52 | final int clickInterceptInterval; 53 | 54 | ///输入框提示文本颜色 55 | final Color placeholderColor; 56 | 57 | ///输入框文本颜色 58 | final Color inputTextColor; 59 | 60 | ///输入框背景 61 | final Color inputBackgroundColor; 62 | 63 | ///默认文字大小 64 | final double textSize; 65 | 66 | ///文本字体 67 | final String? fontFamily; 68 | 69 | ///toast配置 70 | ToastThemeData toastThemeData; 71 | 72 | ///解决ios 中文[FontWeight.w500]不起作用bug 73 | List? fontFamilyFallback; 74 | 75 | ///文本国际化 76 | Map? textDelegates; 77 | 78 | ///加载组件 79 | Widget? loadingWidget; 80 | 81 | TodoLibData({ 82 | this.buttonHeight = 45, 83 | this.clickInterceptInterval = 1000, 84 | this.textSize = 14, 85 | this.placeholderColor = const Color(0xFFC7CCD5), 86 | this.inputTextColor = const Color(0xFF393C42), 87 | this.inputBackgroundColor = const Color(0xFFF6F7F9), 88 | this.toastThemeData = const ToastThemeData(), 89 | this.fontFamily, 90 | this.fontFamilyFallback, 91 | this.loadingWidget, 92 | this.textDelegates, 93 | }) { 94 | textDelegates = _updateTextDelegates(textDelegates); 95 | } 96 | 97 | ///用户设置的替换原有的 98 | Map _updateTextDelegates( 99 | Map? textDelegates, 100 | ) { 101 | final map = { 102 | 'zh': const TextDelegate(), 103 | 'en': const TextDelegateEn(), 104 | }; 105 | textDelegates?.forEach((key, value) { 106 | map[key] = value; 107 | }); 108 | return map; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: todo_flutter 2 | description: Flutter project development scaffold 3 | version: 0.0.7 4 | homepage: https://github.com/azhon/todo_flutter 5 | 6 | environment: 7 | sdk: ">=2.15.0 <4.0.0" 8 | flutter: ">=3.7.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | # 路由 14 | fluro: ^2.0.5 15 | # 屏幕适配 16 | flutter_screenutil: ^5.9.3 17 | # 图片库 18 | extended_image: ^9.0.4 19 | extended_image_library: ^4.0.5 20 | # bloc状态管理 21 | flutter_bloc: ^8.1.6 22 | # 网络库 23 | dio: ^5.7.0 24 | # 日志 25 | logger: ^2.4.0 26 | # 刷新 27 | pull_to_refresh: ^2.0.0 28 | # 本地存储 29 | shared_preferences: ^2.2.3 30 | # toast 31 | fluttertoast: ^8.2.5 32 | # 时间格式化 33 | intl: ^0.19.0 34 | # 模块通信 35 | module_bridge: ^1.0.5 36 | # 网络抓包 37 | network_capture: ^1.2.2 38 | # 精度计算 39 | decimal: ^2.3.3 40 | dev_dependencies: 41 | flutter_test: 42 | sdk: flutter 43 | flutter_lints: ^2.0.3 44 | 45 | # For information on the generic Dart part of this file, see the 46 | # following page: https://dart.dev/tools/pub/pubspec 47 | 48 | # The following section is specific to Flutter. 49 | flutter: 50 | assets: 51 | - assets/images/ 52 | # To add assets to your package, add an assets section, like this: 53 | # assets: 54 | # - images/a_dot_burr.jpeg 55 | # - images/a_dot_ham.jpeg 56 | # 57 | # For details regarding assets in packages, see 58 | # https://flutter.dev/assets-and-images/#from-packages 59 | # 60 | # An image asset can refer to one or more resolution-specific "variants", see 61 | # https://flutter.dev/assets-and-images/#resolution-aware. 62 | 63 | # To add custom fonts to your package, add a fonts section here, 64 | # in this "flutter" section. Each entry in this list should have a 65 | # "family" key with the font family name, and a "fonts" key with a 66 | # list giving the asset and other descriptors for the font. For 67 | # example: 68 | # fonts: 69 | # - family: Schyler 70 | # fonts: 71 | # - asset: fonts/Schyler-Regular.ttf 72 | # - asset: fonts/Schyler-Italic.ttf 73 | # style: italic 74 | # - family: Trajan Pro 75 | # fonts: 76 | # - asset: fonts/TrajanPro.ttf 77 | # - asset: fonts/TrajanPro_Bold.ttf 78 | # weight: 700 79 | # 80 | # For details regarding fonts in packages, see 81 | # https://flutter.dev/custom-fonts/#from-packages 82 | -------------------------------------------------------------------------------- /test/todo_flutter_lib_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | --------------------------------------------------------------------------------