Repository: toly1994328/FlutterUnit Branch: master Commit: 5ea68ae13cff Files: 1054 Total size: 2.8 MB Directory structure: gitextract_p937gcv0/ ├── .gitignore ├── .metadata ├── LICENSE ├── README-EN.md ├── README.md ├── analysis_options.yaml ├── android/ │ ├── .gitignore │ ├── app/ │ │ ├── build.gradle.kts │ │ ├── proguard-rules.pro │ │ └── src/ │ │ ├── debug/ │ │ │ └── AndroidManifest.xml │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── toly1994/ │ │ │ │ └── flutter_unit/ │ │ │ │ └── MainActivity.kt │ │ │ └── res/ │ │ │ ├── drawable/ │ │ │ │ └── launch_background.xml │ │ │ ├── drawable-v21/ │ │ │ │ └── launch_background.xml │ │ │ ├── values/ │ │ │ │ └── styles.xml │ │ │ └── values-night/ │ │ │ └── styles.xml │ │ └── profile/ │ │ └── AndroidManifest.xml │ ├── build/ │ │ └── reports/ │ │ └── problems/ │ │ └── problems-report.html │ ├── build.gradle.kts │ ├── gradle/ │ │ └── wrapper/ │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ └── settings.gradle.kts ├── assets/ │ ├── data/ │ │ ├── gallery_info.json │ │ ├── packages/ │ │ │ └── data.json │ │ └── web/ │ │ ├── node.json │ │ └── widget.json │ └── version.json ├── desiredFileName.txt ├── devtools_options.yaml ├── doc/ │ ├── development/ │ │ └── architecture.md │ └── version/ │ ├── 3.1.0.md │ └── 3.2.0.md ├── ios/ │ ├── .gitignore │ ├── Flutter/ │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner/ │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.imageset/ │ │ │ ├── Contents.json │ │ │ └── README.md │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h │ ├── 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 ├── l10n.yaml ├── lib/ │ ├── main.dart │ └── src/ │ ├── flutter_unit.dart │ ├── l10n/ │ │ ├── arb/ │ │ │ ├── app_en.arb │ │ │ └── app_zh.arb │ │ ├── gen/ │ │ │ ├── app_l10n.dart │ │ │ ├── app_l10n_en.dart │ │ │ └── app_l10n_zh.dart │ │ └── locale_provider.dart │ ├── navigation/ │ │ ├── model/ │ │ │ └── app_tab.dart │ │ ├── router/ │ │ │ ├── app_route.dart │ │ │ ├── system/ │ │ │ │ ├── app.dart │ │ │ │ ├── global.dart │ │ │ │ └── settings.dart │ │ │ └── widgets/ │ │ │ ├── collection_route.dart │ │ │ └── widgets_route.dart │ │ └── view/ │ │ ├── app_bloc_provider.dart │ │ ├── desktop/ │ │ │ ├── flutter_unit_desk_navigation.dart │ │ │ ├── locale_change_menu.dart │ │ │ ├── menu_bar_leading.dart │ │ │ ├── menu_bar_tail.dart │ │ │ ├── theme_model_switch_icon.dart │ │ │ ├── toly_unit_menu_cell.dart │ │ │ └── unit_shortcuts_scope.dart │ │ └── mobile/ │ │ ├── carousel.dart │ │ ├── news.dart │ │ ├── pure_bottom_bar.dart │ │ └── unit_navigation.dart │ └── starter/ │ ├── bridge/ │ │ └── unit_bridge.dart │ ├── fx_application.dart │ ├── start_repository.dart │ └── view/ │ ├── error/ │ │ └── app_start_error.dart │ └── splash/ │ ├── Flutter_unit_splash.dart │ └── flutter_unit_text.dart ├── linux/ │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter/ │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── macos/ │ ├── .gitignore │ ├── Flutter/ │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Runner/ │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ └── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ └── MainMenu.xib │ │ ├── Configs/ │ │ │ ├── AppInfo.xcconfig │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements │ ├── Runner.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ └── xcshareddata/ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ └── Runner.xcscheme │ └── Runner.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist ├── modules/ │ ├── basic_system/ │ │ ├── app/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── app/ │ │ │ │ │ ├── action/ │ │ │ │ │ │ ├── action.dart │ │ │ │ │ │ └── url.dart │ │ │ │ │ ├── cons/ │ │ │ │ │ │ ├── cons.dart │ │ │ │ │ │ ├── global_value.dart │ │ │ │ │ │ ├── path_unit.dart │ │ │ │ │ │ ├── sp.dart │ │ │ │ │ │ └── str_unit.dart │ │ │ │ │ ├── res/ │ │ │ │ │ │ └── toly_icon.dart │ │ │ │ │ ├── router/ │ │ │ │ │ │ └── app_route.dart │ │ │ │ │ ├── style/ │ │ │ │ │ │ ├── behavior/ │ │ │ │ │ │ │ └── no_scroll_behavior.dart │ │ │ │ │ │ ├── gap.dart │ │ │ │ │ │ ├── shape/ │ │ │ │ │ │ │ ├── coupon_shape_border.dart │ │ │ │ │ │ │ └── techno_shape.dart │ │ │ │ │ │ ├── unit_color.dart │ │ │ │ │ │ └── unit_text_style.dart │ │ │ │ │ └── theme/ │ │ │ │ │ ├── app_theme.dart │ │ │ │ │ └── size_unit.dart │ │ │ │ ├── app.dart │ │ │ │ ├── app_config/ │ │ │ │ │ ├── app_config.dart │ │ │ │ │ ├── bloc/ │ │ │ │ │ │ ├── bloc.dart │ │ │ │ │ │ └── state.dart │ │ │ │ │ └── repository/ │ │ │ │ │ └── repository.dart │ │ │ │ ├── event/ │ │ │ │ │ └── api.dart │ │ │ │ ├── http/ │ │ │ │ │ ├── flutter_unit/ │ │ │ │ │ │ ├── api/ │ │ │ │ │ │ │ └── upgrade_api.dart │ │ │ │ │ │ └── unit_host.dart │ │ │ │ │ ├── http.dart │ │ │ │ │ ├── register.dart │ │ │ │ │ └── science/ │ │ │ │ │ ├── science_host.dart │ │ │ │ │ └── science_rep_interceptor.dart │ │ │ │ ├── news/ │ │ │ │ │ ├── cacheable.dart │ │ │ │ │ └── news_bloc.dart │ │ │ │ └── view/ │ │ │ │ ├── about/ │ │ │ │ │ ├── about_app_page.dart │ │ │ │ │ ├── about_me_page.dart │ │ │ │ │ └── version_info.dart │ │ │ │ ├── account/ │ │ │ │ │ └── desk/ │ │ │ │ │ ├── desk_account_page.dart │ │ │ │ │ ├── sliver_cellection_panel.dart │ │ │ │ │ ├── sliver_list_panel.dart │ │ │ │ │ ├── sliver_share_panel.dart │ │ │ │ │ └── user_header.dart │ │ │ │ ├── data_manage/ │ │ │ │ │ └── data_manage_page.dart │ │ │ │ ├── setting/ │ │ │ │ │ ├── app_style_setting.dart │ │ │ │ │ ├── code_style_setting.dart │ │ │ │ │ ├── font_setting.dart │ │ │ │ │ ├── item_style_setting.dart │ │ │ │ │ ├── language_setting.dart │ │ │ │ │ ├── setting_page.dart │ │ │ │ │ ├── theme_color_setting.dart │ │ │ │ │ └── theme_model_setting.dart │ │ │ │ ├── unit_todo/ │ │ │ │ │ ├── attr_unit_page.dart │ │ │ │ │ ├── layout_unit_page.dart │ │ │ │ │ ├── paint_unit_page.dart │ │ │ │ │ └── point_unit_page.dart │ │ │ │ ├── view.dart │ │ │ │ └── wrapper/ │ │ │ │ └── overlay_tool_wrapper.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── app_config_test.dart │ │ ├── authentication/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── authentication.dart │ │ │ │ ├── blocs/ │ │ │ │ │ ├── authentic/ │ │ │ │ │ │ ├── bloc.dart │ │ │ │ │ │ ├── event.dart │ │ │ │ │ │ └── state.dart │ │ │ │ │ ├── register/ │ │ │ │ │ │ ├── bloc.dart │ │ │ │ │ │ ├── event.dart │ │ │ │ │ │ └── state.dart │ │ │ │ │ └── user/ │ │ │ │ │ ├── bloc.dart │ │ │ │ │ └── state.dart │ │ │ │ ├── models/ │ │ │ │ │ └── user.dart │ │ │ │ ├── repository/ │ │ │ │ │ ├── auth_repository.dart │ │ │ │ │ └── impl/ │ │ │ │ │ └── http_auth_repository.dart │ │ │ │ └── views/ │ │ │ │ ├── authentic_widget.dart │ │ │ │ └── mobile/ │ │ │ │ ├── login/ │ │ │ │ │ ├── login_form.dart │ │ │ │ │ └── login_page.dart │ │ │ │ ├── register/ │ │ │ │ │ ├── arc_clipper.dart │ │ │ │ │ ├── register_page.dart │ │ │ │ │ └── send_code.dart │ │ │ │ └── user/ │ │ │ │ ├── page_item.dart │ │ │ │ ├── support_me.dart │ │ │ │ ├── unit_drawer_header.dart │ │ │ │ ├── user_account.dart │ │ │ │ └── user_page.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── authentication_test.dart │ │ ├── components/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── components.dart │ │ │ │ ├── flutter_ui/ │ │ │ │ │ ├── diy_flexible_space_bar.dart │ │ │ │ │ ├── flutter_ui.dart │ │ │ │ │ ├── no_div_expansion_tile.dart │ │ │ │ │ └── toly_date_picker.dart │ │ │ │ └── project_ui/ │ │ │ │ ├── default/ │ │ │ │ │ ├── empty_search_page.dart │ │ │ │ │ ├── empty_shower.dart │ │ │ │ │ ├── error_page.dart │ │ │ │ │ ├── error_shower.dart │ │ │ │ │ ├── loading_shower.dart │ │ │ │ │ └── no_more_widget.dart │ │ │ │ ├── project_ui.dart │ │ │ │ ├── refresh/ │ │ │ │ │ ├── refresh.dart │ │ │ │ │ ├── refresh_config_wrapper.dart │ │ │ │ │ └── toly_refresh_indicator.dart │ │ │ │ ├── time_line/ │ │ │ │ │ ├── flutter_unit_time_line.dart │ │ │ │ │ └── model/ │ │ │ │ │ └── time_node.dart │ │ │ │ ├── top_bar/ │ │ │ │ │ ├── desk_account_top_bar.dart │ │ │ │ │ ├── desk_knowledge_top_bar.dart │ │ │ │ │ ├── desk_simple_top_bar.dart │ │ │ │ │ └── desk_tab_top_bar.dart │ │ │ │ ├── unit_app_bar.dart │ │ │ │ └── wrapper/ │ │ │ │ └── honour_wrapper.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── components_test.dart │ │ ├── fx_updater/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── bloc/ │ │ │ │ │ ├── bloc.dart │ │ │ │ │ ├── event.dart │ │ │ │ │ └── state.dart │ │ │ │ ├── fx_updater.dart │ │ │ │ ├── repository/ │ │ │ │ │ ├── api/ │ │ │ │ │ │ └── upgrade_api.dart │ │ │ │ │ └── model/ │ │ │ │ │ └── app_info.dart │ │ │ │ ├── strategy/ │ │ │ │ │ ├── android_strategy.dart │ │ │ │ │ ├── desktop_strategy.dart │ │ │ │ │ ├── download_mixin.dart │ │ │ │ │ ├── macos_strategy.dart │ │ │ │ │ └── update_strategy.dart │ │ │ │ └── views/ │ │ │ │ ├── app_update_panel.dart │ │ │ │ ├── dialog/ │ │ │ │ │ ├── top_bar.dart │ │ │ │ │ └── update_dialog.dart │ │ │ │ ├── update_red_point.dart │ │ │ │ └── version_shower.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── fx_updater_test.dart │ │ ├── l10n/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── desiredFileName.txt │ │ │ ├── l10n.yaml │ │ │ ├── l10n_copy.sh │ │ │ ├── lib/ │ │ │ │ ├── arb/ │ │ │ │ │ ├── app_en.arb │ │ │ │ │ └── app_zh.arb │ │ │ │ ├── enum/ │ │ │ │ │ └── language.dart │ │ │ │ ├── ext.dart │ │ │ │ ├── gen_l10n/ │ │ │ │ │ ├── app_localizations.dart │ │ │ │ │ ├── app_localizations_en.dart │ │ │ │ │ └── app_localizations_zh.dart │ │ │ │ └── l10n.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── l10n_copy.dart │ │ │ └── l10n_test.dart │ │ ├── storage/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── src/ │ │ │ │ │ ├── app_storage.dart │ │ │ │ │ ├── db_storage/ │ │ │ │ │ │ ├── flutter/ │ │ │ │ │ │ │ ├── article_db_store.dart │ │ │ │ │ │ │ └── flutter_db_store.dart │ │ │ │ │ │ ├── flutter_unit/ │ │ │ │ │ │ │ ├── dao/ │ │ │ │ │ │ │ │ └── cache_dao.dart │ │ │ │ │ │ │ ├── flutter_unit.dart │ │ │ │ │ │ │ ├── flutter_unit_db_store.dart │ │ │ │ │ │ │ └── model/ │ │ │ │ │ │ │ └── cache_po.dart │ │ │ │ │ │ └── storage.dart │ │ │ │ │ └── sp_storage/ │ │ │ │ │ ├── cao/ │ │ │ │ │ │ └── app_config_cao.dart │ │ │ │ │ ├── exp.dart │ │ │ │ │ ├── models/ │ │ │ │ │ │ └── app_config_po.dart │ │ │ │ │ └── sp_storage.dart │ │ │ │ └── storage.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── db_storage_test.dart │ │ ├── toly_ui/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── adapter/ │ │ │ │ │ └── platform_view_adapter.dart │ │ │ │ ├── button/ │ │ │ │ │ └── feedback_widget.dart │ │ │ │ ├── code/ │ │ │ │ │ ├── code.dart │ │ │ │ │ ├── code_widget.dart │ │ │ │ │ ├── high_light_code.dart │ │ │ │ │ ├── highlighter_style.dart │ │ │ │ │ └── language/ │ │ │ │ │ ├── dart_languge.dart │ │ │ │ │ └── language.dart │ │ │ │ ├── decorations/ │ │ │ │ │ └── round_rect_rab_indicator.dart │ │ │ │ ├── default/ │ │ │ │ │ └── loading/ │ │ │ │ │ └── planet_loading.dart │ │ │ │ ├── dialog/ │ │ │ │ │ ├── alert_conform_dialog.dart │ │ │ │ │ └── delete_message_panel.dart │ │ │ │ ├── input/ │ │ │ │ │ ├── edit_panel.dart │ │ │ │ │ ├── icon_input.dart │ │ │ │ │ └── input_button.dart │ │ │ │ ├── markdown/ │ │ │ │ │ ├── markdown_widget.dart │ │ │ │ │ ├── md_text_styles.dart │ │ │ │ │ └── syntax_high_lighter.dart │ │ │ │ ├── object/ │ │ │ │ │ └── windmill.dart │ │ │ │ ├── popable/ │ │ │ │ │ └── drop_selectable_widget.dart │ │ │ │ ├── selector/ │ │ │ │ │ ├── burst_menu.dart │ │ │ │ │ ├── color_chooser.dart │ │ │ │ │ └── multi_chip_filter.dart │ │ │ │ ├── sliver_header/ │ │ │ │ │ ├── sliver_pinned_header.dart │ │ │ │ │ └── sliver_snap_header.dart │ │ │ │ ├── ti/ │ │ │ │ │ ├── circle.dart │ │ │ │ │ ├── circle_image.dart │ │ │ │ │ ├── circle_text.dart │ │ │ │ │ ├── color_wrapper.dart │ │ │ │ │ ├── math_runner.dart │ │ │ │ │ ├── panel.dart │ │ │ │ │ ├── tag.dart │ │ │ │ │ ├── text_typer.dart │ │ │ │ │ └── toly_switch_list_tile.dart │ │ │ │ └── toly_ui.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── toly_ui_test.dart │ │ ├── unit_env/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── src/ │ │ │ │ │ └── host.dart │ │ │ │ └── unit_env.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── unit_env_test.dart │ │ └── utils/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── lib/ │ │ │ ├── src/ │ │ │ │ ├── color_utils.dart │ │ │ │ ├── convert_man.dart │ │ │ │ ├── http_utils/ │ │ │ │ │ ├── http_util.dart │ │ │ │ │ ├── http_utils.dart │ │ │ │ │ ├── logs_interceptor.dart │ │ │ │ │ ├── response_interceptor.dart │ │ │ │ │ ├── task_result.dart │ │ │ │ │ └── token_interceptor.dart │ │ │ │ ├── random_provider.dart │ │ │ │ └── toast.dart │ │ │ └── utils.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ └── utils_test.dart │ ├── knowledge_system/ │ │ ├── algorithm/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── algorithm.dart │ │ │ │ └── src/ │ │ │ │ ├── algorithm/ │ │ │ │ │ ├── finding/ │ │ │ │ │ │ ├── functions/ │ │ │ │ │ │ │ ├── AStar.dart │ │ │ │ │ │ │ ├── BFS.dart │ │ │ │ │ │ │ ├── BestFS.dart │ │ │ │ │ │ │ ├── DFS.dart │ │ │ │ │ │ │ └── dijkstra.dart │ │ │ │ │ │ └── functions.dart │ │ │ │ │ └── sort/ │ │ │ │ │ ├── functions/ │ │ │ │ │ │ ├── bubble.dart │ │ │ │ │ │ ├── cocktail.dart │ │ │ │ │ │ ├── comb.dart │ │ │ │ │ │ ├── cycle.dart │ │ │ │ │ │ ├── gnome.dart │ │ │ │ │ │ ├── heap.dart │ │ │ │ │ │ ├── insertion.dart │ │ │ │ │ │ ├── merge.dart │ │ │ │ │ │ ├── oddEven.dart │ │ │ │ │ │ ├── pigeonHole.dart │ │ │ │ │ │ ├── quick.dart │ │ │ │ │ │ ├── selection.dart │ │ │ │ │ │ └── shell.dart │ │ │ │ │ └── functions.dart │ │ │ │ ├── data_scope/ │ │ │ │ │ ├── sort_config.dart │ │ │ │ │ └── state.dart │ │ │ │ ├── finding/ │ │ │ │ │ ├── data_scope/ │ │ │ │ │ │ ├── finding_config.dart │ │ │ │ │ │ ├── finding_state.dart │ │ │ │ │ │ ├── position.dart │ │ │ │ │ │ └── random_queue.dart │ │ │ │ │ └── view/ │ │ │ │ │ ├── board.dart │ │ │ │ │ ├── finding_button.dart │ │ │ │ │ ├── finding_page.dart │ │ │ │ │ └── finding_tool_bar.dart │ │ │ │ ├── navigation/ │ │ │ │ │ ├── menu/ │ │ │ │ │ │ ├── algo_menu.dart │ │ │ │ │ │ ├── finding.dart │ │ │ │ │ │ └── sort.dart │ │ │ │ │ ├── router/ │ │ │ │ │ │ └── router.dart │ │ │ │ │ └── view/ │ │ │ │ │ ├── algo_desk_navigation.dart │ │ │ │ │ ├── algo_menu_cell.dart │ │ │ │ │ └── algo_menu_tree.dart │ │ │ │ ├── sort/ │ │ │ │ │ ├── data_painter.dart │ │ │ │ │ ├── sort_page.dart │ │ │ │ │ ├── sort_parper.dart │ │ │ │ │ ├── sort_setting.dart │ │ │ │ │ └── top_bar/ │ │ │ │ │ ├── sort_bar.dart │ │ │ │ │ └── sort_button.dart │ │ │ │ └── views/ │ │ │ │ ├── algo_page.dart │ │ │ │ └── desktop/ │ │ │ │ └── desk_algo_panel.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── utils_test.dart │ │ ├── artifact/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── artifact.dart │ │ │ │ └── src/ │ │ │ │ ├── articles/ │ │ │ │ │ ├── bloc/ │ │ │ │ │ │ ├── article/ │ │ │ │ │ │ │ └── bloc.dart │ │ │ │ │ │ ├── columnize/ │ │ │ │ │ │ │ └── bloc.dart │ │ │ │ │ │ └── exp.dart │ │ │ │ │ ├── data/ │ │ │ │ │ │ ├── dao/ │ │ │ │ │ │ │ ├── article_dao.dart │ │ │ │ │ │ │ └── columnize_dao.dart │ │ │ │ │ │ ├── exp.dart │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ ├── article.dart │ │ │ │ │ │ │ └── columnize.dart │ │ │ │ │ │ └── repository/ │ │ │ │ │ │ ├── article_repository.dart │ │ │ │ │ │ └── columnize_repository.dart │ │ │ │ │ └── view/ │ │ │ │ │ ├── article/ │ │ │ │ │ │ ├── article_detail_page.dart │ │ │ │ │ │ ├── column_detail_page.dart │ │ │ │ │ │ ├── columnize_page_view.dart │ │ │ │ │ │ ├── sliver_article.dart │ │ │ │ │ │ ├── sliver_columnize.dart │ │ │ │ │ │ └── toly_article_scroll_page.dart │ │ │ │ │ ├── artifact_page.dart │ │ │ │ │ ├── building/ │ │ │ │ │ │ └── building_panel.dart │ │ │ │ │ └── desk_artifact_page.dart │ │ │ │ └── points/ │ │ │ │ ├── bloc/ │ │ │ │ │ ├── bloc.dart │ │ │ │ │ ├── point_bloc.dart │ │ │ │ │ └── point_comment_bloc.dart │ │ │ │ ├── data/ │ │ │ │ │ ├── api/ │ │ │ │ │ │ ├── app_info.dart │ │ │ │ │ │ ├── category_api.dart │ │ │ │ │ │ └── issues_api.dart │ │ │ │ │ └── model/ │ │ │ │ │ ├── github_model.dart │ │ │ │ │ ├── github_user.dart │ │ │ │ │ ├── issue.dart │ │ │ │ │ ├── issue_comment.dart │ │ │ │ │ ├── license.dart │ │ │ │ │ ├── repository.dart │ │ │ │ │ └── repository_permissions.dart │ │ │ │ ├── exp.dart │ │ │ │ ├── repository/ │ │ │ │ │ └── api/ │ │ │ │ │ └── point_api.dart │ │ │ │ └── view/ │ │ │ │ ├── desk_ui/ │ │ │ │ │ ├── desk_point_page.dart │ │ │ │ │ └── github_repo_panel.dart │ │ │ │ └── issues_point/ │ │ │ │ ├── issue_item.dart │ │ │ │ ├── issues_detail.dart │ │ │ │ ├── issues_point_page.dart │ │ │ │ └── repo_widget.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── utils_test.dart │ │ ├── awesome/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── awesome.dart │ │ │ │ └── listenable/ │ │ │ │ ├── change_notifier_01/ │ │ │ │ │ ├── main.dart │ │ │ │ │ ├── notifier/ │ │ │ │ │ │ └── progress_value_notifier.dart │ │ │ │ │ └── page/ │ │ │ │ │ ├── detail/ │ │ │ │ │ │ ├── detail_progress_view.dart │ │ │ │ │ │ └── download_detail.dart │ │ │ │ │ └── home/ │ │ │ │ │ ├── home_page.dart │ │ │ │ │ └── home_progress_view.dart │ │ │ │ └── change_notifier_02/ │ │ │ │ ├── main.dart │ │ │ │ ├── notifier/ │ │ │ │ │ ├── download_data_scope.dart │ │ │ │ │ └── progress_value_notifier.dart │ │ │ │ └── page/ │ │ │ │ ├── detail/ │ │ │ │ │ ├── detail_progress_view.dart │ │ │ │ │ └── download_detail.dart │ │ │ │ └── home/ │ │ │ │ ├── home_page.dart │ │ │ │ └── home_progress_view.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── awesome_test.dart │ │ ├── layout/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── lib/ │ │ │ │ ├── layout.dart │ │ │ │ └── src/ │ │ │ │ ├── bloc/ │ │ │ │ │ ├── display_logic.dart │ │ │ │ │ └── display_state.dart │ │ │ │ ├── data/ │ │ │ │ │ ├── display_map/ │ │ │ │ │ │ ├── base.dart │ │ │ │ │ │ ├── display_map.dart │ │ │ │ │ │ ├── funny.dart │ │ │ │ │ │ └── multi.dart │ │ │ │ │ └── model/ │ │ │ │ │ └── display_frame.dart │ │ │ │ ├── ext/ │ │ │ │ │ └── go_router/ │ │ │ │ │ ├── listener.dart │ │ │ │ │ └── path.dart │ │ │ │ ├── navigation/ │ │ │ │ │ ├── menu/ │ │ │ │ │ │ ├── base_layout.dart │ │ │ │ │ │ ├── funny.dart │ │ │ │ │ │ ├── layout.dart │ │ │ │ │ │ ├── menu_repository_impl.dart │ │ │ │ │ │ ├── multi.dart │ │ │ │ │ │ ├── popable.dart │ │ │ │ │ │ └── scroll.dart │ │ │ │ │ ├── router/ │ │ │ │ │ │ ├── app_router.dart │ │ │ │ │ │ ├── desk_router.dart │ │ │ │ │ │ └── transition/ │ │ │ │ │ │ ├── fade_page_transitions_builder.dart │ │ │ │ │ │ ├── page_route/ │ │ │ │ │ │ │ ├── fade_page_route.dart │ │ │ │ │ │ │ ├── slide_page_route.dart │ │ │ │ │ │ │ └── zero_page_route.dart │ │ │ │ │ │ ├── size_clip_transition.dart │ │ │ │ │ │ └── slide_transition/ │ │ │ │ │ │ ├── cupertino_back_gesture_detector.dart │ │ │ │ │ │ └── slide_page_transition_builder.dart │ │ │ │ │ └── view/ │ │ │ │ │ ├── app_desk_navigation.dart │ │ │ │ │ └── app_menu_tree.dart │ │ │ │ └── views/ │ │ │ │ ├── base/ │ │ │ │ │ ├── align/ │ │ │ │ │ │ ├── align_show.dart │ │ │ │ │ │ └── align_show2.dart │ │ │ │ │ ├── padding/ │ │ │ │ │ │ ├── inner_padding.dart │ │ │ │ │ │ ├── outer_padding.dart │ │ │ │ │ │ └── sizedbox_padding.dart │ │ │ │ │ ├── positioned/ │ │ │ │ │ │ └── positioned_show.dart │ │ │ │ │ └── size/ │ │ │ │ │ ├── size_display.dart │ │ │ │ │ ├── size_loss_by_align.dart │ │ │ │ │ ├── size_tight_constraint.dart │ │ │ │ │ └── size_unconstraint.dart │ │ │ │ ├── components/ │ │ │ │ │ └── grid_xy_layout.dart │ │ │ │ ├── display/ │ │ │ │ │ ├── layout_playground.dart │ │ │ │ │ ├── playground_bottom_bar.dart │ │ │ │ │ └── playground_top_bar.dart │ │ │ │ ├── interest/ │ │ │ │ │ └── elevator/ │ │ │ │ │ └── elevator.dart │ │ │ │ ├── layout_page.dart │ │ │ │ ├── multi/ │ │ │ │ │ └── flex/ │ │ │ │ │ ├── column_show.dart │ │ │ │ │ └── row_show.dart │ │ │ │ ├── playground/ │ │ │ │ │ ├── cons.dart │ │ │ │ │ ├── data/ │ │ │ │ │ │ ├── flex_attr.dart │ │ │ │ │ │ ├── stack_attr.dart │ │ │ │ │ │ └── wrap_attr.dart │ │ │ │ │ └── view/ │ │ │ │ │ ├── display_item.dart │ │ │ │ │ ├── flex/ │ │ │ │ │ │ ├── flex_op_panel.dart │ │ │ │ │ │ └── flex_playground.dart │ │ │ │ │ ├── form_item/ │ │ │ │ │ │ ├── item_selector.dart │ │ │ │ │ │ ├── item_size_input.dart │ │ │ │ │ │ └── value_input.dart │ │ │ │ │ ├── stack/ │ │ │ │ │ │ ├── stack_op_panel.dart │ │ │ │ │ │ └── stack_playground.dart │ │ │ │ │ └── wrap/ │ │ │ │ │ ├── wrap_op_panel.dart │ │ │ │ │ └── wrap_playground.dart │ │ │ │ ├── popable/ │ │ │ │ │ ├── autocomplete_demo.dart │ │ │ │ │ ├── dropdown_button_demo.dart │ │ │ │ │ └── dropdown_menu_demo.dart │ │ │ │ ├── scroll/ │ │ │ │ │ ├── grid_view/ │ │ │ │ │ │ └── grid_view_demo01.dart │ │ │ │ │ ├── list_view/ │ │ │ │ │ │ └── list_view_demo01.dart │ │ │ │ │ └── page_view/ │ │ │ │ │ └── page_view_demo01.dart │ │ │ │ └── test_show.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── layout_test.dart │ │ └── note/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── lib/ │ │ │ ├── note.dart │ │ │ └── src/ │ │ │ ├── bloc/ │ │ │ │ ├── bloc.dart │ │ │ │ └── news_bloc.dart │ │ │ ├── env/ │ │ │ │ └── env.dart │ │ │ ├── repository/ │ │ │ │ ├── article_repository.dart │ │ │ │ ├── model/ │ │ │ │ │ ├── article.dart │ │ │ │ │ ├── category.dart │ │ │ │ │ ├── model.dart │ │ │ │ │ ├── query.dart │ │ │ │ │ └── status.dart │ │ │ │ └── repository.dart │ │ │ └── view/ │ │ │ ├── art_sys_scope.dart │ │ │ ├── article_admin.dart │ │ │ ├── article_editor.dart │ │ │ ├── article_item.dart │ │ │ ├── article_list.dart │ │ │ ├── components/ │ │ │ │ └── button/ │ │ │ │ └── button.dart │ │ │ ├── desktop/ │ │ │ │ └── article_display.dart │ │ │ ├── mobile/ │ │ │ │ ├── mobile_article_list.dart │ │ │ │ ├── mobile_article_page.dart │ │ │ │ ├── mobile_editor.dart │ │ │ │ └── note.dart │ │ │ ├── news/ │ │ │ │ └── news_page.dart │ │ │ └── view.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── note_test.dart │ │ └── parser.dart │ ├── painting_system/ │ │ └── draw_system/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── lib/ │ │ │ ├── draw_system.dart │ │ │ └── src/ │ │ │ ├── anim/ │ │ │ │ ├── bezier3_player/ │ │ │ │ │ ├── bezier3_palyer.dart │ │ │ │ │ └── touch_info.dart │ │ │ │ ├── circle_halo.dart │ │ │ │ ├── curve_shower/ │ │ │ │ │ ├── anim_painter.dart │ │ │ │ │ ├── curve_anim_shower.dart │ │ │ │ │ └── point_data.dart │ │ │ │ ├── draw_path.dart │ │ │ │ ├── dundun_path.dart │ │ │ │ ├── rotate_by_point/ │ │ │ │ │ ├── angle_panter.dart │ │ │ │ │ ├── line.dart │ │ │ │ │ └── rotate_by_point.dart │ │ │ │ └── spring_widget.dart │ │ │ ├── art/ │ │ │ │ ├── circle_packing.dart │ │ │ │ ├── cubic_disarray.dart │ │ │ │ ├── hypnotic_squares.dart │ │ │ │ ├── joy_division.dart │ │ │ │ ├── piet_mondrian.dart │ │ │ │ ├── tiled_lines.dart │ │ │ │ ├── triangular_mesh.dart │ │ │ │ └── un_deux_trois.dart │ │ │ ├── base/ │ │ │ │ ├── clock_widget.dart │ │ │ │ ├── digital/ │ │ │ │ │ ├── digital_painter.dart │ │ │ │ │ ├── digital_path.dart │ │ │ │ │ ├── digital_shower.dart │ │ │ │ │ └── digital_widget.dart │ │ │ │ ├── draw_grid_axis.dart │ │ │ │ ├── draw_path_fun.dart │ │ │ │ ├── draw_picture.dart │ │ │ │ ├── n_side/ │ │ │ │ │ ├── n_side_page.dart │ │ │ │ │ └── shape_painter.dart │ │ │ │ ├── polar/ │ │ │ │ │ ├── angle_painter.dart │ │ │ │ │ ├── polar.dart │ │ │ │ │ └── polar_painter_widget.dart │ │ │ │ └── windmill.dart │ │ │ ├── bloc/ │ │ │ │ └── gallery_unit/ │ │ │ │ └── bloc.dart │ │ │ ├── desk_ui/ │ │ │ │ ├── desk_frame.dart │ │ │ │ └── desk_gallery_unit.dart │ │ │ ├── fun/ │ │ │ │ ├── bufeng/ │ │ │ │ │ ├── bufeng_panel.dart │ │ │ │ │ ├── config.dart │ │ │ │ │ └── painter.dart │ │ │ │ ├── dundun_view.dart │ │ │ │ ├── random_portrait.dart │ │ │ │ └── stemp/ │ │ │ │ ├── stamp_data.dart │ │ │ │ └── stamp_paper.dart │ │ │ ├── gallery_card_item.dart │ │ │ ├── gallery_detail_page.dart │ │ │ ├── gallery_factory.dart │ │ │ ├── gallery_unit.dart │ │ │ ├── particle/ │ │ │ │ ├── out/ │ │ │ │ │ ├── clock_fx.dart │ │ │ │ │ ├── clock_widget.dart │ │ │ │ │ ├── particle.dart │ │ │ │ │ └── rnd.dart │ │ │ │ ├── random/ │ │ │ │ │ ├── particle.dart │ │ │ │ │ ├── particle_manage.dart │ │ │ │ │ ├── random_particle.dart │ │ │ │ │ └── world_render.dart │ │ │ │ ├── split/ │ │ │ │ │ ├── particle.dart │ │ │ │ │ ├── particle_manage.dart │ │ │ │ │ ├── particle_split.dart │ │ │ │ │ └── world_render.dart │ │ │ │ └── split_img/ │ │ │ │ ├── particle.dart │ │ │ │ ├── particle_manage.dart │ │ │ │ ├── split_image.dart │ │ │ │ └── world_render.dart │ │ │ ├── picture_frame.dart │ │ │ └── utils/ │ │ │ ├── colors.dart │ │ │ └── coordinate.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ └── draw_system_test.dart │ ├── tools_system/ │ │ ├── pkg_player/ │ │ │ ├── .amazonq/ │ │ │ │ └── rules/ │ │ │ │ ├── dart.md │ │ │ │ └── file_create.md │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── analysis_options.yaml │ │ │ ├── desiredFileName.txt │ │ │ ├── devtools_options.yaml │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── analysis_options.yaml │ │ │ │ ├── android/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ │ ├── main/ │ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ │ ├── kotlin/ │ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ │ └── example/ │ │ │ │ │ │ │ │ └── example/ │ │ │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ │ │ │ └── res/ │ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ ├── drawable-v21/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ ├── values/ │ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ │ └── values-night/ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ └── profile/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── devtools_options.yaml │ │ │ │ ├── ios/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── Runner-Bridging-Header.h │ │ │ │ │ ├── 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 │ │ │ │ │ └── RunnerTests/ │ │ │ │ │ └── RunnerTests.swift │ │ │ │ ├── lib/ │ │ │ │ │ ├── app_theme.dart │ │ │ │ │ └── main.dart │ │ │ │ ├── linux/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── flutter/ │ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ │ ├── generated_plugin_registrant.cc │ │ │ │ │ │ ├── generated_plugin_registrant.h │ │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ │ └── runner/ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── main.cc │ │ │ │ │ ├── my_application.cc │ │ │ │ │ └── my_application.h │ │ │ │ ├── macos/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ │ ├── Flutter-Release.xcconfig │ │ │ │ │ │ └── GeneratedPluginRegistrant.swift │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ │ └── Release.entitlements │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── RunnerTests/ │ │ │ │ │ └── RunnerTests.swift │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── test/ │ │ │ │ │ └── widget_test.dart │ │ │ │ ├── web/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── manifest.json │ │ │ │ └── windows/ │ │ │ │ ├── .gitignore │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── flutter/ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── generated_plugin_registrant.cc │ │ │ │ │ ├── generated_plugin_registrant.h │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ └── runner/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Runner.rc │ │ │ │ ├── flutter_window.cpp │ │ │ │ ├── flutter_window.h │ │ │ │ ├── main.cpp │ │ │ │ ├── resource.h │ │ │ │ ├── runner.exe.manifest │ │ │ │ ├── utils.cpp │ │ │ │ ├── utils.h │ │ │ │ ├── win32_window.cpp │ │ │ │ └── win32_window.h │ │ │ ├── fx.yaml │ │ │ ├── l10n.yaml │ │ │ ├── lib/ │ │ │ │ ├── pkg_player.dart │ │ │ │ └── src/ │ │ │ │ ├── bloc/ │ │ │ │ │ ├── bloc.dart │ │ │ │ │ ├── category/ │ │ │ │ │ │ ├── category_cubit.dart │ │ │ │ │ │ └── category_state.dart │ │ │ │ │ ├── comments/ │ │ │ │ │ │ ├── comment_replies_cubit.dart │ │ │ │ │ │ ├── comment_replies_state.dart │ │ │ │ │ │ ├── comments_cubit.dart │ │ │ │ │ │ └── comments_state.dart │ │ │ │ │ └── packages/ │ │ │ │ │ ├── package_cubit.dart │ │ │ │ │ └── package_state.dart │ │ │ │ ├── l10n/ │ │ │ │ │ ├── arb/ │ │ │ │ │ │ ├── l10n_en.arb │ │ │ │ │ │ └── l10n_zh.arb │ │ │ │ │ ├── gen/ │ │ │ │ │ │ ├── l10n.dart │ │ │ │ │ │ ├── l10n_en.dart │ │ │ │ │ │ └── l10n_zh.dart │ │ │ │ │ └── l10n.dart │ │ │ │ ├── repository/ │ │ │ │ │ ├── api/ │ │ │ │ │ │ ├── request.dart │ │ │ │ │ │ └── url.dart │ │ │ │ │ ├── database/ │ │ │ │ │ │ ├── dao/ │ │ │ │ │ │ │ ├── category_dao.dart │ │ │ │ │ │ │ ├── dao.dart │ │ │ │ │ │ │ ├── package_dao.dart │ │ │ │ │ │ │ ├── package_service.dart │ │ │ │ │ │ │ ├── tag_dao.dart │ │ │ │ │ │ │ └── topic_dao.dart │ │ │ │ │ │ ├── database.dart │ │ │ │ │ │ └── database_helper.dart │ │ │ │ │ ├── model/ │ │ │ │ │ │ ├── category_model.dart │ │ │ │ │ │ ├── comment_model.dart │ │ │ │ │ │ ├── model.dart │ │ │ │ │ │ ├── plugin_model.dart │ │ │ │ │ │ └── sort_type.dart │ │ │ │ │ └── repository.dart │ │ │ │ └── view/ │ │ │ │ ├── comments/ │ │ │ │ │ ├── comment_replies_page.dart │ │ │ │ │ ├── comments_detail_page.dart │ │ │ │ │ ├── comments_empty.dart │ │ │ │ │ ├── comments_error.dart │ │ │ │ │ ├── comments_loading.dart │ │ │ │ │ ├── comments_top_bar.dart │ │ │ │ │ └── comments_with_data.dart │ │ │ │ ├── components/ │ │ │ │ │ ├── card/ │ │ │ │ │ │ ├── plugin_card.dart │ │ │ │ │ │ └── plugin_card_page.dart │ │ │ │ │ └── dialog/ │ │ │ │ │ └── sort_picker.dart │ │ │ │ ├── home/ │ │ │ │ │ ├── empty_list.dart │ │ │ │ │ ├── pkg_list_with_data.dart │ │ │ │ │ ├── pkg_player_home_page.dart │ │ │ │ │ ├── plugin_item.dart │ │ │ │ │ └── recommendation_page.dart │ │ │ │ ├── package_detail/ │ │ │ │ │ ├── comments_section.dart │ │ │ │ │ ├── detail.dart │ │ │ │ │ ├── detail_flexible_bar.dart │ │ │ │ │ ├── plugin_dependencies_section.dart │ │ │ │ │ ├── plugin_detail_page.dart │ │ │ │ │ ├── plugin_info_section.dart │ │ │ │ │ └── plugin_tags.dart │ │ │ │ └── view.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── pkg_player_test.dart │ │ │ ├── science_server/ │ │ │ │ ├── moke/ │ │ │ │ │ ├── category.json │ │ │ │ │ └── moke.dart │ │ │ │ ├── package.dart │ │ │ │ ├── sync/ │ │ │ │ │ ├── model/ │ │ │ │ │ │ ├── package_detail.dart │ │ │ │ │ │ └── pub_package.dart │ │ │ │ │ ├── package_repository.dart │ │ │ │ │ └── pub_repository.dart │ │ │ │ ├── sync.dart │ │ │ │ ├── system.dart │ │ │ │ └── test_comments.dart │ │ │ └── scripts/ │ │ │ ├── analyze_packages.dart │ │ │ ├── find_untranslated.dart │ │ │ ├── get_ipv4.dart │ │ │ ├── insert_chinese_desc.dart │ │ │ └── update_dev_ip.dart │ │ └── treasure_tools/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── lib/ │ │ │ ├── src/ │ │ │ │ ├── bloc/ │ │ │ │ │ └── state.dart │ │ │ │ ├── class_generator.dart │ │ │ │ ├── code_gen_page.dart │ │ │ │ ├── data/ │ │ │ │ │ └── task_result.dart │ │ │ │ ├── desk_widget_top_bar.dart │ │ │ │ ├── icon_font_gen/ │ │ │ │ │ ├── gen_message_action.dart │ │ │ │ │ ├── icon_font_class_parser.dart │ │ │ │ │ ├── icon_font_gen_config.dart │ │ │ │ │ └── icon_font_gen_page.dart │ │ │ │ ├── model/ │ │ │ │ │ ├── class.dart │ │ │ │ │ └── field.dart │ │ │ │ ├── popable/ │ │ │ │ │ ├── class_gen_field.dart │ │ │ │ │ └── toly_select.dart │ │ │ │ ├── view/ │ │ │ │ │ ├── json_display/ │ │ │ │ │ │ └── json_display.dart │ │ │ │ │ └── mobile/ │ │ │ │ │ └── mobile_tool_page.dart │ │ │ │ └── wrapper.dart │ │ │ └── treasure_tools.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── iconfont_parser_test.dart │ │ ├── treasure_tools_test.dart │ │ ├── yaml_parser_test.dart │ │ └── yaml_parser_test2.dart │ └── widget_system/ │ ├── widget_module/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── lib/ │ │ │ ├── blocs/ │ │ │ │ ├── action/ │ │ │ │ │ └── widget_action.dart │ │ │ │ ├── blocs.dart │ │ │ │ ├── category_bloc/ │ │ │ │ │ ├── category_bloc.dart │ │ │ │ │ ├── category_event.dart │ │ │ │ │ └── category_state.dart │ │ │ │ ├── category_widget_bloc/ │ │ │ │ │ ├── category_widget_bloc.dart │ │ │ │ │ ├── category_widget_event.dart │ │ │ │ │ └── category_widget_state.dart │ │ │ │ ├── widget_detail_bloc/ │ │ │ │ │ ├── widget_detail_bloc.dart │ │ │ │ │ └── widget_detail_state.dart │ │ │ │ └── widgets_bloc/ │ │ │ │ ├── widgets_bloc.dart │ │ │ │ ├── widgets_event.dart │ │ │ │ └── widgets_state.dart │ │ │ ├── event/ │ │ │ │ ├── widget_event.dart │ │ │ │ └── widget_statistics_event.dart │ │ │ ├── views/ │ │ │ │ ├── components/ │ │ │ │ │ ├── collected_tag.dart │ │ │ │ │ └── widget_logo_map.dart │ │ │ │ ├── desk_ui/ │ │ │ │ │ ├── category_panel/ │ │ │ │ │ │ ├── desk_category_page.dart │ │ │ │ │ │ └── desk_top_like_panel.dart │ │ │ │ │ ├── desk_ui.dart │ │ │ │ │ ├── widget_detail/ │ │ │ │ │ │ ├── link_widget_buttons.dart │ │ │ │ │ │ ├── widget_detail_bar.dart │ │ │ │ │ │ ├── widget_detail_page.dart │ │ │ │ │ │ ├── widget_detail_panel.dart │ │ │ │ │ │ └── widget_node_panel.dart │ │ │ │ │ └── widget_panel/ │ │ │ │ │ ├── desk_search_bar.dart │ │ │ │ │ ├── desk_search_bar_v2.dart │ │ │ │ │ ├── desk_widget_top_bar.dart │ │ │ │ │ └── widget_panel.dart │ │ │ │ ├── mobile/ │ │ │ │ │ ├── category_page/ │ │ │ │ │ │ ├── category_detail.dart │ │ │ │ │ │ ├── category_list_item.dart │ │ │ │ │ │ ├── category_page.dart │ │ │ │ │ │ ├── collect_page.dart │ │ │ │ │ │ ├── delete_category_dialog.dart │ │ │ │ │ │ ├── edit_category_panel.dart │ │ │ │ │ │ ├── empty_category.dart │ │ │ │ │ │ ├── home_right_drawer.dart │ │ │ │ │ │ ├── like_widget_page.dart │ │ │ │ │ │ └── sync/ │ │ │ │ │ │ ├── async_button.dart │ │ │ │ │ │ ├── category_api.dart │ │ │ │ │ │ └── upload_button.dart │ │ │ │ │ ├── mobile_ui.dart │ │ │ │ │ ├── search_page/ │ │ │ │ │ │ ├── app_search_bar.dart │ │ │ │ │ │ ├── standard_search_bar.dart │ │ │ │ │ │ └── standard_search_page.dart │ │ │ │ │ ├── widget_detail/ │ │ │ │ │ │ ├── category_end_drawer.dart │ │ │ │ │ │ ├── collect_widget_list_item.dart │ │ │ │ │ │ ├── node_display/ │ │ │ │ │ │ │ ├── code_display.dart │ │ │ │ │ │ │ ├── collapse.dart │ │ │ │ │ │ │ ├── node_display.dart │ │ │ │ │ │ │ └── node_title.dart │ │ │ │ │ │ ├── widget_detail_bar.dart │ │ │ │ │ │ ├── widget_detail_page.dart │ │ │ │ │ │ ├── widget_detail_panel.dart │ │ │ │ │ │ ├── widget_fields_sliver.dart │ │ │ │ │ │ └── widget_node_panel.dart │ │ │ │ │ └── widget_page/ │ │ │ │ │ ├── home_drawer.dart │ │ │ │ │ ├── phone_widget_content.dart │ │ │ │ │ ├── slider.dart │ │ │ │ │ ├── standard_home_page.dart │ │ │ │ │ ├── standard_home_search.dart │ │ │ │ │ ├── unit_drawer_header.dart │ │ │ │ │ ├── widget_list_panel.dart │ │ │ │ │ └── widget_page.dart │ │ │ │ └── widgets_bloc_provider.dart │ │ │ └── widget_module.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ └── widget_module_test.dart │ ├── widget_repository/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── doc/ │ │ │ └── tables_overview.md │ │ ├── lib/ │ │ │ ├── src/ │ │ │ │ ├── database/ │ │ │ │ │ ├── dao/ │ │ │ │ │ │ ├── category_dao.dart │ │ │ │ │ │ ├── like_dao.dart │ │ │ │ │ │ ├── node_dao.dart │ │ │ │ │ │ ├── widget_dao.dart │ │ │ │ │ │ └── widget_statistics_dao.dart │ │ │ │ │ ├── database.dart │ │ │ │ │ ├── db_impl/ │ │ │ │ │ │ ├── category_db_repository.dart │ │ │ │ │ │ ├── node_db_repository.dart │ │ │ │ │ │ └── widget_db_repository.dart │ │ │ │ │ └── po/ │ │ │ │ │ ├── category_po.dart │ │ │ │ │ ├── node_po.dart │ │ │ │ │ └── widget_po.dart │ │ │ │ ├── memory/ │ │ │ │ │ ├── memory_node_repository.dart │ │ │ │ │ └── memory_widget_repository.dart │ │ │ │ ├── model/ │ │ │ │ │ ├── category_model.dart │ │ │ │ │ ├── model.dart │ │ │ │ │ ├── node_model.dart │ │ │ │ │ ├── widget_field_model.dart │ │ │ │ │ ├── widget_filter.dart │ │ │ │ │ ├── widget_model.dart │ │ │ │ │ └── widget_statistics.dart │ │ │ │ └── repository/ │ │ │ │ ├── category_repository.dart │ │ │ │ ├── node_repository.dart │ │ │ │ ├── repository.dart │ │ │ │ ├── widget_repository.dart │ │ │ │ ├── widget_statistics_provider.dart │ │ │ │ └── widget_statistics_service.dart │ │ │ └── widget_repository.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ └── widget_repository_test.dart │ └── widget_ui/ │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── lib/ │ │ ├── src/ │ │ │ ├── bloc/ │ │ │ │ ├── bloc.dart │ │ │ │ └── liked_widget_bloc.dart │ │ │ └── view/ │ │ │ ├── field/ │ │ │ │ ├── filed.dart │ │ │ │ ├── widget_fields_dialog.dart │ │ │ │ └── widget_fields_page.dart │ │ │ ├── node_tiled/ │ │ │ │ └── node_tiled.dart │ │ │ ├── view.dart │ │ │ └── widget_tiled/ │ │ │ ├── widget_detail_logo.dart │ │ │ ├── widget_fields_dialog.dart │ │ │ ├── widget_fields_page.dart │ │ │ ├── widget_id_view.dart │ │ │ ├── widget_item.dart │ │ │ ├── widget_like_tag.dart │ │ │ ├── widget_logo.dart │ │ │ └── widget_tiled.dart │ │ └── widget_ui.dart │ ├── pubspec.yaml │ └── test/ │ └── widget_ui_test.dart ├── pubspec.yaml ├── test/ │ ├── app_update_test.dart │ ├── size.dart │ └── widget_test.dart ├── web/ │ ├── index.html │ ├── manifest.json │ └── splash.js └── windows/ ├── .gitignore ├── CMakeLists.txt ├── flutter/ │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake └── runner/ ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ /lib/temp # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ /android/app/.cxx/ /lib/tools/ /lib/res/constant/github_client_config.dart # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages /build_tools /libs /pubspec_overrides.yaml modules/game_system ================================================ FILE: .metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled. version: revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 channel: stable project_type: app # Tracks metadata for the flutter migrate command migration: platforms: - platform: root create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 - platform: android create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 - platform: ios create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 - platform: linux create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 - platform: macos create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 - platform: web create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 - platform: windows create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 # User provided section # List of Local paths (relative to this file) that should be # ignored by the migrate tool. # # Files that are not part of the templates will be ignored by default. unmanaged_files: - 'lib/main.dart' - 'ios/Runner.xcodeproj/project.pbxproj' ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README-EN.md ================================================

FlutterUnit🔖
⭐️ All Platform Flutter Experience App ⭐️

FlutterUnit is a cross-platform experience app, Here, you can fully explore the creativity that Flutter offers.

License: GPL-3.0

Download v3.0.0 : [Android] [iOS][MacOS][Windows][Web]

FlutterUnit App

--- ### Env and Build #### Flutter Version ``` ·]>> flutter --version Flutter 3.38.3 • channel stable • https://github.com/flutter/flutter.git Framework • revision 19074d12f7 (9 days ago) • 2025-11-20 17:53:13 -0500 Engine • hash 8bf2090718fea3655f466049a757f823898f0ad1 (revision 13e658725d) (8 days ago) • 2025-11-20 20:19:23.000Z Tools • Dart 3.10.1 • DevTools 2.51.1 ``` #### Build Application ``` ·]>> git clone https://github.com/toly1994328/FlutterUnit.git ·]>> cd FlutterUnit Build Android: ·]>> flutter build apk --release --target-platform android-arm64 --split-per-abi -v Build iOS: ·]>> flutter build ios Build Windows: ·]>> flutter build windows Build Linux: ·]>> flutter build linux Build web: ·]>> flutter build web ``` #### My Flutter Books - 🔥 [免费] [掘金小册 -《Flutter 入门教程》](https://juejin.cn/book/7212822723330834487) - 🔥 [掘金小册 -《Flutter 语言基础 - 梦始之地》](https://juejin.cn/book/6844733827617652750) - 🔥 [掘金小册 -《Flutter 绘制指南 - 妙笔生花》](https://juejin.im/book/6844733827265331214) - 🔥 [掘金小册 -《Flutter 手势探索 - 执掌天下》](https://juejin.cn/book/6896378716427911181) - 🔥 [掘金小册 -《Flutter 动画探索 - 流光幻影》](https://juejin.cn/book/6965102582473687071) - 🔥 [掘金小册 -《Flutter 滑动探索 - 珠联璧合》](https://juejin.cn/book/6984685333312962573) - 🔥 [掘金小册 -《Flutter 布局探索 - 薪火相传》](https://juejin.cn/book/7075958265250578469) - 🔥 [掘金小册 -《Flutter 渲染机制 - 聚沙成塔》](https://juejin.cn/book/6965102582473687071) --- - [Flutter环境配置](https://github.com/toly1994328/FlutterUnit/issues/22) - [Flutter实用插件集录 ](https://github.com/toly1994328/FlutterUnit/issues/41) - [Flutter要点集录 ](https://github.com/toly1994328/FlutterUnit/labels/point) --- #### MacOS 桌面版本组件界面 ![](./doc/screens/macos-2.webp) #### Windows 桌面版本组件界面 ![](./doc/screens/windows-1.png) > 开源不易,请我喝咖啡 ~ ![](./doc/ewm/coffee1.png) #### Star History [![Star History Chart](https://api.star-history.com/svg?repos=toly1994328/FlutterUnit&type=Date)](https://star-history.com/#toly1994328/FlutterUnit&Date) ### 一、组件的展示页面 #### 1. `300+组件收录` > Flutter源码中的可用的组件一共350个左右,纷繁复杂,也没有明确的分类标准 FlutterUnit 对`大大小小,常用不常用`的组件能收的尽量收录。并`根据个人感觉进行评星 ` `目前收录组件306个`,每个都有至少一个演示展现和代码展示。 | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6acf7b8a1d~tplv-t2oaga2asx-zoom-1.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6ad06db455~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6ad085632b~tplv-t2oaga2asx-image.image) | --- #### 2. 组件详情页 > `213个组件`全部都有详情页。对于重要的组件会详细展现 一般都会有某个演示对应的组件和属性,尽量做到细致,如果有需要补充,欢迎联系我。 `最重要的是: 所有的演示展现都是Flutter的组件形成的,而非图片,这就意味着可操作性更高。` | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6ad8ba98f1~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6afb3841c4~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6afb590185~tplv-t2oaga2asx-image.image) | | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b0ad26b14~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b13d3fb5b~tplv-t2oaga2asx-image.image) |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b15efec19~tplv-t2oaga2asx-image.image)| --- #### 3. 组件的可操作性 > 对一些操作交互的组件或有可操作性的某些组件,`提供操作演示` | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b177c5b67~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b21cc116a~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b2486b5a5~tplv-t2oaga2asx-image.image)| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b34887a94~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b3ca09b47~tplv-t2oaga2asx-image.image) |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b3d4e0253~tplv-t2oaga2asx-image.image)| --- #### 4. 相关组件的关联切换 > `相关组件通过link to 可以进行切换, 满足你的探索欲。` 如果有的关联未加入,欢迎联系我,对我来说,加个数字就行了。 | . | . | . | |------|------------|------------| |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b4a4d6005~tplv-t2oaga2asx-image.image)|![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b5066fbf0~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b5359b695~tplv-t2oaga2asx-image.image)| --- #### 5. 代码的查看和分享 > 激动人心的是,你可以通过右侧的图标`展开/隐藏 实现下面效果的代码` 并且`支持分享`,如果你想亲自体验,so,easy ! 而且`代码高亮样式可以自定义`。 | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b6badc1bb~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b6e75653c~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b72cdd78f~tplv-t2oaga2asx-image.image)| --- ### 二、全局配置 #### 1. 颜色主题 > 只提供八种颜色,可在`右滑菜单页`的`我的主题`配置,`可以拓展` | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c2e937170~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c3253c4ec~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c4f97f74d~tplv-t2oaga2asx-image.image) | --- #### 2.字体配置 > 支持全局字体设置,`可以拓展` | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c5448cb6c~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c55542837~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c5bec6c56~tplv-t2oaga2asx-image.image)| --- #### 3.item样式设置 > 支持item样式设置,`可以拓展,支持征集`,详见`Flutter Unit 1.0 征集方案` | . | . | . | |------|------------|------------| |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c7d4b5988~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c8935dfe1~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c8f90d6ec~tplv-t2oaga2asx-image.image)| --- #### 4.代码面板风格设置 > 支持代码风格设置,`可以拓展,支持征集`,详见`Flutter Unit 1.0 征集方案` | . | . | |------|------------| |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6cac86d591~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6cac7d5bc7~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6cb75e5450~tplv-t2oaga2asx-image.image)|![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6cd88c7ff9~tplv-t2oaga2asx-image.image)| --- ### 三、搜索与收藏功能 #### 1.搜索功能 > 由于Flutter中Widget比较杂乱,不太好分类,所以搜索是非常重要的 另外可以根据星级进行过滤,支持多选。目前正在考虑根据功能分类,之后会有所完善。 | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c1a355ad3~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c211dfc99~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c28562ec5~tplv-t2oaga2asx-image.image) | --- #### 2.收藏功能 | 添加收藏集 | 修改收藏集 | 删除收藏集 | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b7979f4ae~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b97f00113~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6ba47f3fd2~tplv-t2oaga2asx-image.image) | | 长按右菜单滑页 | 长按左菜单滑页 | 详情内长按展示收藏菜单 | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6beb370b8b~tplv-t2oaga2asx-image.image) |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6befe43cd2~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6bfd3cd42f~tplv-t2oaga2asx-image.image) | | 删除与数据同步 | 组件加入收藏集 | 收藏集支持多选 | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6ba47ab64c~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6badf6ee28~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6bb380c7f5~tplv-t2oaga2asx-image.image) | > `FlutterUnit 2.0 `目前基本就是这么多功能,可以在Github中下载打包后的apk玩玩 希望能对你的Flutter学习有所帮助。 --- ================================================ FILE: README.md ================================================

FlutterUnit
⭐️ 全平台 Flutter 探索应用 ⭐️

FlutterUnit 是一个全平台体验应用,你可以在这里尽情体验 Flutter 带来的创造力。

License: GPL-3.0

下载 App: [Android] [iOS][MacOS][Windows][Web]

FlutterUnit App

--- ### 环境与构建 #### Flutter 版本 ``` ·]>> flutter --version Flutter 3.38.3 • channel stable • https://github.com/flutter/flutter.git Framework • revision 19074d12f7 (9 days ago) • 2025-11-20 17:53:13 -0500 Engine • hash 8bf2090718fea3655f466049a757f823898f0ad1 (revision 13e658725d) (8 days ago) • 2025-11-20 20:19:23.000Z Tools • Dart 3.10.1 • DevTools 2.51.1 ``` #### 构建应用 ``` ·]>> git clone https://github.com/toly1994328/FlutterUnit.git ·]>> cd FlutterUnit Build Android: ·]>> flutter build apk --release --target-platform android-arm64 --split-per-abi -v Build iOS: ·]>> flutter build ios Build Windows: ·]>> flutter build windows Build Linux: ·]>> flutter build linux Build web: ·]>> flutter build web ``` #### Flutter Unit 周边 - 🔥 [免费] [掘金小册 -《Flutter 入门教程》](https://juejin.cn/book/7212822723330834487) - 🔥 [掘金小册 -《Flutter 语言基础 - 梦始之地》](https://juejin.cn/book/6844733827617652750) - 🔥 [掘金小册 -《Flutter 绘制指南 - 妙笔生花》](https://juejin.im/book/6844733827265331214) - 🔥 [掘金小册 -《Flutter 手势探索 - 执掌天下》](https://juejin.cn/book/6896378716427911181) - 🔥 [掘金小册 -《Flutter 动画探索 - 流光幻影》](https://juejin.cn/book/6965102582473687071) - 🔥 [掘金小册 -《Flutter 滑动探索 - 珠联璧合》](https://juejin.cn/book/6984685333312962573) - 🔥 [掘金小册 -《Flutter 布局探索 - 薪火相传》](https://juejin.cn/book/7075958265250578469) - 🔥 [掘金小册 -《Flutter 渲染机制 - 聚沙成塔》](https://juejin.cn/book/6965102582473687071) --- - [Flutter环境配置](https://github.com/toly1994328/FlutterUnit/issues/22) - [Flutter实用插件集录 ](https://github.com/toly1994328/FlutterUnit/issues/41) - [Flutter要点集录 ](https://github.com/toly1994328/FlutterUnit/labels/point) --- #### MacOS 桌面版本组件界面 ![](./doc/screens/macos-2.webp) #### Windows 桌面版本组件界面 ![](./doc/screens/windows-1.png) > 开源不易,请我喝咖啡 ~ ![](./doc/ewm/coffee1.png) #### Star History [![Star History Chart](https://api.star-history.com/svg?repos=toly1994328/FlutterUnit&type=Date)](https://star-history.com/#toly1994328/FlutterUnit&Date) ### 一、组件的展示页面 #### 1. `300+组件收录` > Flutter源码中的可用的组件一共350个左右,纷繁复杂,也没有明确的分类标准 FlutterUnit 对`大大小小,常用不常用`的组件能收的尽量收录。并`根据个人感觉进行评星 ` `目前收录组件306个`,每个都有至少一个演示展现和代码展示。 | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6acf7b8a1d~tplv-t2oaga2asx-zoom-1.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6ad06db455~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6ad085632b~tplv-t2oaga2asx-image.image) | --- #### 2. 组件详情页 > `213个组件`全部都有详情页。对于重要的组件会详细展现 一般都会有某个演示对应的组件和属性,尽量做到细致,如果有需要补充,欢迎联系我。 `最重要的是: 所有的演示展现都是Flutter的组件形成的,而非图片,这就意味着可操作性更高。` | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6ad8ba98f1~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6afb3841c4~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6afb590185~tplv-t2oaga2asx-image.image) | | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b0ad26b14~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b13d3fb5b~tplv-t2oaga2asx-image.image) |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b15efec19~tplv-t2oaga2asx-image.image)| --- #### 3. 组件的可操作性 > 对一些操作交互的组件或有可操作性的某些组件,`提供操作演示` | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b177c5b67~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b21cc116a~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b2486b5a5~tplv-t2oaga2asx-image.image)| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b34887a94~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b3ca09b47~tplv-t2oaga2asx-image.image) |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b3d4e0253~tplv-t2oaga2asx-image.image)| --- #### 4. 相关组件的关联切换 > `相关组件通过link to 可以进行切换, 满足你的探索欲。` 如果有的关联未加入,欢迎联系我,对我来说,加个数字就行了。 | . | . | . | |------|------------|------------| |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b4a4d6005~tplv-t2oaga2asx-image.image)|![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b5066fbf0~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b5359b695~tplv-t2oaga2asx-image.image)| --- #### 5. 代码的查看和分享 > 激动人心的是,你可以通过右侧的图标`展开/隐藏 实现下面效果的代码` 并且`支持分享`,如果你想亲自体验,so,easy ! 而且`代码高亮样式可以自定义`。 | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b6badc1bb~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b6e75653c~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b72cdd78f~tplv-t2oaga2asx-image.image)| --- ### 二、全局配置 #### 1. 颜色主题 > 只提供八种颜色,可在`右滑菜单页`的`我的主题`配置,`可以拓展` | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c2e937170~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c3253c4ec~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c4f97f74d~tplv-t2oaga2asx-image.image) | --- #### 2.字体配置 > 支持全局字体设置,`可以拓展` | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c5448cb6c~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c55542837~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c5bec6c56~tplv-t2oaga2asx-image.image)| --- #### 3.item样式设置 > 支持item样式设置,`可以拓展,支持征集`,详见`Flutter Unit 1.0 征集方案` | . | . | . | |------|------------|------------| |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c7d4b5988~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c8935dfe1~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c8f90d6ec~tplv-t2oaga2asx-image.image)| --- #### 4.代码面板风格设置 > 支持代码风格设置,`可以拓展,支持征集`,详见`Flutter Unit 1.0 征集方案` | . | . | |------|------------| |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6cac86d591~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6cac7d5bc7~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6cb75e5450~tplv-t2oaga2asx-image.image)|![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6cd88c7ff9~tplv-t2oaga2asx-image.image)| --- ### 三、搜索与收藏功能 #### 1.搜索功能 > 由于Flutter中Widget比较杂乱,不太好分类,所以搜索是非常重要的 另外可以根据星级进行过滤,支持多选。目前正在考虑根据功能分类,之后会有所完善。 | . | . | . | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c1a355ad3~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c211dfc99~tplv-t2oaga2asx-image.image)| ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6c28562ec5~tplv-t2oaga2asx-image.image) | --- #### 2.收藏功能 | 添加收藏集 | 修改收藏集 | 删除收藏集 | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b7979f4ae~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6b97f00113~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6ba47f3fd2~tplv-t2oaga2asx-image.image) | | 长按右菜单滑页 | 长按左菜单滑页 | 详情内长按展示收藏菜单 | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6beb370b8b~tplv-t2oaga2asx-image.image) |![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6befe43cd2~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6bfd3cd42f~tplv-t2oaga2asx-image.image) | | 删除与数据同步 | 组件加入收藏集 | 收藏集支持多选 | |------|------------|------------| | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6ba47ab64c~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6badf6ee28~tplv-t2oaga2asx-image.image) | ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae6bb380c7f5~tplv-t2oaga2asx-image.image) | > `FlutterUnit 2.0 `目前基本就是这么多功能,可以在Github中下载打包后的apk玩玩 希望能对你的Flutter学习有所帮助。 --- ================================================ FILE: analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: rules: avoid_print: false # Uncomment to disable the `avoid_print` rule file_names: false analyzer: # exclude: # - modules/widget_system/widgets/** ================================================ FILE: android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties **/*.keystore **/*.jks ================================================ FILE: android/app/build.gradle.kts ================================================ plugins { id("com.android.application") id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } android { namespace = "com.toly1994.flutter_unit" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion packagingOptions { jniLibs { useLegacyPackaging = true } dex { useLegacyPackaging = true } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.toly1994.flutter_unit" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } buildTypes { getByName("release") { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.getByName("debug") isShrinkResources = true // 移除未使用的资源 isMinifyEnabled = true // 启用 R8 代码压缩 proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) ndk { debugSymbolLevel = "none" } } } } flutter { source = "../.." } ================================================ FILE: android/app/proguard-rules.pro ================================================ -dontwarn javax.annotation.** -keep class javax.annotation.** { *; } ================================================ FILE: android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: android/app/src/main/kotlin/com/toly1994/flutter_unit/MainActivity.kt ================================================ package com.toly1994.flutter_unit import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() { } ================================================ FILE: android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: android/build/reports/problems/problems-report.html ================================================ Gradle Configuration Cache
Loading...
================================================ FILE: android/build.gradle.kts ================================================ allprojects { repositories { google() mavenCentral() } } val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() rootProject.layout.buildDirectory.value(newBuildDir) subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) } subprojects { project.evaluationDependsOn(":app") } tasks.register("clean") { delete(rootProject.layout.buildDirectory) } ================================================ FILE: android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip ================================================ FILE: android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true ================================================ FILE: android/settings.gradle.kts ================================================ pluginManagement { val flutterSdkPath = run { val properties = java.util.Properties() file("local.properties").inputStream().use { properties.load(it) } val flutterSdkPath = properties.getProperty("flutter.sdk") require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } flutterSdkPath } includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.3" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false } include(":app") ================================================ FILE: assets/data/gallery_info.json ================================================ [ { "image": "assets/images/anim_draw.webp", "name": "基础绘制", "info": "收录一些基础图形绘制案例,这些案例对初涉绘制的编程者会非常友好。通过这些案例,可以学会点、线、矩形、圆、圆弧、文字、图片等基本图形的绘制方法,了解 Canvas、Paint、Path 等绘制中核心对象的使用。" }, { "image": "assets/images/draw_bg3.webp", "name": "动画手势", "info": "收录一些动画和手势的绘制案例,这些案例会让绘制更具有操作性。通过这些案例,可以学会动画和手势的使用,如滑动、旋转、缩放、移动等效果,让绘制不再只是静态展现。" }, { "image": "assets/images/base_draw.webp", "name": "粒子绘制", "info": "收录一些粒子相关的绘制案例,这些案例将是绘制的顶级操作。通过这些案例,可以学会如何使用粒子来绘制惊艳的视觉效果,如粒子时钟、粒子爆炸、粒子背景等效果,让绘制拥有无限可能。" }, { "image": "assets/images/draw_bg4.webp", "name": "趣味绘制", "info": "收录一些比较有趣的绘制案例,让我们一起在这里一起体验绘制的乐趣、编程的乐趣和智慧的乐趣吧。" }, { "image": "assets/images/caver.webp", "name": "艺术画廊", "info": "收录一些殿堂级的绘制案例,这些案例将是绘制的巅峰作品,它们的没有任何的实用性,也不为任何需求而生,它们仅是因为存在而存在,是人类智慧和表达的媒介,称谓艺术。" } ] ================================================ FILE: assets/data/packages/data.json ================================================ [ { "name": "crypto", "last_update": '' } ] ================================================ FILE: assets/data/web/node.json ================================================ [{"id":null,"widgetId":9,"name":"CircleAvatar的表现","priority":1,"subtitle":"【radius】 : 半径 【double】\n【backgroundImage】 : 图片资源 【ImageProvider】\n【foregroundColor】: 前景色 【Color】\n【backgroundColor】: 背景色 【Color】\n【minRadius】: 最小半径 【double】\n【maxRadius】: 最大半径 【double】\n【child】: 孩子组件 【Child】","code":"import 'package:flutter/material.dart';\nclass CustomCircleAvatar extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return CircleAvatar(\n radius: 50,\n backgroundImage: AssetImage(\"assets/images/wy_200x300.jpg\"),\n foregroundColor: Colors.white,\n child: Icon(\n Icons.check,\n size: 50,\n ),\n );\n }\n}\n"},{"id":null,"widgetId":19,"name":"RadioListTile需要一个泛型T","priority":1,"subtitle":"【value】 : 条目对象 【T】\n【groupValue】 : 选中对象 【T】\n【selected】: 是否选中 【bool】\n【secondary】: 右侧组件 【Widget】\n【title】: 中间上组件 【Widget】\n【subtitle】: 中间下组件 【Widget】\n【onChanged】: 切换事件 【Function(T)】","code":"import 'package:flutter/material.dart';\nclass ItemBean {\n final String title;\n final String subTitle;\n final String imgUrl;\n\n ItemBean(this.title, this.subTitle, this.imgUrl);\n}\n\nclass CustomRadioListTile extends StatefulWidget {\n @override\n _CustomRadioListTileState createState() => _CustomRadioListTileState();\n}\n\nclass _CustomRadioListTileState extends State {\n final Map languages = {\n ItemType.java:\n ItemBean(\"Java\", \"曾经世界上最流行的语言\", \"assets/images/java.jpeg\"),\n ItemType.kotlin:\n ItemBean(\"Kotlin\", \"未来世界上最流行的语言\", \"assets/images/kotlin.jpg\"),\n ItemType.dart:\n ItemBean(\"Dart\", \"世界上最优雅的语言\", \"assets/images/dart.jpg\"),\n };\n var _type = ItemType.java;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n color: Colors.grey.withAlpha(11),\n child: Column(\n mainAxisSize: MainAxisSize.min,\n children: languages.keys\n .map((type) => RadioListTile(\n value: type,\n groupValue: _type,\n title: Text(languages[type].title),\n subtitle: Text(languages[type].subTitle),\n selected: _type == type,\n secondary: CircleAvatar(\n backgroundImage: AssetImage(languages[type].imgUrl),\n ),\n onChanged: (type) => setState(() => _type = type),\n ))\n .toList()),\n );\n }\n}\n"},{"id":null,"widgetId":19,"name":"RadioListTile选中色和密排","priority":2,"subtitle":"【activeColor】 : 选中时颜色 【Color】\n【dense】: 是否密排 【bool】","code":"import 'package:flutter/material.dart';\nclass ItemBean {\n final String title;\n final String subTitle;\n final String imgUrl;\n\n ItemBean(this.title, this.subTitle, this.imgUrl);\n}\n\nclass DenseRadioListTile extends StatefulWidget {\n @override\n _DenseRadioListTileState createState() => _DenseRadioListTileState();\n}\n\nclass _DenseRadioListTileState extends State {\n final Map languages = {\n ItemType.java:\n ItemBean(\"Java\", \"曾经世界上最流行的语言\", \"assets/images/java.jpeg\"),\n ItemType.kotlin:\n ItemBean(\"Kotlin\", \"未来世界上最流行的语言\", \"assets/images/kotlin.jpg\"),\n ItemType.dart:\n ItemBean(\"Dart\", \"世界上最优雅的语言\", \"assets/images/dart.jpg\"),\n };\n var _type = ItemType.java;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n color: Colors.grey.withAlpha(11),\n child: Column(\n mainAxisSize: MainAxisSize.min,\n children: languages.keys\n .map((type) => RadioListTile(\n value: type,\n groupValue: _type,\n title: Text(languages[type].title),\n activeColor: Colors.orangeAccent,\n dense: true,\n subtitle: Text(languages[type].subTitle),\n selected: _type == type,\n secondary: CircleAvatar(\n backgroundImage: AssetImage(languages[type].imgUrl),\n ),\n onChanged: (type) => setState(() => _type = type),\n ))\n .toList()),\n );\n }\n}"},{"id":null,"widgetId":28,"name":"mini属性","priority":2,"subtitle":"【mini】: 是否是迷你 【bool】","code":"import 'package:flutter/material.dart';\nclass MiniFAB extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var data = {\n Colors.red: Icons.add,\n Colors.blue: Icons.bluetooth,\n Colors.green: Icons.android,\n };\n return Wrap(\n spacing: 20,\n children: data.keys\n .map((e) => FloatingActionButton(\n heroTag: e.toString()+\"b\",\n onPressed: () {},\n backgroundColor: e,\n mini: true,\n foregroundColor: Colors.white,\n child: Icon(data[e]),\n tooltip: \"android\",\n elevation: 5, //z-阴影盖度\n ))\n .toList());\n }\n}"},{"id":null,"widgetId":28,"name":"FloatingActionButton点击事件","priority":1,"subtitle":"【child】: 子组件 【Widget】\n【tooltip】: 长按时提示文字 【String】\n【backgroundColor】: 背景色 【Color】\n【foregroundColor】: 前景色 【Color】\n【elevation】: 影深 【double】\n【onPressed】: 点击事件 【Function】","code":"import 'package:flutter/material.dart';\nclass CustomFAB extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var data = {\n Colors.red: Icons.add,\n Colors.blue: Icons.bluetooth,\n Colors.green: Icons.android,\n };\n return Wrap(\n spacing: 20,\n children: data.keys\n .map((e) => FloatingActionButton(\n heroTag: e.toString()+\"a\",\n onPressed: () {},\n backgroundColor: e,\n foregroundColor: Colors.white,\n child: Icon(data[e]),\n tooltip: \"android\",\n elevation: 5, //z-阴影盖度\n ))\n .toList());\n }\n}"},{"id":null,"widgetId":28,"name":"shape属性","priority":3,"subtitle":"【shape】: 形状 【ShapeBorder】","code":"import 'dart:math';\nimport 'package:flutter/material.dart';\nimport '../../../../app/utils/pather.dart';\nclass ShapeFAB extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var data = {\n Colors.red: Icons.add,\n Colors.blue: Icons.bluetooth,\n Colors.green: Icons.android,\n };\n return Wrap(\n spacing: 20,\n children: data.keys\n .map((e) => FloatingActionButton(\n heroTag: e.toString()+\"c\",\n onPressed: () {},\n backgroundColor: e,\n shape: StarBorder(),\n foregroundColor: Colors.white,\n child: Icon(data[e]),\n tooltip: \"android\",\n elevation: 5,\n ))\n .toList());\n }\n}\n\n/// 边线形状类\nclass StarBorder extends ShapeBorder {\n @override\n EdgeInsetsGeometry get dimensions => null;\n\n @override\n Path getInnerPath(Rect rect, {TextDirection textDirection}) {\n return null;\n }\n\n @override\n Path getOuterPath(Rect rect, {TextDirection textDirection}) {\n return Pather.create.nStarPath(20, 25, 25 * cos((360 / 9 / 2) * pi / 180),\n dx: rect.height / 2, dy: rect.width / 2);\n }\n\n @override\n void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}\n\n @override\n ShapeBorder scale(double t) {\n return null;\n }\n}"},{"id":null,"widgetId":154,"name":"Drawer基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【elevation】 : 影深 【double】","code":"import 'package:flutter/material.dart';\nclass CustomDrawer extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 400,\n child: Scaffold(\n appBar: AppBar(\n title: Text('Flutter Unit'),\n ),\n drawer: Drawer(\n elevation: 3,\n child: _buildChild(),\n ),\n ),\n );\n }\n\n Widget _buildChild() => ListView(\n padding: EdgeInsets.zero,\n children: const [\n DrawerHeader(\n decoration: BoxDecoration(\n image: DecorationImage(\n image: AssetImage('assets/images/caver.jpeg'),\n fit: BoxFit.cover),\n ),\n child: Text(\n '张风捷特烈',\n style: TextStyle(fontSize: 24, color: Colors.white, shadows: [\n Shadow(color: Colors.black, offset: Offset(1, 1), blurRadius: 3)\n ]),\n ),\n ),\n ListTile(\n leading: Icon(\n Icons.star,\n color: Colors.blue,\n ),\n title: Text('我的收藏'),\n ),\n ListTile(\n leading: Icon(\n Icons.palette,\n color: Colors.orangeAccent,\n ),\n title: Text('我的绘画'),\n ),\n ListTile(\n leading: Icon(\n Icons.insert_drive_file,\n color: Colors.green,\n ),\n title: Text('我的文件'),\n ),\n ],\n );\n}\n"},{"id":null,"widgetId":134,"name":"DayPicker基本使用","priority":1,"subtitle":" \n【selectedDate】 : 选中日期 【DateTime】\n【currentDate】 : 当前日期 【DateTime】\n【firstDate】 : 最前日期限制 【DateTime】\n【lastDate】 : 最后日期限制 【DateTime】\n【displayedMonth】 : 当前展示的月份 【DateTime】\n【onChanged】 : 点击回调 【Function(DateTime)】","code":"import 'package:flutter/material.dart';\nclass CustomDayPicker extends StatefulWidget {\n @override\n _CustomDayPickerState createState() => _CustomDayPickerState();\n}\n\nclass _CustomDayPickerState extends State {\n\n DateTime _date = DateTime.now();\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 350,\n child: DayPicker(\n selectedDate: _date,\n currentDate: DateTime.now(),\n onChanged: (date){\n setState(() => _date = date);\n },\n firstDate: DateTime(2018),\n lastDate: DateTime(2030),\n displayedMonth: DateTime.now()\n ),\n );\n }\n}\n"},{"id":null,"widgetId":27,"name":"OutlineButton点击事件","priority":1,"subtitle":"【textColor】: 子组件文字颜色 【Color】\n【splashColor】: 水波纹颜色 【Color】\n【highlightColor】: 长按高亮色 【Color】\n【highlightedBorderColor】: 高亮时框色 【Color】\n【child】: 子组件 【Widget】\n【padding】: 内边距 【EdgeInsetsGeometry】\n【borderSide】: 边线 【BorderSide】\n【onPressed】: 点击事件 【Function】","code":"import 'package:flutter/material.dart';\nclass CustomOutlineButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return OutlineButton(//边线按钮\n onPressed: () {},\n child: Text(\"OutlineButton\"),\n padding: EdgeInsets.all(8),\n splashColor: Colors.green,\n highlightColor: Colors.orangeAccent,\n highlightedBorderColor: Colors.grey,\n textColor: Color(0xff000000),\n borderSide: BorderSide(color: Color(0xff0A66F8), width: 2),\n );\n }\n}\n"},{"id":null,"widgetId":147,"name":"Listener基本事件","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【onPointerDown】 : 按下事件 【Function(PointerDownEvent)】\n【onPointerMove】 : 移动事件 【Function(PointerMoveEvent)】\n【onPointerMove】 : 抬起事件 【Function(PointerUpEvent)】\n【onPointerCancel】 : 取消事件 【Function(PointerUpEvent)】","code":"import 'package:flutter/material.dart';\nclass CustomListener extends StatefulWidget {\n @override\n _CustomListenerState createState() => _CustomListenerState();\n}\n\nclass _CustomListenerState extends State {\n var _info = '';\n\n @override\n Widget build(BuildContext context) {\n return Listener(\n onPointerDown: (detail) => setState(() => _info = detail.toString()),\n onPointerMove: (detail) => setState(() => _info = detail.toString()),\n onPointerUp: (detail) => setState(() => _info = detail.toString()),\n onPointerCancel: (detail) => setState(() => _info = detail.toString()),\n\n child: Container(\n alignment: Alignment.center,\n width: 300,\n height: 300 * 0.618,\n color: Colors.grey.withAlpha(33),\n child: Text(\n _info,\n style: TextStyle(fontSize: 16, color: Colors.blue),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":159,"name":"PositionedDirectional基本使用","priority":1,"subtitle":" \n【child】 : 组件 【Widget】\n【top】 : 到父顶距离 【double】\n【end】 : 到父右距离 【double】\n【start】 : 到父左距离 【double】\n【bottom】 : 到父底距离 【double】","code":"import 'package:flutter/material.dart';\nclass CustomPositionedDirectional extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var yellowBox = Container(\n color: Colors.yellow,\n height: 100,\n width: 100,\n );\n\n var redBox = Container(\n color: Colors.red,\n height: 90,\n width: 90,\n );\n\n var greenBox = Container(\n color: Colors.green,\n height: 80,\n width: 80,\n );\n\n var cyanBox = Container(\n color: Colors.cyanAccent,\n height: 70,\n width: 70,\n );\n\n return Container(\n width: 200,\n height: 120,\n color: Colors.grey.withAlpha(33),\n child: Stack(\n children: [\n yellowBox,\n redBox,\n PositionedDirectional(top: 20, start: 20, child: greenBox),\n PositionedDirectional(\n child: cyanBox,\n bottom: 10,\n end: 10,\n )\n ],\n ));\n }\n}\n"},{"id":null,"widgetId":3,"name":"可以通过shape属性实现裁切效果","priority":2,"subtitle":"【shape】 : 形状 【ShapeBorder】\n【margin】: 外边距 【double】\n【color】: 颜色 【Color】\n【child】: 孩子 【Widget】","code":"import 'package:flutter/material.dart';\nimport '../../../../app/utils/pather.dart';\nclass ShapeCard extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Card(\n color: Color(0xffB3FE65),\n elevation: 6,\n shape: StarShapeBorder(),\n child: Container(\n alignment: Alignment.center,\n width: 100,\n height: 100,\n child: Text(\"Card\", style: TextStyle(fontSize: 20)),\n ),\n );\n }\n}\n\nclass StarShapeBorder extends ShapeBorder {\n @override\n EdgeInsetsGeometry get dimensions => null;\n\n @override\n Path getInnerPath(Rect rect, {TextDirection textDirection}) {\n return null;\n }\n\n @override\n Path getOuterPath(Rect rect, {TextDirection textDirection}) =>\n Pather.create.nStarPath(9, 50, 40, dx: 50, dy: 50);\n\n @override\n void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {\n }\n\n @override\n ShapeBorder scale(double t) {\n return null;\n }\n}"},{"id":null,"widgetId":3,"name":"Card可以让一个组件卡片化","priority":1,"subtitle":"【elevation】 : 影深 【double】\n【margin】: 外边距 【double】\n【color】: 颜色 【Color】\n【child】: 孩子 【Widget】","code":"import 'package:flutter/material.dart';\nclass CustomCard extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Card(\n color: Color(0xffB3FE65),\n elevation: 4,\n margin: EdgeInsets.all(10),\n child: Container(\n alignment: Alignment.topLeft,\n width: 200,\n height: 0.618*200,\n margin: EdgeInsets.all(10),\n child: Text(\"Card\", style: TextStyle(fontSize: 20)),\n ),\n );\n }\n}"},{"id":null,"widgetId":204,"name":"PreferredSize的转化使用","priority":2,"subtitle":"【PreferredSize将普通组件转化为PreferredSizeWidget","code":"import 'package:flutter/material.dart';\nclass AdapterPreferredSize extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: Scaffold(\n appBar: PreferredSize(\n preferredSize: Size.fromHeight(150),\n child: AppBar(\n title: Text('PreferredSize'),\n bottom: PreferredSize(\n preferredSize: Size.fromHeight(40),\n child: Container(\n height: 40,\n color: Colors.orange,\n ),\n ),\n ),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":204,"name":"PreferredSize调整AppBar高度","priority":1,"subtitle":"【preferredSize】 : 尺寸 【Size】","code":"import 'package:flutter/material.dart';\nclass CustomPreferredSize extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: Scaffold(\n appBar: PreferredSize(\n preferredSize: Size.fromHeight(150),\n child: AppBar(\n title: Text('PreferredSize'),\n ),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":29,"name":"ButtonBar边距和高","priority":2,"subtitle":"【buttonPadding】: 内边距 【EdgeInsetsGeometry】\n【buttonHeight】: 高 【double】","code":"import 'package:flutter/material.dart';\nimport '../../../dialogs/dialog_about.dart';\nclass PaddingButtonBar extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ButtonBar(\n alignment: MainAxisAlignment.center,\n buttonHeight: 40,\n buttonPadding: EdgeInsets.only(left: 15,right: 15),\n children: [\n RaisedButton(\n color: Colors.blue,\n child: Text(\"Raised\"),\n onPressed: () => DialogAbout.show(context)),\n OutlineButton(\n child: Text(\"Outline\"),\n onPressed: () => DialogAbout.show(context)),\n FlatButton(\n color: Colors.blue,\n onPressed: () => DialogAbout.show(context),\n child: Text(\"Flat\"),\n )\n ],\n );\n }\n}\n"},{"id":null,"widgetId":29,"name":"ButtonBar对齐方式","priority":1,"subtitle":"【alignment】: 对齐方式 【MainAxisAlignment】\n【children】: 子组件集 【List】","code":"import 'package:flutter/material.dart';\nimport '../../../dialogs/dialog_about.dart';\nclass CustomButtonBar extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ButtonBar(\n alignment: MainAxisAlignment.center,\n children: [\n RaisedButton(\n color: Colors.blue,\n child: Text(\"Raised\"),\n onPressed: () => DialogAbout.show(context)),\n OutlineButton(\n child: Text(\"Outline\"),\n onPressed: () => DialogAbout.show(context)),\n FlatButton(\n color: Colors.blue,\n onPressed: () => DialogAbout.show(context),\n child: Text(\"Flat\"),\n )\n ],\n );\n }\n}\n\n"},{"id":null,"widgetId":146,"name":"GestureDetector基本事件","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【onTap】 : 点击事件 【Function()】\n【onDoubleTap】 : 双击事件 【Function()】\n【onLongPress】 : 长按事件 【Function()】","code":"import 'package:flutter/material.dart';\nclass CustomGestureDetector extends StatefulWidget {\n @override\n _CustomGestureDetectorState createState() => _CustomGestureDetectorState();\n}\n\nclass _CustomGestureDetectorState extends State {\n var _info = '';\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () => setState(() => _info = 'onTap'),\n onDoubleTap: () => setState(() => _info = 'onDoubleTap'),\n onLongPress: () => setState(() => _info = 'onLongPress'),\n child: Container(\n alignment: Alignment.center,\n width: 300,\n height: 300 * 0.4,\n color: Colors.grey.withAlpha(33),\n child: Text(\n _info,\n style: TextStyle(fontSize: 18, color: Colors.blue),\n ),\n ),\n );\n }\n}"},{"id":null,"widgetId":146,"name":"GestureDetector详情信息","priority":2,"subtitle":" \n【onTapDown】 : 按下回调 【Function(TapDownDetails)】\n【onTapUp】 : 子组件 【Function(TapUpDetails)】\n【onTapCancel】 : 点击取消 【Function()】","code":"import 'package:flutter/material.dart';\nclass TapGestureDetector extends StatefulWidget {\n @override\n _TapGestureDetectorState createState() => _TapGestureDetectorState();\n}\n\nclass _TapGestureDetectorState extends State {\n var _info = '';\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTapDown: (detail) => setState(() => _info =\n 'onTapDown:\\n相对落点:${detail.localPosition}\\n绝对落点:${detail.globalPosition}'),\n onTapUp: (detail) => setState(() => _info =\n 'onTapUp:\\n相对落点:${detail.localPosition}\\n绝对落点:${detail.globalPosition}'),\n onTapCancel: () => setState(() => _info = 'onTapCancel'),\n child: Container(\n alignment: Alignment.center,\n width: 300,\n height: 300 * 0.618,\n color: Colors.grey.withAlpha(33),\n child: Text(\n _info,\n style: TextStyle(fontSize: 18, color: Colors.blue),\n ),\n ),\n );\n }\n}"},{"id":null,"widgetId":146,"name":"GestureDetector的Pan事件","priority":3,"subtitle":" \n【onPanDown】 : 按下回调 【Function(DragDownDetails)】\n【onPanEnd】 : 拖动结束 【Function(DragEndDetails)】\n【onPanStart】 : 开始拖动 【Function(DragStartDetails)】\n【onPanUpdate】 : 拖动更新 【Function(TapUpDetails)】\n【onPanCancel】 : 拖动取消 【Function()】","code":"import 'package:flutter/material.dart';\nclass PanGestureDetector extends StatefulWidget {\n @override\n _PanGestureDetectorState createState() => _PanGestureDetectorState();\n}\n\nclass _PanGestureDetectorState extends State {\n var _info = '';\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onPanDown: (detail) => setState(() => _info =\n 'onPanDown:\\n相对落点:${detail.localPosition}\\n绝对落点:${detail.globalPosition}'),\n onPanEnd: (detail) => setState(() => _info =\n 'onPanEnd:\\n初速度:${detail.primaryVelocity}\\n最终速度:${detail.velocity}'),\n onPanUpdate: (detail) => setState(() => _info =\n 'onPanUpdate:\\n相对落点:${detail.localPosition}\\n绝对落点:${detail.globalPosition}'),\n onPanStart: (detail) => setState(() => _info =\n 'onPanStart:\\n相对落点:${detail.localPosition}\\n绝对落点:${detail.globalPosition}'),\n onPanCancel: () => setState(() => _info = 'onTapCancel'),\n child: SingleChildScrollView(\n child: Container(\n alignment: Alignment.center,\n width: 300,\n height: 300 * 0.618,\n color: Colors.grey.withAlpha(33),\n child: Text(\n _info,\n style: TextStyle(fontSize: 18, color: Colors.blue),\n ),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":13,"name":"ActionChip的普通表现如下","priority":1,"subtitle":"【onPressed】: 点击事件 【Function】\n【pressElevation】: 按下时影深 【double】\n其他属性同Chip组件,无右侧组件。","code":"import 'package:flutter/material.dart';\nimport '../../../dialogs/dialog_about.dart';\nclass CustomActionChip extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ActionChip(\n padding: EdgeInsets.all(5),\n labelPadding: EdgeInsets.all(3),\n label: Text(\"This is a ActionChip.\"),\n backgroundColor: Colors.grey.withAlpha(66),\n avatar: Image.asset(\"assets/images/icon_head.png\"),\n shadowColor: Colors.orangeAccent,\n elevation: 3,\n pressElevation: 5,\n onPressed: ()=> DialogAbout.show(context),\n );\n }\n}\n"},{"id":null,"widgetId":26,"name":"RaisedButton点击事件","priority":1,"subtitle":"【color】: 颜色 【Color】\n【splashColor】: 水波纹颜色 【Color】\n【elevation】: 影深 【double】\n【child】: 子组件 【Widget】\n【textColor】: 子组件文字颜色 【Color】\n【highlightColor】: 长按高亮色 【Color】\n【padding】: 内边距 【EdgeInsetsGeometry】\n【onPressed】: 点击事件 【Function】","code":"import 'package:flutter/material.dart';\nclass CustomRaisedButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return RaisedButton(\n color: Colors.blue,\n splashColor: Colors.green,\n onPressed: () {},\n child: Text(\"RaisedButton\"),\n textColor: Color(0xffFfffff),\n padding: EdgeInsets.all(8),\n elevation: 5,\n highlightColor: Color(0xffF88B0A),\n );\n }\n}\n"},{"id":null,"widgetId":211,"name":"MaterialBanner两行的使用","priority":2,"subtitle":"【contentTextStyle】: 中间位置样式 【TextStyle】\n【leadingPadding】: 左侧组件边距 【EdgeInsetsGeometry】\n当尾部组件数量大于1,该组件结构为左中下。","code":"import 'package:flutter/material.dart';\nclass MaterialBannerDemoTwo extends StatelessWidget {\n final info =\n 'A banner displays an important, succinct message, and provides actions for users to address. '\n 'A user action is required for itto be dismissed.';\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [MaterialBanner(\n content: Text(\n info,\n style: TextStyle(color: Colors.white),\n ),\n backgroundColor: Colors.purple,\n leading: Icon(Icons.warning, color: Colors.yellow),\n padding: EdgeInsetsDirectional.only(start: 16.0, top: 2.0,end: 2),\n leadingPadding:EdgeInsetsDirectional.only(end: 16.0) ,\n actions: [\n RaisedButton(\n color: Colors.white,\n onPressed: () {},\n child: Text(\n 'I KNOW',\n style: TextStyle(\n color: Colors.purple,\n fontWeight: FontWeight.bold,\n fontSize: 14),\n ),\n ),\n\n RaisedButton(\n color: Colors.white,\n onPressed: () {},\n child: Text(\n 'I IGNORE',\n style: TextStyle(\n color: Colors.purple,\n fontWeight: FontWeight.bold,\n fontSize: 14),\n ),\n ),\n ],\n )],\n );\n }\n\n}\n"},{"id":null,"widgetId":211,"name":"MaterialBanner一行的使用","priority":1,"subtitle":"【content】 : 中间组件 【Widget】\n【leading】: 左侧组件 【Widget】\n【actions】: 右侧组件列表 【List】\n【padding】: 内边距 【EdgeInsetsGeometry】\n【forceActionsBelow】: 是否按钮在下方 【bool】\n【backgroundColor】: 背景色 【Color】","code":"import 'package:flutter/material.dart';\nclass MaterialBannerDemo extends StatelessWidget {\n final info =\n 'Welcome to Flutter Unit!';\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [MaterialBanner(\n content: Text(\n info,\n style: TextStyle(color: Colors.white),\n ),\n backgroundColor: Colors.purple,\n leading: Icon(Icons.info, color: Colors.lightBlueAccent),\n padding: EdgeInsetsDirectional.only(start: 16.0, top: 2.0),\n forceActionsBelow: false, // 默认false\n actions: [\n Text(\n 'I KNOW',\n style:TextStyle(\n color: Colors.orange,\n fontWeight: FontWeight.bold,\n fontSize: 14) ,\n )\n ],\n )],\n );\n }\n\n}\n"},{"id":null,"widgetId":164,"name":"SingleChildScrollView基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【padding】 : 点击事件 【EdgeInsetsGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomSingleChildScrollView extends StatelessWidget {\n final data = [\n Colors.blue[50],\n Colors.blue[100],\n Colors.blue[200],\n Colors.blue[300],\n Colors.blue[400],\n Colors.blue[500],\n Colors.blue[600],\n Colors.blue[700],\n Colors.blue[800],\n Colors.blue[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: SingleChildScrollView(\n padding: EdgeInsets.symmetric(horizontal: 10),\n child: Column(\n children: data\n .map((color) => Container(\n alignment: Alignment.center,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n ),\n\n ),\n );\n }\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":164,"name":"SingleChildScrollView滑动方向","priority":2,"subtitle":" \n【scrollDirection】 : 滑动方向 【Axis】\n【reverse】 : 是否反向 【Axis】","code":"import 'package:flutter/material.dart';\nclass DirectionSingleChildScrollView extends StatelessWidget {\n final data = [\n Colors.blue[50],\n Colors.blue[100],\n Colors.blue[200],\n Colors.blue[300],\n Colors.blue[400],\n Colors.blue[500],\n Colors.blue[600],\n Colors.blue[700],\n Colors.blue[800],\n Colors.blue[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: SingleChildScrollView(\n scrollDirection: Axis.horizontal,\n reverse: true,\n padding: EdgeInsets.symmetric(horizontal: 10),\n child: Row(\n children: data\n .map((color) => Container(\n alignment: Alignment.center,\n width: 90,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n ),\n\n ),\n );\n }\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":33,"name":"ToggleButtons多选切换","priority":3,"subtitle":" 可以控制状态转化的逻辑来形成不同的效果。","code":"import 'package:flutter/material.dart';\nclass ProToggleButtons extends StatefulWidget {\n @override\n _ProToggleButtonsState createState() => _ProToggleButtonsState();\n}\n\nclass _ProToggleButtonsState extends State {\n var _isSelected = [false, false, false];\n @override\n Widget build(BuildContext context) {\n return ToggleButtons(\n children: [\n Icon(Icons.skip_previous),\n Icon(Icons.pause),\n Icon(Icons.skip_next),\n ],\n borderWidth: 1,\n borderColor: Colors.blue,\n selectedBorderColor: Colors.orangeAccent,\n splashColor: Colors.purple.withAlpha(66),\n borderRadius: BorderRadius.circular(10),\n selectedColor: Colors.red,\n fillColor: Colors.green.withAlpha(11),\n isSelected: _isSelected,\n onPressed: (value) => setState(() {\n _isSelected[value] = !_isSelected[value];\n }),\n );\n }\n}"},{"id":null,"widgetId":33,"name":"ToggleButtons单选切换","priority":1,"subtitle":"【children】: 子组件集 【List】\n【borderWidth】: 边线宽 【double】\n【borderRadius】: 圆角 【BorderRadius】\n【isSelected】: 是否选中集 【List】\n【onPressed】: 点击事件 【Function(int)】","code":"import 'package:flutter/material.dart';\nclass CustomToggleButtons extends StatefulWidget {\n @override\n _CustomToggleButtonsState createState() => _CustomToggleButtonsState();\n}\n\nclass _CustomToggleButtonsState extends State {\n var _isSelected = [true, false, false];\n\n @override\n Widget build(BuildContext context) {\n return ToggleButtons(\n children: [\n Icon(Icons.skip_previous),\n Icon(Icons.pause),\n Icon(Icons.skip_next),\n ],\n borderWidth: 1,\n borderRadius: BorderRadius.circular(10),\n isSelected: _isSelected,\n onPressed: (value) => setState(() {\n _isSelected = _isSelected.map((e) => false).toList();\n _isSelected[value] = true;\n }),\n );\n\n }\n}"},{"id":null,"widgetId":33,"name":"ToggleButtons颜色属性","priority":2,"subtitle":"【borderColor】: 边线色 【Color】\n【selectedBorderColor】: 选中边线色 【Color】\n【selectedColor】: 选中时组件色 【Color】\n【fillColor】: 选中时填充色 【Color】\n【splashColor】: 水波纹色 【Color】","code":"import 'package:flutter/material.dart';\nclass ColorToggleButtons extends StatefulWidget {\n @override\n _ColorToggleButtonsState createState() => _ColorToggleButtonsState();\n}\n\nclass _ColorToggleButtonsState extends State {\n var _isSelected = [true, false, false];\n @override\n Widget build(BuildContext context) {\n return ToggleButtons(\n children: [\n Icon(Icons.skip_previous),\n Icon(Icons.pause),\n Icon(Icons.skip_next),\n ],\n borderWidth: 1,\n borderColor: Colors.orangeAccent,\n selectedBorderColor: Colors.blue,\n splashColor: Colors.purple.withAlpha(66),\n borderRadius: BorderRadius.circular(10),\n selectedColor: Colors.red,\n fillColor: Colors.green.withAlpha(11),\n isSelected: _isSelected,\n onPressed: (value) => setState(() {\n _isSelected = _isSelected.map((e) => false).toList();\n _isSelected[value] = true;\n }),\n );\n }\n}\n"},{"id":null,"widgetId":10,"name":"replacement可在隐藏时进行占位","priority":2,"subtitle":"【replacement】 : 隐藏时的占位组件 【Widget】","code":"import 'package:flutter/material.dart';\nclass ReplacementVisibility extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n children: [\n _buildVisibility(true),\n _buildVisibility(false),\n ],\n );\n }\n\n _buildVisibility(bool visible) {\n var box = Container(\n height: 30,\n width: 30,\n color: Colors.blue,\n );\n return Container(\n width: 150,\n height: 150 * 0.618,\n color: Colors.cyanAccent.withAlpha(33),\n child: Row(\n mainAxisAlignment: MainAxisAlignment.center,\n children: [\n box,\n Visibility(\n visible: visible,\n replacement: Container(\n alignment: Alignment.center,\n height: 80 * 0.618,\n width: 80,\n ),\n child: Container(\n alignment: Alignment.center,\n height: 80 * 0.618,\n width: 80,\n color: Colors.red,\n child: Text(\n \"visible\\ntrue\",\n style: TextStyle(fontSize: 20),\n ),\n )),\n box,\n ],\n ),\n );\n }\n}\n"},{"id":null,"widgetId":10,"name":"根据visible控制内部子组件的显隐情况","priority":1,"subtitle":"【visible】 : 是否显示 【bool】\n【child】: 孩子 【Widget】\n默认孩子隐藏时会失去原来所在区域。","code":"import 'package:flutter/material.dart';\nclass CustomVisibility extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n children: [\n _buildVisibility(true),\n _buildVisibility(false),\n ],\n );\n }\n\n _buildVisibility(bool visible) {\n var box = Container(\n height: 30,\n width: 30,\n color: Colors.blue,\n );\n return Container(\n width: 150,\n height: 150 * 0.618,\n color: Colors.cyanAccent.withAlpha(33),\n child: Row(\n mainAxisAlignment: MainAxisAlignment.center,\n children: [\n box,\n Visibility(\n visible: visible,\n child: Container(\n alignment: Alignment.center,\n height: 80 * 0.618,\n width: 80,\n color: Colors.red,\n child: Text(\n \"visible\\ntrue\",\n style: TextStyle(fontSize: 20),\n ),\n )),\n box,\n ],\n ),\n );\n }\n}\n"},{"id":null,"widgetId":208,"name":"Title基本使用","priority":1,"subtitle":"【title】 : 名称 【int】\n【color】: 颜色 【Color】\n【child】: 子组件 【Widget】","code":"import 'package:flutter/material.dart';\nclass TitleDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Title(\n title: '张风捷特烈',\n color: Color(0xFF9C27B0),\n child: Center(child: Text('应用菜单栏中该页的名称为: 张风捷特烈'))),\n );\n }\n}\n"},{"id":null,"widgetId":163,"name":"GridView.extent构造","priority":3,"subtitle":" \n【maxCrossAxisExtent】 : box轴向长度 【double】\n【reverse】 : 是否反向滑动 【bool】\n【shrinkWrap】 : 无边界时是否包裹 【bool】","code":"import 'package:flutter/material.dart';\nclass ExtentGridView extends StatelessWidget {\n final data = List.generate(128, (i) => Color(0xFF00FFFF - 2*i));\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: GridView.extent(\n scrollDirection: Axis.horizontal,\n maxCrossAxisExtent: 80.0,\n mainAxisSpacing: 2,\n crossAxisSpacing: 2,\n childAspectRatio: 0.618,\n children: data\n .map((color) => _buildItem(color))\n .toList(),\n ),\n );\n }\n\n Container _buildItem(Color color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 30,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n );\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":163,"name":"GridView.count构造","priority":1,"subtitle":" \n【children】 : 子组件列表 【List】\n【crossAxisCount】 : 主轴一行box数量 【int】\n【mainAxisSpacing】 : 主轴每行间距 【double】\n【crossAxisSpacing】 : 交叉轴每行间距 【double】\n【childAspectRatio】 : box主长/交叉轴长 【double】\n【crossAxisCount】 : 主轴一行数量 【int】","code":"import 'package:flutter/material.dart';\nclass CustomGridView extends StatelessWidget {\n final data = List.generate(128, (i) => Color(0xFFFF00FF - 2*i));\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: GridView.count(\n crossAxisCount: 4,\n mainAxisSpacing: 2,\n crossAxisSpacing: 2,\n childAspectRatio: 1/0.618,\n children: data\n .map((color) => _buildItem(color))\n .toList(),\n ),\n );\n }\n\n Container _buildItem(Color color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 30,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n );\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":163,"name":"GridView.builder构造","priority":4,"subtitle":" \n【itemCount】 : 条目数量 【int】\n【gridDelegate】 : 网格代理 【SliverGridDelegate】\n【itemBuilder】 : 条目构造器 【IndexedWidgetBuilder】","code":"import 'package:flutter/material.dart';\nclass BuilderGridView extends StatelessWidget {\n final data = List.generate(128, (i) => Color(0xFF33FFF - 2*i));\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: GridView.builder(\n itemCount: data.length,\n scrollDirection: Axis.vertical,\n gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(//网格代理:定交叉轴数目\n crossAxisCount: 4,//条目个数\n mainAxisSpacing: 5,//主轴间距\n crossAxisSpacing: 5,//交叉轴间距\n childAspectRatio:1/0.618),\n itemBuilder: (_, int position)=> _buildItem(data[position])\n ),\n );\n }\n\n Container _buildItem(Color color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 30,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n );\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":163,"name":"GridView滑动方向","priority":2,"subtitle":" \n【scrollDirection】 : 滑动方向 【Axis】\n【reverse】 : 是否反向滑动 【bool】\n【shrinkWrap】 : 无边界时是否包裹 【bool】","code":"import 'package:flutter/material.dart';\nclass HorizontalGridView extends StatelessWidget {\n final data = List.generate(128, (i) => Color(0xFF00FFFF - 2*i));\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: GridView.count(\n scrollDirection: Axis.horizontal,\n reverse: true,\n crossAxisCount: 4,\n mainAxisSpacing: 2,\n crossAxisSpacing: 2,\n childAspectRatio: 0.618,\n children: data\n .map((color) => _buildItem(color))\n .toList(),\n ),\n );\n }\n\n Container _buildItem(Color color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 30,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n );\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":140,"name":"SnackBar基本使用","priority":1,"subtitle":" \n【content】 : 中间内容组件 【Widget】\n【action】 : 右侧按钮 【SnackBarAction】\n【duration】 : 持续时长 【Widget】\n【backgroundColor】 : 背景色 【Color】\n【elevation】 : 影深 【double】\n【shape】 : 形状 【ShapeBorder】\n【onVisible】 : 显示时回调 【Function()】","code":"import 'package:flutter/material.dart';\nclass CustomSnackBar extends StatefulWidget {\n @override\n _CustomSnackBarState createState() => _CustomSnackBarState();\n}\n\nclass _CustomSnackBarState extends State {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: FlatButton(\n color: Colors.blue,\n onPressed: () =>\n Scaffold.of(context).showSnackBar(_buildSnackBar()),\n child: Text(\n '点我弹出SnackBar',\n style: TextStyle(color: Colors.white),\n )));\n }\n\n Widget _buildSnackBar() {\n return SnackBar(\n elevation: 3,\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.only(\n topLeft: Radius.circular(10), topRight: Radius.circular(10))),\n content: Text('Wellcome to for join Flutter Unit!'),\n duration: Duration(seconds: 3),\n //持续时间\n backgroundColor: Colors.red,\n onVisible: () => print('onVisible'),\n action: SnackBarAction(\n textColor: Colors.white, label: '确定', onPressed: () {}),\n );\n }\n}\n"},{"id":null,"widgetId":206,"name":"TabPageSelectorIndicator基本使用","priority":1,"subtitle":"【size】: 大小 【double】\n【backgroundColor】: 背景色 【Color】\n【borderColor】: 边线色 【Color】","code":"import 'package:flutter/material.dart';\nclass TabPageSelectorIndicatorDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [\n TabPageSelectorIndicator(\n backgroundColor: Colors.greenAccent,\n borderColor: Colors.deepPurpleAccent,\n size: 15,\n ),\n TabPageSelectorIndicator(\n backgroundColor: Colors.blue,\n borderColor: Colors.grey,\n size: 20,\n ),\n TabPageSelectorIndicator(\n backgroundColor: Colors.green,\n borderColor: Colors.red,\n size: 25,\n ),\n TabPageSelectorIndicator(\n backgroundColor: Colors.yellow,\n borderColor: Colors.brown,\n size: 30,\n ),\n TabPageSelectorIndicator(\n backgroundColor: Colors.amber,\n borderColor: Colors.purpleAccent,\n size: 35,\n ),\n ],\n ),\n );\n }\n\n}\n\n"},{"id":null,"widgetId":16,"name":"ListTile的密排属性","priority":3,"subtitle":"【dense】: 是否密排 【bool】","code":"import 'package:flutter/material.dart';\nclass DenseListTile extends StatefulWidget {\n @override\n _DenseListTileState createState() => _DenseListTileState();\n}\n\nclass _DenseListTileState extends State {\n bool _dense = false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(10),\n color: Colors.grey.withAlpha(22),\n child: ListTile(\n leading: Image.asset(\"assets/images/icon_head.png\"),\n title: Text(\"以梦为马\"),\n subtitle: Text(\"海子\"),\n selected: false,\n contentPadding: EdgeInsets.all(5),\n trailing: Icon(Icons.more_vert),\n dense: _dense,\n onTap: () => setState(() => _dense = !_dense),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":16,"name":"ListTile选中效果和长按事件","priority":2,"subtitle":"【selected】: 是否选中 【bool】\n【onTap】: 点击事件 【Function()】","code":"import 'package:flutter/material.dart';\nclass SelectListTile extends StatefulWidget {\n @override\n _SelectListTileState createState() => _SelectListTileState();\n}\n\nclass _SelectListTileState extends State {\n bool _selected = false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(10),\n color: Colors.grey.withAlpha(22),\n child: ListTile(\n leading: Image.asset(\"assets/images/icon_head.png\"),\n selected: _selected,\n title: Text(\"以梦为马\"),\n subtitle: Text(\"海子\"),\n contentPadding: EdgeInsets.all(5),\n trailing: Icon(Icons.more_vert),\n onTap: () => setState(() => _selected = !_selected),\n ),\n );\n }\n}"},{"id":null,"widgetId":16,"name":"ListTile的基本表现如下","priority":1,"subtitle":"【leading】: 左侧组件 【Widget】\n【title】: 中间上组件 【Widget】\n【subtitle】: 中间下组件 【Widget】\n【trailing】: 尾组件 【Widget】\n【contentPadding】: 内边距 【EdgeInsetsGeometry】\n【onLongPress】: 点击事件 【Function()】","code":"import 'package:flutter/material.dart';\nclass CustomListTile extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(10),\n color: Colors.grey.withAlpha(22),\n child: ListTile(\n leading: Image.asset(\"assets/images/icon_head.png\"),\n title: Text(\"以梦为马\"),\n subtitle: Text(\"海子\"),\n contentPadding: EdgeInsets.all(5),\n trailing: Icon(Icons.more_vert),\n onLongPress: () => Navigator.of(context).pushNamed('AboutMePage'),\n ),\n );\n }\n}"},{"id":null,"widgetId":20,"name":"GridTileBar的基本表现如下","priority":1,"subtitle":"【value】 : 条目对象 【T】\n【leading】: 左侧组件 【Widget】\n【trailing】: 尾组件 【Widget】\n【title】: 中间上组件 【Widget】\n【subtitle】: 中间下组件 【Widget】\n【backgroundColor】: 背景色 【Color】","code":"import 'package:flutter/material.dart';\nclass CustomGridTileBar extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return GridTileBar(\n backgroundColor: Colors.blue.withAlpha(120),\n trailing: Icon(\n Icons.star,\n color: Colors.red,\n ),\n leading: CircleAvatar(\n backgroundImage: AssetImage(\"assets/images/wy_200x300.jpg\"),\n ),\n title: Text(\"百里·巫缨\"),\n subtitle: Text(\"倾国必倾城\"),\n );\n }\n}\n"},{"id":null,"widgetId":21,"name":"GridTile的基本表现如下","priority":1,"subtitle":"【header】: 头组件 【Widget】\n【child】: 子组件 【Widget】\n【footer】: 脚组件 【Widget】","code":"import 'package:flutter/material.dart';\nclass CustomGridTile extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 200, height: 200,\n child: GridTile(\n header: GridTileBar(\n backgroundColor: Colors.blue.withAlpha(120),\n trailing: Icon(\n Icons.star,\n color: Colors.red,\n ),\n leading: CircleAvatar(\n backgroundImage: AssetImage(\"assets/images/wy_200x300.jpg\"),\n ),\n title: Text(\"百里·巫缨\"),\n subtitle: Text(\"倾国必倾城\"),\n ),\n child: Opacity(\n opacity: 0.5,\n child: Image.asset(\n \"assets/images/sabar.jpg\",\n fit: BoxFit.cover,\n ),\n ),\n footer: Padding(\n padding: const EdgeInsets.all(8.0),\n child: Text(\n \"ID:z\\$ySX32&29\",\n style: TextStyle(shadows: [\n Shadow(\n color: Colors.blue,\n offset: Offset(.1, .1),\n blurRadius: 2),\n ]),\n ),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":11,"name":"可以设置颜色和阴影","priority":2,"subtitle":"【backgroundColor】: 背景色 【Color】\n【shadowColor】: 阴影色 【Color】\n【elevation】: 影深 【double】","code":"import 'package:flutter/material.dart';\nclass ColorOfChip extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 20,\n children: [\n Chip(\n avatar: Image.asset(\"assets/images/icon_head.png\"),\n label: Text(\"张风捷特烈\"),\n padding: EdgeInsets.all(5),\n labelPadding: EdgeInsets.all(5),\n backgroundColor: Colors.grey.withAlpha(66),\n shadowColor: Colors.orangeAccent,\n elevation: 3,\n ),\n Chip(\n avatar: Image.asset(\"assets/images/icon_head.png\"),\n label: Text(\"张风捷特烈\"),\n padding: EdgeInsets.all(5),\n labelPadding: EdgeInsets.all(5),\n backgroundColor: Colors.cyanAccent.withAlpha(11),\n shadowColor: Colors.blue.withAlpha(88),\n elevation: 4,\n ),\n ],\n );\n }\n}"},{"id":null,"widgetId":11,"name":"可以设置右侧点击按钮","priority":3,"subtitle":"【deleteIcon】: 右侧组件(通常为Icon) 【Widget】\n【deleteIconColor】: 右侧组件颜色 【Color】\n【onDeleted】: 右侧组件点击事件 【Function】","code":"import 'package:flutter/material.dart';\nimport '../../../dialogs/dialog_about.dart';\nclass DeleteOfChip extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Chip(\n avatar: Image.asset(\"assets/images/icon_head.png\"),\n label: Text(\"张风捷特烈\"),\n padding: EdgeInsets.all(5),\n labelPadding: EdgeInsets.all(3),\n backgroundColor: Colors.grey.withAlpha(66),\n shadowColor: Colors.orangeAccent,\n// deleteIcon: Icon(Icons.close,size: 18),\n deleteIconColor: Colors.red,\n onDeleted: () => DialogAbout.show(context),\n elevation: 3,\n );\n }\n}"},{"id":null,"widgetId":11,"name":"Chip的普通表现如下","priority":1,"subtitle":"【avatar】: 左侧组件 【Widget】\n【label】: 中间组件 【Widget】\n【padding】 : 内边距 【EdgeInsetsGeometry】\n【labelPadding】: label边距 【EdgeInsetsGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomChip extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 20,\n children: [\n Chip(\n avatar: Image.asset(\"assets/images/icon_head.png\"),\n label: Text(\"张风捷特烈\"),\n padding: EdgeInsets.all(5),\n labelPadding: EdgeInsets.all(5),\n ),\n Chip(\n avatar: CircleAvatar(\n backgroundImage:\n AssetImage(\"assets/images/wy_200x300.jpg\")),\n label: Text(\"百里巫缨\"),\n padding: EdgeInsets.all(8),\n labelPadding: EdgeInsets.all(6),\n ),\n ],\n );\n }\n}"},{"id":null,"widgetId":214,"name":"NavigationToolbar基本使用","priority":1,"subtitle":"【leading】 : 左侧组件 【Widget】\n【middle】: 中间组件 【Widget】\n【trailing】: 右侧组件组件 【Widget】\n【centerMiddle】: 中间组件是否居中 【bool】\n【middleSpacing】: 中间组件距左距离 【double】","code":"import 'package:flutter/material.dart';\nclass NavigationToolbarDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Column(\n mainAxisSize: MainAxisSize.min,\n children: [\n Container(\n height: 60,\n child: NavigationToolbar(\n leading: Icon(Icons.ac_unit),\n middle: Text('middleSpacing#true'),\n middleSpacing: 20,\n centerMiddle: true,\n trailing: Icon(Icons.more_vert),\n ),\n ),\n Container(\n height: 60,\n child: NavigationToolbar(\n leading: Icon(Icons.ac_unit),\n middle: Text('middleSpacing#false'),\n middleSpacing: 20,\n centerMiddle: false,\n trailing: Icon(Icons.more_vert),\n ),\n ),\n ],\n );\n }\n}"},{"id":null,"widgetId":128,"name":"SimpleDialog基本使用","priority":1,"subtitle":" \n【title】 : 顶部组件 【Widget】\n【children】 : 子组件列表 【List】\n【titlePadding】 : 顶部内边距 【EdgeInsetsGeometry】\n【contentPadding】 : 内容内边距 【EdgeInsetsGeometry】\n【backgroundColor】 : 右下角组件列表 【背景色】\n【elevation】 : 右下角组件列表 【背景色】\n【shape】 : 影深 【double】","code":"import 'dart:math';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomSimpleDialog extends StatelessWidget {\n final info = [\n '性别: 男 未婚',\n '微信: zdl1994328',\n \"掘金: 张风捷特烈\",\n \"github: toly1994328\",\n \"邮箱: 1981462008@qq.com\",\n ];\n\n @override\n Widget build(BuildContext context) {\n return Stack(\n children: [\n _buildSimpleDialog(context),\n Positioned(\n top: 70,\n right: 30,\n child: _buildRaisedButton(context)),\n\n ],\n );\n }\n Widget _buildRaisedButton(BuildContext context) => RaisedButton(\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n color: Colors.blue,\n onPressed: () {\n showDialog(context: context, builder: (ctx) => _buildSimpleDialog(ctx));\n },\n child: Text(\n 'Just Show It',\n style: TextStyle(color: Colors.white),\n ),\n );\n\n SimpleDialog _buildSimpleDialog(BuildContext context) {\n return SimpleDialog(\n title: _buildTitle(),\n titlePadding: EdgeInsets.only(\n top: 5,\n left: 20,\n ),\n contentPadding: EdgeInsets.symmetric(horizontal: 5),\n children: _buildChild(context),\n backgroundColor: Colors.white,\n elevation: 4,\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n );\n }\n\n List _buildChild(BuildContext context) {\n return info\n .map((str) => Column(\n crossAxisAlignment: CrossAxisAlignment.start,\n children: [\n SimpleDialogOption(\n onPressed: () => print(str),\n child: Container(\n width: double.infinity,\n child: Text(\n str,\n style: TextStyle(color: Color(0xff999999), fontSize: 16),\n ),\n ),\n ),\n Divider(\n indent: 20,\n height: 12,\n color: info.indexOf(str) == info.length - 1\n ? Colors.transparent\n : Theme.of(context).dividerColor,\n )\n ],\n ))\n .toList();\n }\n\n Widget _buildTitle() {\n return Row(\n //标题\n children: [\n Image.asset(\n \"assets/images/icon_head.png\",\n width: 30,\n height: 30,\n ),\n SizedBox(\n width: 10,\n ),\n Expanded(\n child: Text(\n \"张风捷特烈\",\n style: TextStyle(fontSize: 18),\n )),\n CloseButton()\n ],\n );\n }\n}\n"},{"id":null,"widgetId":23,"name":"MaterialButton长按事件","priority":2,"subtitle":"【highlightColor】: 长按高亮色 【Color】\n【onLongPress】: 长按事件 【Function】","code":"import 'package:flutter/material.dart';\nclass LongPressMaterialButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return MaterialButton(\n height: 40,\n elevation: 5,\n color: Colors.blue,\n highlightColor: Colors.green,\n textColor: Colors.white,\n padding: EdgeInsets.all(8),\n child: Text(\"MaterialButton\"),\n onLongPress: () => Navigator.of(context).pushNamed('AboutMePage'),\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'));\n }\n}"},{"id":null,"widgetId":23,"name":"MaterialButton点击事件","priority":1,"subtitle":"【color】: 颜色 【Color】\n【splashColor】: 水波纹颜色 【Color】\n【height】: 高 【double】\n【elevation】: 影深 【double】\n【child】: 子组件 【Widget】\n【textColor】: 子组件文字颜色 【Color】\n【highlightColor】: 长按高亮色 【Color】\n【padding】: 内边距 【EdgeInsetsGeometry】\n【onPressed】: 点击事件 【Function】","code":"import 'package:flutter/material.dart';\nclass CustomMaterialButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return MaterialButton(\n height: 40,\n elevation: 5,\n color: Colors.orangeAccent,\n textColor: Colors.white,\n splashColor: Colors.blue,\n padding: EdgeInsets.all(8),\n child: Text(\"MaterialButton\"),\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'));\n }\n}"},{"id":null,"widgetId":23,"name":"MaterialButton的自定义形状","priority":3,"subtitle":"【shape】: 形状 【ShapeBorder】","code":"import 'package:flutter/material.dart';\nclass ShapeMaterialButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 20,\n children: [\n Container(\n width: 40,\n height: 40,\n child: MaterialButton(\n padding: EdgeInsets.all(0),\n textColor: Color(0xffFfffff),\n elevation: 3,\n color: Colors.blue,\n highlightColor: Color(0xffF88B0A),\n splashColor: Colors.red,\n child: Icon(\n Icons.add,\n color: Colors.white,\n ),\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onLongPress: () => Navigator.of(context).pushNamed('AboutMePage'),\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage')),\n ),\n Container(\n width: 100,\n height: 40,\n child: MaterialButton(\n padding: EdgeInsets.all(0),\n textColor: Color(0xffFfffff),\n elevation: 3,\n color: Colors.blue,\n highlightColor: Color(0xffF88B0A),\n splashColor: Colors.red,\n child: Icon(\n Icons.remove,\n color: Colors.white,\n ),\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(15))),\n onLongPress: () => Navigator.of(context).pushNamed('AboutMePage'),\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage')),\n ),\n ],\n );\n }\n}"},{"id":null,"widgetId":32,"name":"CloseButton点击事件","priority":1,"subtitle":" 点击时会退出当前栈","code":"import 'package:flutter/material.dart';\nclass CustomCloseButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return CloseButton();\n }\n}\n"},{"id":null,"widgetId":58,"name":"TabBar基本使用","priority":1,"subtitle":" \n【controller】 : 控制器 【TabController】\n【indicatorColor】 : 指示器颜色 【指示器颜色】\n【indicatorWeight】 : 指示器高 【double】\n【indicatorPadding】 : 指示器边距 【EdgeInsetsGeometry】\n【labelStyle】 : 页签文字样式 【TextStyle】\n【unselectedLabelStyle】 : 未选中文字样式 【TextStyle】\n【isScrollable】 : 是否可滑动 【bool】\n【onTap】 : 页签点击回调 【Function(int)】\n【tabs】 : 标签组件 【List】","code":"import 'package:flutter/material.dart';\nclass CustomTabBar extends StatefulWidget {\n @override\n _CustomTabBarState createState() => _CustomTabBarState();\n}\n\nclass _CustomTabBarState extends State\n with SingleTickerProviderStateMixin {\n final tabs = ['风画庭', '雨韵舍', '雷鸣殿', '电疾堂', '霜寒阁', '雪月楼'];\n TabController _tabController;\n\n @override\n void initState() {\n super.initState();\n _tabController = TabController(vsync: this, length: tabs.length);\n }\n\n @override\n void dispose() {\n _tabController.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return TabBar(\n onTap: (tab) {\n print(tab);\n },\n labelStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),\n unselectedLabelStyle: TextStyle(fontSize: 16),\n isScrollable: true,\n controller: _tabController,\n labelColor: Colors.blue,\n indicatorWeight: 3,\n indicatorPadding: EdgeInsets.symmetric(horizontal: 10),\n unselectedLabelColor: Colors.grey,\n indicatorColor: Colors.orangeAccent,\n tabs: tabs.map((e) => Tab(text: e)).toList(),\n );\n }\n}"},{"id":null,"widgetId":58,"name":"通过修改源码可实现无水波纹","priority":1,"subtitle":" \n详见:components/flutter/no_shadow_tab_bar.dart","code":"import 'package:flutter/material.dart';\nimport 'no_shadow_tab_bar.dart';\nclass NoShadowTabBarDemo extends StatefulWidget {\n @override\n _NSTabBarState createState() => _NSTabBarState();\n}\n\nclass _NSTabBarState extends State\n with SingleTickerProviderStateMixin {\n final tabs = ['风画庭', '雨韵舍', '雷鸣殿', '电疾堂', '霜寒阁', '雪月楼'];\n TabController _tabController;\n\n @override\n void initState() {\n super.initState();\n _tabController = TabController(vsync: this, length: tabs.length);\n }\n\n @override\n void dispose() {\n _tabController.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return NoShadowTabBar(\n onTap: (tab) {\n print(tab);\n },\n labelStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),\n unselectedLabelStyle: TextStyle(fontSize: 16),\n isScrollable: true,\n controller: _tabController,\n labelColor: Colors.blue,\n indicatorWeight: 3,\n indicatorPadding: EdgeInsets.symmetric(horizontal: 10),\n unselectedLabelColor: Colors.grey,\n indicatorColor: Colors.orangeAccent,\n tabs: tabs.map((e) => Tab(text: e)).toList(),\n );\n }\n}"},{"id":null,"widgetId":37,"name":"GridPage基础属性","priority":1,"subtitle":"【child】: 子组件 【Widget】\n【color】: 颜色 【Color】\n【interval】: 小块边长 【double】","code":"import 'package:flutter/material.dart';\nclass CustomGridPage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 200,\n height: 100,\n child: GridPaper(\n color: Colors.red,\n interval: 50,\n child: Image.asset(\n \"assets/images/wy_300x200.jpg\",\n fit: BoxFit.cover,\n )));\n }\n}"},{"id":null,"widgetId":37,"name":"GridPage再分割","priority":2,"subtitle":"【child】: 子组件 【Widget】\n【color】: 颜色 【Color】\n【subdivisions】: 小块中子块个数 【int】\n【divisions】: 小块中子块的分割数 【int】","code":"import 'package:flutter/material.dart';\nclass DivisionsGridPage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 200,\n height: 100,\n child: GridPaper(\n color: Colors.red,\n interval: 50,\n divisions: 4,\n subdivisions: 4,\n child: Image.asset(\n \"assets/images/wy_300x200.jpg\",\n fit: BoxFit.cover,\n )));\n }\n}\n"},{"id":null,"widgetId":129,"name":"CupertinoAlertDialog基本使用","priority":1,"subtitle":" \n【title】 : 顶部组件 【Widget】\n【content】 : 内容组件 【Widget】\n【actions】 : 顶部文字样式 【List】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoAlertDialog extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildRaisedButton(context),\n _buildCupertinoAlertDialog(context),\n ],\n );\n }\n\n Widget _buildRaisedButton(BuildContext context) => RaisedButton(\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n color: Colors.blue,\n onPressed: () {\n showDialog(\n context: context,\n builder: (ctx) => _buildCupertinoAlertDialog(context));\n },\n child: Text(\n 'Just Show It !',\n style: TextStyle(color: Colors.white),\n ),\n );\n\n Widget _buildCupertinoAlertDialog(BuildContext context) {\n return Material(\n color: Colors.transparent,\n child: CupertinoAlertDialog(\n title: _buildTitle(context),\n content: _buildContent(),\n actions: [\n CupertinoButton(\n child: Text(\"Yes, Delete\"),\n onPressed: () => Navigator.pop(context),\n ),\n CupertinoButton(\n child: Text(\"Cancle\"),\n onPressed: () => Navigator.pop(context),\n ),\n ]),\n );\n }\n\n Widget _buildTitle(context) {\n return Row(\n //标题\n children: [\n Icon(\n CupertinoIcons.delete_solid,\n color: Colors.red,\n ),\n Expanded(\n child: Text(\n 'Delete File',\n style: TextStyle(color: Colors.red, fontSize: 20),\n )),\n InkWell(\n child: Icon(CupertinoIcons.clear_thick),\n onTap: () => Navigator.pop(context),\n )\n ]);\n }\n\n Widget _buildContent() {\n return Padding(\n padding: const EdgeInsets.only(top: 18.0),\n child: Column(\n children: [\n Text(\n ' Hi toly! If you push the conform buttom ,'\n ' You will lose this file. Are you sure wand to do that?',\n style: TextStyle(color: Color(0xff999999), fontSize: 16),\n textAlign: TextAlign.justify,\n ),\n ],\n ),\n );\n }\n}\n"},{"id":null,"widgetId":218,"name":"返回按钮基本使用","priority":1,"subtitle":"【onPressed】 : 点击事件 【VoidCallback】\n【color】: 颜色 【Color】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CupertinoNavigationBarBackButtonDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: CupertinoNavigationBarBackButton(\n color: Colors.deepPurpleAccent,\n onPressed: ()=>Navigator.of(context).pop(),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":107,"name":"Spacer基本使用","priority":1,"subtitle":" \n一个Spacer会占据可延伸区域","code":"import 'package:flutter/material.dart';\nclass OneSpacer extends StatelessWidget {\n\n @override\n Widget build(BuildContext context) {\n return\n Container(\n color: Colors.grey.withAlpha(33),\n child: Row(children: [\n Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: Colors.red,\n ),\n Spacer(),\n Container(\n alignment: Alignment.center,\n width: 60,\n height: 50,\n color: Colors.blue,\n ),\n ],),\n );\n }\n}"},{"id":null,"widgetId":148,"name":"Tab基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【text】 : 文字 【String】\n【icon】 : 下方组件 【Widgit】\n text和child不能同时存在","code":"import 'package:flutter/material.dart';\nclass CustomTab extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [\n Tab(\n icon:Icon( Icons.add,color: Colors.blue,),\n child: Text('添加'),\n ),\n Tab(\n icon:Icon( Icons.close,color: Colors.red,),\n text: '删除',\n ),\n Tab(\n icon:Icon( Icons.refresh,color: Colors.green),\n text: '更新',\n ),\n ],\n ),\n );\n }\n}\n"},{"id":null,"widgetId":107,"name":"多个Spacer空间分配","priority":2,"subtitle":" \n【flex】 : 剩余空间分配占比 【int】","code":"import 'package:flutter/material.dart';\nclass ManySpacer extends StatefulWidget {\n @override\n _ManySpacerState createState() => _ManySpacerState();\n}\n\nclass _ManySpacerState extends State {\n int _flexA=1;\n int _flexB=1;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildSliders(),\n Container(\n color: Colors.grey.withAlpha(33),\n child: Row(children: [\n Spacer(flex: _flexA),\n Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: Colors.red,\n ),\n Spacer(flex: _flexB),\n Container(\n alignment: Alignment.center,\n width: 60,\n height: 50,\n color: Colors.blue,\n ),\n ],),\n )\n ],\n );\n }\n\n Widget _buildSliders() {\n return Column(\n children: [\n Slider(\n divisions: 20,\n min: 1,\n max: 10,\n label: \"左边flex: $_flexA\",\n value: _flexA.toDouble(),\n onChanged: (v) => setState(() => _flexA = v.round())\n ),\n Slider(\n divisions: 20,\n label: \"右边flex: $_flexB\",\n min: 1,\n max: 10,\n value: _flexB.toDouble(),\n onChanged: (v) => setState(() => _flexB = v.round())\n ),\n ],\n );\n }\n}"},{"id":null,"widgetId":35,"name":"VerticalDivider颜色和粗细","priority":1,"subtitle":"【color】: 颜色 【Color】\n【thickness】: 线粗细 【double】","code":"import 'package:flutter/material.dart';\nclass CustomVerticalDivider extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var dataColor = [\n Colors.red, Colors.yellow,\n Colors.blue, Colors.green];\n var dataThickness = [1.0, 2.0, 4.0, 6.0];\n var data = Map.fromIterables(dataColor, dataThickness);\n return Container(\n height: 150,\n child: Row(\n mainAxisSize: MainAxisSize.min,\n children: dataColor\n .map((e) => VerticalDivider(\n color: e,\n thickness: data[e],\n ))\n .toList(),\n ),\n );\n }\n}"},{"id":null,"widgetId":35,"name":"VerticalDivider宽度和空缺","priority":2,"subtitle":"【indent】: 前面空缺长度 【double】\n【endIndent】: 后面空缺长度 【double】\n【height】: 占位高 【double】","code":"import 'package:flutter/material.dart';\nclass HeightVerticalDivider extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var dataColor = [\n Colors.red, Colors.yellow,\n Colors.blue, Colors.green];\n var dataThickness = [10.0, 20.0, 30.0, 40.0];\n var data = Map.fromIterables(dataColor, dataThickness);\n return Container(\n height: 150,\n child: Row(\n mainAxisSize: MainAxisSize.min,\n children: dataColor\n .map((e) => VerticalDivider(\n color: e,\n indent:data[e],\n endIndent: data[e]*2,\n width: data[e],\n thickness: data[e]/10,\n ))\n .toList(),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":7,"name":"用于显示一个纯色图片","priority":1,"subtitle":"【入参】 : 图片资源 【ImageProvider】\n【size】 : 大小 【double】\n【color】: 角标颜色 【Color】","code":"import 'package:flutter/material.dart';\nclass CustomImageIcon extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var data = {\n Colors.blue: 50.0,\n Colors.red: 60.0,\n Colors.green: 70.0,\n Colors.yellow: 80.0,\n };\n return Wrap(\n spacing: 10,\n children: data.keys\n .map((e) => ImageIcon(\n AssetImage(\"assets/images/leaf.png\"),\n color: e,\n size: data[e],\n ))\n .toList(),\n );\n }\n}\n"},{"id":null,"widgetId":22,"name":"右上角和底部","priority":2,"subtitle":"【otherAccountsPictures】: 右上组件 【List】\n【onDetailsPressed】: 右下角点击事件 【Function()】\n【arrowColor】: 右下角按钮颜色 【Color】\n【margin】: 外边距 【EdgeInsetsGeometry】","code":"import 'package:flutter/material.dart';\nclass ProUAGHP extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width / 3 * 2,\n child: UserAccountsDrawerHeader(\n margin: EdgeInsets.all(10),\n accountName: Container(\n padding: const EdgeInsets.all(8.0),\n child: Text(\n \"张风捷特烈\",\n style:\n TextStyle(color: Colors.orangeAccent, fontSize: 22,\n shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2),\n ]),\n ),\n ),\n accountEmail: Padding(\n padding: const EdgeInsets.all(8.0),\n child: Text(\"1981462002@qq.com\",\n style: TextStyle(\n color: Colors.white, fontSize: 14,\n shadows: [\n Shadow(\n color: Colors.orangeAccent,\n offset: Offset(.5, .5),\n blurRadius: 2),\n ])),\n ),\n currentAccountPicture: Container(\n padding: const EdgeInsets.all(15.0),\n child: CircleAvatar(\n backgroundImage: AssetImage(\"assets/images/icon_head.png\"),\n ),\n ),\n otherAccountsPictures: [\n FlutterLogo(colors: Colors.green),\n ],\n onDetailsPressed: () {\n\n },\n arrowColor: Colors.white,\n decoration: BoxDecoration(\n image: DecorationImage(\n image: AssetImage(\"assets/images/caver.jpeg\")),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":22,"name":"该组件的基本表现如下","priority":1,"subtitle":"【currentAccountPicture】: 上组件 【Widget】\n【accountName】: 中组件 【Widget】\n【accountEmail】: 下组件 【Widget】\n【decoration】: 装饰 【Decoration】","code":"import 'package:flutter/material.dart';\nclass CustomUAGHP extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width / 3 * 2,\n child: UserAccountsDrawerHeader(\n accountName: Container(\n padding: const EdgeInsets.all(8.0),\n child: Text(\n \"张风捷特烈\",\n style:\n TextStyle(color: Colors.orangeAccent, fontSize: 22, shadows: [\n Shadow(\n color: Colors.black, offset: Offset(.5, .5), blurRadius: 2),\n ]),\n ),\n ),\n accountEmail: Padding(\n padding: const EdgeInsets.all(8.0),\n child: Text(\"1981462002@qq.com\",\n style: TextStyle(color: Colors.white, fontSize: 14, shadows: [\n Shadow(\n color: Colors.orangeAccent,\n offset: Offset(.5, .5),\n blurRadius: 2),\n ])),\n ),\n currentAccountPicture: Container(\n padding: const EdgeInsets.all(15.0),\n child: CircleAvatar(\n backgroundImage: AssetImage(\"assets/images/icon_head.png\"),\n ),\n ),\n decoration: BoxDecoration(\n image: DecorationImage(image: AssetImage(\"assets/images/caver.jpeg\")),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":126,"name":"Dialog基本使用","priority":1,"subtitle":" \n【child】 : 动画图标数据 【Widget】\n【elevation】 : 影深 【double】\n【backgroundColor】 : 背景色 【Color】\n【shape】 : 形状 【ShapeBorder】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomDialog extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildRaisedButton(context),\n _buildDialog(),\n ],\n );\n }\n\n Widget _buildDialog() => Dialog(\n backgroundColor: Colors.white,\n elevation: 5,\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n child: Container(\n width: 50,\n child: DeleteDialog(),\n ),\n );\n\n Widget _buildRaisedButton(BuildContext context) => RaisedButton(\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n color: Colors.blue,\n onPressed: () {\n showDialog(context: context, builder: (ctx) => _buildDialog());\n },\n child: Text(\n 'Just Show It !',\n style: TextStyle(color: Colors.white),\n ),\n\n );\n}\n\nclass DeleteDialog extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n\n child: Column(\n mainAxisSize: MainAxisSize.min,\n children: [\n _buildBar(context),\n _buildTitle(),\n _buildContent(),\n _buildFooter(context),\n ],\n ),\n );\n }\n\n Widget _buildTitle() {\n return Text(\n 'Delete Doucument',\n style: TextStyle(color: Color(0xff5CC5E9), fontSize: 24),\n );\n }\n\n Widget _buildContent() {\n return Padding(\n padding: const EdgeInsets.all(15.0),\n child: Text(\n ' Hi toly! If you push the conform buttom ,'\n ' You will lose this file. Are you sure wand to do that?',\n style: TextStyle(color: Color(0xffCFCFCF), fontSize: 16),\n textAlign: TextAlign.justify,\n ),\n );\n }\n\n Widget _buildFooter(context) {\n return Padding(\n padding: const EdgeInsets.only(bottom: 15.0, top: 10,left: 10,right: 10),\n child: Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [\n Container(\n alignment: Alignment.center,\n height: 40,\n width: 100,\n decoration: BoxDecoration(\n borderRadius: BorderRadius.all(Radius.circular(30)),\n color: Color(0xff73D1EE)),\n child: Text('Yes',\n style: TextStyle(color: Colors.white, fontSize: 16)),\n ),\n InkWell(\n onTap: ()=>Navigator.of(context).pop(),\n child: Container(\n alignment: Alignment.center,\n height: 40,\n width: 100,\n decoration: BoxDecoration(\n borderRadius: BorderRadius.all(Radius.circular(30)),\n color: Colors.orangeAccent),\n child: Text('Cancle',\n style: TextStyle(color: Colors.white, fontSize: 16)),\n ),\n )\n ],\n ),\n );\n }\n\n _buildBar(context) => Container(\n height: 30,\n alignment: Alignment.centerRight,\n margin: EdgeInsets.only(right: 10, top: 5),\n child: InkWell(\n onTap: ()=>Navigator.of(context).pop(),\n child: Icon(\n Icons.close,\n color: Color(0xff82CAE3),\n ),\n ),\n );\n}\n"},{"id":null,"widgetId":131,"name":"CupertinoActionSheet基本使用","priority":1,"subtitle":" \n【title】 : 第一行组件 【Widget】\n【message】 : 第二行组件 【Widget】\n【cancelButton】 : 取消按钮处组件 【Widget】\n【actions】 : 中间组件列表 【List】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoActionSheet extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Column(\n mainAxisSize: MainAxisSize.min,\n children: [\n _buildRaisedButton(context),\n _buildCupertinoActionSheet(context),\n ],\n );\n }\n\n Widget _buildCupertinoActionSheet(BuildContext context) =>\n Container(\n alignment: Alignment.bottomCenter,\n child: CupertinoActionSheet(\n title: Text(\"Please chose a language\"),\n message: Text('the language you use in this application.'),\n cancelButton: CupertinoActionSheetAction(\n onPressed: () => Navigator.pop(context), child: Text(\"Cancel\")),\n actions: [\n CupertinoActionSheetAction(\n onPressed: () => Navigator.pop(context), child: Text('Dart')),\n CupertinoActionSheetAction(\n onPressed: () => Navigator.pop(context), child: Text('Java')),\n CupertinoActionSheetAction(\n onPressed: () => Navigator.pop(context), child: Text('Kotlin')),\n ],\n ),\n );\n\n Widget _buildRaisedButton(BuildContext context) => RaisedButton(\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n color: Colors.blue,\n onPressed: () => showDialog(\n context: context,\n builder: (ctx) => _buildCupertinoActionSheet(context)),\n child: Text(\n 'Just Show It !',\n style: TextStyle(color: Colors.white),\n ),\n );\n}\n"},{"id":null,"widgetId":155,"name":"DrawerHeader基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【decoration】 : 装饰 【Decoration】\n【margin】 : 外边距 【EdgeInsetsGeometry】\n【padding】 : 内边距 【EdgeInsetsGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomDrawerHeader extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 400,\n child: Scaffold(\n appBar: AppBar(\n title: Text('Flutter Unit'),\n ),\n drawer: Drawer(\n elevation: 3,\n child: _buildChild(),\n ),\n ),\n );\n }\n\n Widget _buildChild() => ListView(\n padding: EdgeInsets.zero,\n children: [\n _buildHeader(),\n ListTile(\n leading: Icon(\n Icons.star,\n color: Colors.blue,\n ),\n title: Text('我的收藏'),\n ),\n ListTile(\n leading: Icon(\n Icons.palette,\n color: Colors.orangeAccent,\n ),\n title: Text('我的绘画'),\n ),\n ListTile(\n leading: Icon(\n Icons.insert_drive_file,\n color: Colors.green,\n ),\n title: Text('我的文件'),\n ),\n ],\n );\n\n Widget _buildHeader() {\n return DrawerHeader(\n margin: EdgeInsets.all(10),\n padding: EdgeInsets.only(left: 20,top: 15),\n decoration: BoxDecoration(\n borderRadius: BorderRadius.only(\n topLeft:Radius.circular(40),\n topRight:Radius.circular(40)\n ),\n image: DecorationImage(\n image: AssetImage('assets/images/caver.jpeg'),\n fit: BoxFit.cover),\n ),\n child: Text(\n '张风捷特烈',\n style: TextStyle(fontSize: 24, color: Colors.white, shadows: [\n Shadow(color: Colors.black, offset: Offset(1, 1), blurRadius: 3)\n ]),\n ),\n );\n }\n}"},{"id":null,"widgetId":1,"name":"Container还具有变换性","priority":5,"subtitle":"【transform】 : 变换矩阵 【Matrix4】\n基于Matrix4的矩阵变换,变换详情见线性代数","code":"import 'package:flutter/material.dart';\nimport 'dart:math';\nclass ContainerTransform extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n //容器\n alignment: Alignment.center,\n color: Colors.cyanAccent,\n width: 150,\n height: 150 * 0.618,\n transform: Matrix4.skew(-pi / 10, 0),\n child: Text(\n \"Container\",\n style: TextStyle(fontSize: 20),\n ),\n );\n }\n}"},{"id":null,"widgetId":1,"name":"Container的约束性","priority":6,"subtitle":" \n【constraints】 : 约束 【BoxConstraints】\n会约束该区域的尺寸,不会小于指定的最小宽高,也不会大于指定的最大宽高。","code":"import 'package:flutter/material.dart';\nclass ContainerConstraints extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n //容器\n color: Colors.blue,\n width: 200,\n height: 200 * 0.618,\n constraints: BoxConstraints(\n minWidth: 100, maxWidth: 150,\n minHeight: 20, maxHeight: 100),\n );\n }\n}"},{"id":null,"widgetId":1,"name":"可对子组件进行对齐定位","priority":3,"subtitle":"【alignment】 : 对齐定位 【AlignmentGeometry】","code":"import 'package:flutter/material.dart';\nclass ContainerAlignment extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n alignment: Alignment.bottomRight,\n width: 200,\n height: 200 * 0.618,\n color: Colors.grey.withAlpha(88),\n child: Icon(\n Icons.android,\n color: Colors.green,\n ),\n );\n }\n}"},{"id":null,"widgetId":1,"name":"可用于显示一个指定宽高的区域","priority":1,"subtitle":"【width】 : 宽 【int】\n【高】: 外边距 【int】\n【color】: 子组件 【Color】","code":"import 'package:flutter/material.dart';\nclass CustomContainer extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n alignment: Alignment.topLeft,\n width: 200,\n height: 200 * 0.618,\n color: Colors.red.withAlpha(88),\n );\n }\n}"},{"id":null,"widgetId":1,"name":"可以在区域中放入一个子组件","priority":2,"subtitle":"【padding】 : 内边距 【EdgeInsetsGeometry】\n【margin】: 外边距 【EdgeInsetsGeometry】\n【child】: 子组件 【Widget】","code":"import 'package:flutter/material.dart';\nclass ContainerWithChild extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n alignment: Alignment.topLeft,\n padding: EdgeInsets.all(20),\n margin: EdgeInsets.all(10),\n width: 200,\n height: 200 * 0.618,\n color: Colors.grey.withAlpha(88),\n child: Icon(Icons.android),\n );\n }\n}"},{"id":null,"widgetId":1,"name":"可对子组件进行装饰","priority":4,"subtitle":" \n【decoration】 : 装饰 【Decoration】\n可装饰: 边线、圆弧、颜色、渐变色、阴影、图片等内容","code":"import 'package:flutter/material.dart';\nclass ContainerDecoration extends StatelessWidget {\n\n static const rainbow = [\n 0xffff0000,\n 0xffFF7F00,\n 0xffFFFF00,\n 0xff00FF00,\n 0xff00FFFF,\n 0xff0000FF,\n 0xff8B00FF\n ];\n\n @override\n Widget build(BuildContext context) {\n var stops = [0.0, 1 / 6, 2 / 6, 3 / 6, 4 / 6, 5 / 6, 1.0];\n return Container(//容器\n alignment: Alignment.center,\n width: 200,\n height: 200 * 0.618,\n margin: EdgeInsets.all(20),\n padding: EdgeInsets.all(20),\n decoration: BoxDecoration(//添加渐变色\n gradient: LinearGradient(\n stops: stops,\n colors: rainbow.map((e) => Color(e)).toList()),\n borderRadius: BorderRadius.only(\n topLeft: Radius.circular(50),\n bottomRight: Radius.circular(50)),\n boxShadow: [\n BoxShadow(\n color: Colors.grey,\n offset: Offset(1, 1),\n blurRadius: 10,\n spreadRadius: 1),\n ]),\n child: Text(\n \"Container\",\n style: TextStyle(fontSize: 20),\n ),\n );\n }\n}"},{"id":null,"widgetId":162,"name":"ListView.separated构造","priority":3,"subtitle":" \n【separatorBuilder】 : 条目构造器 【IndexedWidgetBuilder】","code":"import 'package:flutter/material.dart';\nclass SeparatedListView extends StatelessWidget {\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: ListView.separated(\n separatorBuilder: (context, index) => Divider(\n thickness: 1,\n height: 1,\n color: Colors.orange,\n ),\n itemCount: data.length,\n itemBuilder: (context, index) => _buildItem(data[index]),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n\n Widget _buildItem(Color color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2)\n ]),\n ),\n );\n}\n"},{"id":null,"widgetId":162,"name":"ListView基本使用","priority":1,"subtitle":" \n【children】 : 子组件列表 【List】\n【padding】 : 点击事件 【EdgeInsetsGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomListView extends StatelessWidget {\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: ListView(\n padding: EdgeInsets.symmetric(horizontal: 5),\n children: data\n .map((color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":162,"name":"ListView.builder构造","priority":3,"subtitle":" \n【itemCount】 : 条目个数 【int】\n【itemBuilder】 : 条目构造器 【IndexedWidgetBuilder】","code":"import 'package:flutter/material.dart';\nclass BuilderListView extends StatelessWidget {\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: ListView.builder(\n itemCount: data.length,\n itemBuilder: (context, index) => _buildItem(data[index]),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n\n Widget _buildItem(Color color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2)\n ]),\n ),\n );\n}"},{"id":null,"widgetId":162,"name":"ListView横向滑动","priority":2,"subtitle":" \n【scrollDirection】 : 滑动方向 【Axis】\n【reverse】 : 是否反向滑动 【bool】\n【shrinkWrap】 : 无边界时是否包裹 【bool】","code":"import 'package:flutter/material.dart';\nclass HorizontalListView extends StatelessWidget {\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: ListView(\n reverse: true,\n shrinkWrap: true,\n scrollDirection: Axis.horizontal,\n children: data\n .map((color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":12,"name":"ChoiceChip的普通表现如下","priority":1,"subtitle":"【selectedColor】: 选中时颜色 【Color】\n【selectedShadowColor】: 选中时阴影颜色 【Color】\n【onSelected】: 选中事件 【Fuction(bool)】\n 其他属性同Chip组件,无右侧组件。","code":"import 'package:flutter/material.dart';\nclass CustomChoiceChip extends StatefulWidget {\n @override\n _CustomChoiceChipState createState() => _CustomChoiceChipState();\n}\n\nclass _CustomChoiceChipState extends State {\n bool _select = false;\n\n @override\n Widget build(BuildContext context) {\n return ChoiceChip(\n selected: _select,\n padding: EdgeInsets.all(5),\n labelPadding: EdgeInsets.all(5),\n label: Text(\n _select ?\n \"You are selected it.\" :\n \"This is a ChoiceChip.\",\n style: TextStyle(fontSize: 16),\n ),\n backgroundColor: Colors.grey.withAlpha(66),\n avatar: Image.asset(\"assets/images/icon_head.png\"),\n selectedColor: Colors.orangeAccent.withAlpha(44),\n selectedShadowColor: Colors.blue,\n shadowColor: Colors.orangeAccent,\n elevation: 3,\n onSelected: (value) => setState(() => _select = value),\n );\n }\n}"},{"id":null,"widgetId":141,"name":"SnackBarAction基本使用","priority":1,"subtitle":" \n【label】 : 标签 【String】\n【textColor】 : 文字颜色 【Color】\n【disabledTextColor】 : 文字失效色 【Color】\n【onPressed】 : 点击回调 【Function()】","code":"import 'package:flutter/material.dart';\nclass CustomSnackBarAction extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: SnackBarAction(\n disabledTextColor: Colors.red,\n textColor: Colors.blue,\n label: '确定',\n onPressed: () => print('onPressed')));\n }\n}\n"},{"id":null,"widgetId":168,"name":"文字样式-ThemeData#TextTheme","priority":1,"subtitle":" \n子组件可以通过ThemeData.of获取主题的数据进行使用。","code":"import 'package:flutter/material.dart';\nclass TextThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var queryData = Theme.of(context).textTheme;\n var styles = {\n \"headline: \": queryData.headline,\n \"title: \": queryData.title,\n \"subhead: \": queryData.subhead,\n \"subtitle: \": queryData.body1,\n \"body2: \": queryData.body2,\n \"button: \": queryData.button,\n \"overline: \": queryData.overline,\n \"subtitle: \": queryData.subtitle,\n \"button: \": queryData.caption,\n \"display1: \": queryData.display1,\n \"display2: \": queryData.display2,\n \"display3: \": queryData.display3,\n \"display4: \": queryData.display4,\n };\n\n return Container(\n child: Column(\n children: styles.keys.map((e) => buildItem(e, styles[e])).toList(),\n ),\n );\n }\n\n Widget buildItem(String e, TextStyle style) => Column(\n children: [\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Row(\n mainAxisAlignment: MainAxisAlignment.spaceBetween,\n children: [\n Text(\n e,\n style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),\n ),\n Text(\n \"@toly\",\n style: style,\n )\n ],\n ),\n ),\n Divider(\n height: 1,\n )\n ],\n );\n}"},{"id":null,"widgetId":168,"name":"Theme的用法","priority":2,"subtitle":" \n使用Theme,可以指定非常多的属性作为主题,这些属性将应用于所有的后代组件,如指定字体、滑块、卡片、文字、分割线、按钮等属性。","code":"import 'package:flutter/material.dart';\nclass CustomTheme extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Theme(\n data: ThemeData(\n cardTheme: CardTheme(color: Colors.red, elevation: 4),\n dividerTheme: DividerThemeData(\n color: Colors.blue,\n thickness: 2\n ),\n sliderTheme: SliderThemeData(\n thumbColor: Colors.red,\n activeTrackColor: Colors.green,\n inactiveTrackColor: Colors.grey,\n )),\n child: Wrap(\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n Card(\n child: Container(\n width: 50,\n height: 50,\n color: Colors.transparent,\n ),\n ),\n Container(\n width: 150,\n child: Slider(value: 0.8, onChanged: (v) => {})),\n Container( width: 150,child: Divider())\n ]));\n }\n}"},{"id":null,"widgetId":15,"name":"FilterChip可接受选择事件","priority":1,"subtitle":"【selected】: 是否选择 【bool】\n【onSelected】: 选择事件 【Function(bool)】\n【selectedColor】: 选择后的颜色 【Color】\n【selectedShadowColor】: 选择后的阴影颜色 【Color】,","code":"import 'package:flutter/material.dart';\nclass CustomFilterChip extends StatefulWidget {\n @override\n _CustomFilterChipState createState() => _CustomFilterChipState();\n}\n\nclass _CustomFilterChipState extends State {\n final Map map = {\n 'A': 'Ant',\n 'B': 'Bug',\n 'C': 'Cat',\n 'D': 'Dog',\n };\n List _selected = [];\n\n @override\n Widget build(BuildContext context) {\n return Column(\n mainAxisAlignment: MainAxisAlignment.center,\n children: [\n Wrap(\n children: map.keys.map((key) =>\n _buildChild(key)).toList(),\n ),\n Container(\n padding: EdgeInsets.all(10),\n child: Text('您已选择: ${_selected.join(', ')}')),\n ],\n );\n }\n\n Padding _buildChild(String key) {\n return Padding(\n padding: const EdgeInsets.all(4.0),\n child: FilterChip(\n selectedColor: Colors.orange.withAlpha(55),\n selectedShadowColor: Colors.blue,\n shadowColor: Colors.orangeAccent,\n pressElevation: 5,\n elevation: 3,\n avatar: CircleAvatar(child: Text(key)),\n label: Text(map[key]),\n selected: _selected.contains(map[key]),\n onSelected: (bool value) {\n setState(() {\n if (value) {\n _selected.add(map[key]);\n } else {\n _selected.removeWhere((name) => name == map[key]);\n }\n });\n },\n ),\n );\n }\n}\n"},{"id":null,"widgetId":352,"name":"CupertinoDialogAction基本使用","priority":1,"subtitle":"【isDefaultAction】 : 是否是默认性操作 【bool】\n【isDestructiveAction】 : 是否是毁灭性操作 【bool】\n【textStyle】: 文字样式 【TextStyle】\n【onPressed】: 点击事件 【VoidCallback】\n【child】: 子组件 【Widget】","code":"import 'dart:async';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'dart:ui' as ui;\nclass CupertinoDialogActionDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n CupertinoDialogAction(\n isDestructiveAction: false,\n onPressed: ()=>_toast(context),\n child: Text('CupertinoDialogAction'),\n ),\n CupertinoDialogAction(\n isDestructiveAction: true,\n onPressed: ()=>_toast(context),\n child: Text('CupertinoDialogAction'),\n ),\n ],\n );\n }\n\n _toast(BuildContext context){\n Scaffold.of(context).showSnackBar(SnackBar(\n backgroundColor: Theme.of(context).primaryColor,\n content: Text('CupertinoDialogAction'),));\n }\n}\n"},{"id":null,"widgetId":34,"name":"Divider颜色和粗细","priority":1,"subtitle":"【color】: 颜色 【Color】\n【thickness】: 线粗细 【double】","code":"import 'package:flutter/material.dart';\nclass CustomDivider extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var dataColor = [\n Colors.red, Colors.yellow,\n Colors.blue, Colors.green];\n var dataThickness = [1.0, 2.0, 4.0, 6.0];\n var data = Map.fromIterables(dataColor, dataThickness);\n return Column(\n children: dataColor\n .map((e) => Divider(\n color: e,\n thickness: data[e],\n ))\n .toList(),\n );\n }\n}"},{"id":null,"widgetId":34,"name":"Divider高度和空缺","priority":2,"subtitle":"【indent】: 前面空缺长度 【double】\n【endIndent】: 后面空缺长度 【double】\n【height】: 占位高 【double】","code":"import 'package:flutter/material.dart';\nclass HeightDivider extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var dataColor = [\n Colors.red, Colors.yellow,\n Colors.blue, Colors.green];\n var dataThickness = [10.0, 20.0, 30.0, 40.0];\n var data = Map.fromIterables(dataColor, dataThickness);\n return Column(\n children: dataColor\n .map((e) => Divider(\n color: e,\n indent:data[e],\n endIndent: data[e]*2,\n height: data[e],\n thickness: data[e]/10,\n ))\n .toList(),\n );\n }\n}\n"},{"id":null,"widgetId":205,"name":"TabPageSelector基本使用","priority":1,"subtitle":"【controller】 : 控制器 【TabController】\n【indicatorSize】: 指示器大小 【double】\n【selectedColor】: 选中色 【Color】\n【color】: 颜色 【Color】","code":"import 'package:flutter/material.dart';\nclass TabPageSelectorDemo extends StatefulWidget {\n @override\n _TabPageSelectorDemoState createState() => _TabPageSelectorDemoState();\n}\n\nclass _TabPageSelectorDemoState extends State\n with SingleTickerProviderStateMixin {\n final tabs = ['风画庭', '雨韵舍', '雷鸣殿', '电疾堂', '霜寒阁', '雪月楼'];\n TabController _tabController;\n\n @override\n void initState() {\n super.initState();\n _tabController = TabController(vsync: this, length: tabs.length);\n }\n\n @override\n void dispose() {\n _tabController.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: Stack(\n alignment: Alignment.center,\n children: [\n Container(color: Colors.purple, child: _buildTableBarView()),\n Positioned(\n bottom: 10,\n child: buildTabPageSelector(),\n ),\n ],\n ),\n );\n }\n\n Widget buildTabPageSelector() => TabPageSelector(\n controller: _tabController,\n color: Colors.white,\n indicatorSize: 10,\n selectedColor: Colors.orangeAccent,\n );\n\n Widget _buildTableBarView() => TabBarView(\n controller: _tabController,\n children: tabs\n .map((e) => Center(\n child: Text(\n e,\n style: TextStyle(color: Colors.white, fontSize: 20),\n )))\n .toList());\n}\n"},{"id":null,"widgetId":142,"name":"BottomSheet基本使用","priority":1,"subtitle":" \n【builder】 : 组件构造器 【WidgetBuilder】\n【backgroundColor】 : 背景色 【Color】\n【elevation】 : 影深 【double】\n【shape】 : 形状 【ShapeBorder】\n【onClosing】 : 关闭回调 【Function()】","code":"import 'package:flutter/material.dart';\nclass CustomBottomSheet extends StatefulWidget {\n @override\n _CustomBottomSheetState createState() => _CustomBottomSheetState();\n}\n\nclass _CustomBottomSheetState extends State {\n bool opened = false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: FlatButton(\n color: Colors.blue,\n onPressed: () {\n opened = !opened;\n opened\n ? Scaffold.of(context)\n .showBottomSheet((_) => _buildBottomSheet())\n : Navigator.of(context).pop();\n },\n child: Text(\n '点我显隐BottomSheet',\n style: TextStyle(color: Colors.white),\n )));\n }\n\n Widget _buildBottomSheet() => BottomSheet(\n enableDrag: true,\n elevation: 4,\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.only(\n topRight: Radius.circular(60),\n topLeft: Radius.circular(60),\n )\n ),\n backgroundColor: Colors.transparent,\n onClosing: () => print('onClosing'),\n builder: (_) => (Container(\n height: 250,\n decoration: BoxDecoration(\n image: DecorationImage(\n image: AssetImage('assets/images/sabar_bar.jpg'),\n fit: BoxFit.cover),\n borderRadius: BorderRadius.only(\n topRight: Radius.circular(60),\n topLeft: Radius.circular(60),\n )),\n )));\n}\n"},{"id":null,"widgetId":17,"name":"CheckBoxListTile的选中效果","priority":2,"subtitle":"【selected】: 是否选中 【bool】","code":"import 'package:flutter/material.dart';\nclass SelectCheckBoxListTile extends StatefulWidget {\n @override\n _SelectCheckBoxListTileState createState() => _SelectCheckBoxListTileState();\n}\n\nclass _SelectCheckBoxListTileState extends State {\n var _selected = false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(10),\n color: Colors.grey.withAlpha(22),\n child: CheckboxListTile(\n value: _selected,\n selected: _selected,\n checkColor: Colors.yellow,\n activeColor: Colors.orangeAccent,\n secondary: Image.asset(\"assets/images/icon_head.png\"),\n title: Text(\"张风捷特烈\"),\n subtitle: Text(\"@万花过尽知无物\"),\n onChanged: (v) => setState(() => _selected = !_selected),\n ),\n );\n }\n}"},{"id":null,"widgetId":17,"name":"CheckBoxListTile的密排属性","priority":3,"subtitle":"【dense】: 是否密排 【bool】","code":"import 'package:flutter/material.dart';\nclass DenseCheckBoxListTile extends StatefulWidget {\n @override\n _DenseCheckBoxListTileState createState() => _DenseCheckBoxListTileState();\n}\n\nclass _DenseCheckBoxListTileState extends State {\n var _selected = false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(10),\n color: Colors.grey.withAlpha(22),\n child: CheckboxListTile(\n value: _selected,\n dense: true,\n checkColor: Colors.yellow,\n activeColor: Colors.orangeAccent,\n secondary: Image.asset(\"assets/images/icon_head.png\"),\n title: Text(\"张风捷特烈\"),\n subtitle: Text(\"@万花过尽知无物\"),\n onChanged: (v) => setState(() => _selected = !_selected),\n ),\n );\n }\n}"},{"id":null,"widgetId":17,"name":"CheckBoxListTile的基本表现如下","priority":1,"subtitle":"【secondary】: 左侧组件 【Widget】\n【checkColor】: ✔️颜色 【Color】\n【activeColor】: 选中时外框颜色 【Color】\n【title】: 中间上组件 【Widget】\n【subtitle】: 中间下组件 【Widget】\n【onChanged】: 选中事件 【Function(bool)】","code":"import 'package:flutter/material.dart';\nclass CustomCheckBoxListTile extends StatefulWidget {\n @override\n _CustomCheckBoxListTileState createState() => _CustomCheckBoxListTileState();\n}\n\nclass _CustomCheckBoxListTileState extends State {\n var _selected = false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(10),\n color: Colors.grey.withAlpha(22),\n child: CheckboxListTile(\n value: _selected,\n checkColor: Colors.yellow,\n activeColor: Colors.orangeAccent,\n secondary: Image.asset(\"assets/images/icon_head.png\"),\n title: Text(\"张风捷特烈\"),\n subtitle: Text(\"@万花过尽知无物\"),\n onChanged: (v) => setState(() => _selected = !_selected),\n ),\n );\n }\n}"},{"id":null,"widgetId":203,"name":"OrientationBuilder基本使用","priority":1,"subtitle":"【builder】 : 方向组件构造器 【OrientationWidgetBuilder】","code":"import 'package:flutter/material.dart';\nclass OrientationBuilderDemo extends StatefulWidget {\n @override\n _OrientationBuilderDemoState createState() => _OrientationBuilderDemoState();\n}\n\nclass _OrientationBuilderDemoState extends State {\n double _width = 200;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Container(\n width: _width,\n height: 200,\n child: OrientationBuilder(builder: _builder),\n color: Colors.orange,\n ),\n _buildSlider()\n ],\n );\n }\n\n //根据回调的orientation返回组件\n Widget _builder(BuildContext context, Orientation orientation) {\n switch(orientation){\n case Orientation.portrait:\n return Icon(Icons.phone_android,size: 60,);\n break;\n case Orientation.landscape:\n return RotatedBox(\n quarterTurns: 1,\n child: Icon(Icons.phone_android,size: 60,));\n break;\n default: return Container();\n }\n }\n\n Widget _buildSlider() =>Slider(\n value: _width,\n max: 350.0,\n min: 80.0,\n divisions: 17,\n onChanged: (v)=> setState(() => _width= v),\n );\n}\n"},{"id":null,"widgetId":132,"name":"CupertinoActionSheetAction基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【isDefaultAction】 : 是否默认选中 【bool】\n【onPressed】 : 点击事件 【Function()】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport '../../../dialogs/dialog_about.dart';\nclass CustomCupertinoActionSheetAction extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Container(\n margin: EdgeInsets.all(5),\n color: Colors.grey.withAlpha(33),\n child: CupertinoActionSheetAction(\n isDefaultAction: true,\n onPressed: () => DialogAbout.show(context),\n child: Text('张风捷特烈')),\n ),\n Container(\n color: Colors.grey.withAlpha(33),\n margin: EdgeInsets.all(5),\n child: CupertinoActionSheetAction(\n isDefaultAction: false,\n onPressed: () => DialogAbout.show(context),\n child: Text('百里·巫缨')),\n ),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":125,"name":"AnimatedIcon基本使用","priority":1,"subtitle":" \n【icon】 : 动画图标数据 【AnimatedIcons】\n【size】 : 大小 【double】\n【color】 : 颜色 【Color】\n【progress】 : 动画 【Animation】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomAnimatedIcon extends StatefulWidget {\n @override\n _CustomAnimatedIconState createState() => _CustomAnimatedIconState();\n}\n\nclass _CustomAnimatedIconState extends State\n with SingleTickerProviderStateMixin {\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 1));\n _ctrl.forward();\n super.initState();\n }\n\n @override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Wrap(\n runSpacing: 30,\n children: _buildChildren(),\n ),\n );\n }\n\n final data = {\n Colors.orange: AnimatedIcons.menu_arrow,\n Colors.blue: AnimatedIcons.ellipsis_search,\n Colors.red: AnimatedIcons.close_menu,\n Colors.green: AnimatedIcons.arrow_menu,\n Colors.cyanAccent: AnimatedIcons.play_pause,\n Colors.purple: AnimatedIcons.pause_play,\n };\n\n List _buildChildren() => data.keys\n .map((e) => AnimatedIcon(\n size: 50,\n color: e,\n icon: data[e],\n progress: _ctrl,\n ))\n .toList();\n}\n"},{"id":null,"widgetId":130,"name":"AboutDialog基本使用","priority":1,"subtitle":" \n【applicationIcon】 : 左上图标 【Widget】\n【applicationVersion】 : 版本号 【String】\n【applicationName】 : 应用名 【String】\n【applicationLegalese】 : 应用律术 【String】\n【children】 : 子组件列表 【List】","code":"import 'package:flutter/material.dart';\nclass CustomAboutDialog extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Stack(\n children: [\n _buildAboutDialog(),\n Positioned(top: 50, right: 20, child: _buildRaisedButton(context)),\n ],\n );\n }\n\n Widget _buildRaisedButton(BuildContext context) => RaisedButton(\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n color: Colors.blue,\n onPressed: () {\n showDialog(context: context, builder: (ctx) => _buildAboutDialog());\n },\n child: Text(\n 'Just Show It',\n style: TextStyle(color: Colors.white),\n ),\n );\n\n AboutDialog _buildAboutDialog() {\n return AboutDialog(\n applicationIcon: FlutterLogo(),\n applicationVersion: 'v0.0.1',\n applicationName: 'Flutter Unit',\n applicationLegalese: 'Copyright© 2018-2020 张风捷特烈',\n children: [\n Container(\n margin: EdgeInsets.only(top: 20),\n width: 80,\n height: 80,\n child: Image.asset('assets/images/icon_head.png')),\n Container(\n margin: EdgeInsets.only(top: 10),\n alignment: Alignment.center,\n child: Text(\n 'The King Of Coder.',\n style: TextStyle(color: Colors.white, fontSize: 20, shadows: [\n Shadow(\n color: Colors.blue, offset: Offset(.5, .5), blurRadius: 3)\n ]),\n ))\n ],\n );\n }\n}\n"},{"id":null,"widgetId":8,"name":"FadeInImage.assetNetwork加载网络图片","priority":1,"subtitle":"【placeholder】 : 展位图地址 【String】\n【image】 : 显示图地址 【String】\n【width】: 宽 【double】\n【height】: 高 【double】\n【fadeInDuration】: 淡入时长 【Duration】\n【fadeOutDuration】: 淡出时长 【Duration】\n【fadeInCurve】: 淡入曲线 【Cubic】\n【fadeOutCurve】: 淡出曲线 【Cubic】\n【fit】: 适应模式 【BoxFit】\n【alignment】: 对齐模式 【Alignment】\n【repeat】: 重复模式 【ImageRepeat】,","code":"import 'package:flutter/material.dart';\nclass CustomFadeInImage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var placeholder = \"assets/images/icon_head.png\";\n var img =\n \"https://user-gold-cdn.xitu.io/2017/8/24/\"\n \"d324efef8cbee6468a018aad7ab2ba6b?imageView2/\"\n \"1/w/180/h/180/q/85/format/webp/interlace/1\";\n return FadeInImage.assetNetwork(\n placeholder: placeholder,\n image: img,\n width: 100,\n height: 100,\n fit: BoxFit.cover,\n repeat:ImageRepeat.noRepeat,\n alignment: Alignment.center,\n fadeInDuration: Duration(seconds: 5),\n fadeInCurve: Curves.easeInCubic,\n );\n }\n}\n"},{"id":null,"widgetId":36,"name":"Placeholder的fallback属性","priority":2,"subtitle":" 当所在区域无宽高约束时,占位组件的宽高。\"\n【fallbackHeight】: 高 【double】\n【fallbackWidth】: 宽 【double】","code":"import 'package:flutter/material.dart';\nclass FallbackPlaceholder extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return UnconstrainedBox(\n child: Placeholder(\n color: Colors.blue,\n strokeWidth: 2,\n fallbackHeight: 100,\n fallbackWidth: 150,\n ),\n );\n }\n}\n"},{"id":null,"widgetId":36,"name":"Placeholder基础属性","priority":1,"subtitle":"【color】: 颜色 【Color】\n【strokeWidth】: 线粗 【double】","code":"import 'package:flutter/material.dart';\nclass CustomPlaceholder extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 100,\n height: 100*0.618,\n child: Placeholder(\n color: Colors.orangeAccent,\n strokeWidth: 2,\n ),\n );\n }\n}"},{"id":null,"widgetId":30,"name":"IconButton属性","priority":1,"subtitle":"【child】: 子组件 【Widget】\n【icon】: 内边距 【Widget】\n【tooltip】: 长按提示文字 【String】\n【highlightColor】: 长按高亮色 【Color】\n【splashColor】: 水波纹色 【Color】\n【onPressed】: 点击事件 【Function】","code":"import 'package:flutter/material.dart';\nclass CustomIconButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Padding(\n padding: const EdgeInsets.all(8.0),\n child: IconButton(\n padding: EdgeInsets.only(),\n onPressed: () {},\n icon: Icon(Icons.android, size: 40, color: Colors.green),\n tooltip: \"android\",\n highlightColor: Colors.orangeAccent,\n splashColor: Colors.blue,\n ),\n );\n }\n}\n"},{"id":null,"widgetId":14,"name":"可以接受选中事件","priority":2,"subtitle":"【selected】: 是否选中 【bool】\n【onSelected】: 选中事件 【Function(bool)】","code":"import 'package:flutter/material.dart';\nclass SelectInputChip extends StatefulWidget {\n @override\n _SelectInputChipState createState() => _SelectInputChipState();\n}\n\nclass _SelectInputChipState extends State {\n bool _select = false;\n\n @override\n Widget build(BuildContext context) {\n return InputChip(\n selected: _select,\n padding: EdgeInsets.all(5),\n labelPadding: EdgeInsets.all(3),\n label: Text(\"This is a InputChip.\"),\n backgroundColor: Colors.grey.withAlpha(66),\n avatar: Image.asset(\"assets/images/icon_head.png\"),\n selectedColor: Colors.orangeAccent.withAlpha(88),\n selectedShadowColor: Colors.blue,\n shadowColor: Colors.orangeAccent,\n elevation: 3,\n onDeleted: () => Navigator.of(context).pushNamed('AboutMePage'),\n onSelected: (bool value) {\n setState(() {\n _select = value;\n });\n print(\"onSelected\");\n },\n );\n }\n}"},{"id":null,"widgetId":14,"name":"可以接受点击、删除事件","priority":1,"subtitle":"【onPressed】: 点击事件 【Function()】\n【onDeleted】: 删除事件 【Function()】","code":"import 'package:flutter/material.dart';\nclass PressInputChip extends StatefulWidget {\n @override\n _PressInputChipState createState() => _PressInputChipState();\n}\n\nclass _PressInputChipState extends State {\n bool _delete = false;\n\n @override\n Widget build(BuildContext context) {\n return InputChip(\n padding: EdgeInsets.all(5),\n labelPadding: EdgeInsets.all(3),\n label: Text(\n !_delete ?\n \"This is a InputChip.\" :\n \"You are clicked delete icon.\"),\n backgroundColor: Colors.grey.withAlpha(66),\n avatar: Image.asset(\"assets/images/icon_head.png\"),\n selectedColor: Colors.orangeAccent.withAlpha(88),\n selectedShadowColor: Colors.blue,\n shadowColor: Colors.orangeAccent,\n elevation: 3,\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'),\n onDeleted: () => setState(() => _delete = !_delete));\n }\n}"},{"id":null,"widgetId":2,"name":"文字对齐方式","priority":4,"subtitle":"【textAlign】: 对齐方式 【TextAlign】\n下面依次是:left、right、center、justify、start、end,","code":"import 'package:flutter/material.dart';\nclass TextAlignText extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n runSpacing: 10,\n children: TextAlign.values\n .map((e) => Container(\n width: 120,\n color: Colors.cyanAccent.withAlpha(33),\n height: 120 * 0.618,\n child: Text(\n \" 张风捷特烈 toly \" * 3,\n textAlign: e,\n ),\n ))\n .toList(),\n );\n }\n}"},{"id":null,"widgetId":2,"name":"文字的基本样式","priority":1,"subtitle":"【入参】 : 文字 【String】\n【style】: 文字样式 【TextStyle】\n【color】: 文字样式 【Color】\n【fontSize】: 文字大小 【double】\n【fontWeight】: 字重 【FontWeight】\n【fontStyle】: 字体样式 【fontStyle】\n【letterSpacing】: 字距 【double】","code":"import 'package:flutter/material.dart';\nclass CustomText extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var style = TextStyle(\n color: Colors.blue,\n fontSize: 20,\n fontWeight: FontWeight.bold,\n fontStyle: FontStyle.italic,\n letterSpacing: 10,\n );\n return Container(\n width: 200,\n color: Colors.cyanAccent.withAlpha(33),\n height: 200 * 0.618 * 0.618,\n child: Text(\"toly-张风捷特烈-1994`\", style: style),\n );\n }\n}"},{"id":null,"widgetId":2,"name":"文字方向与最大行数","priority":5,"subtitle":"【maxLines】 : 最大行数 【int】\n【【textDirection】 : 文字方向 【TextDirection】\n下面依次是:rtl、ltr,","code":"import 'package:flutter/material.dart';\nclass TextDirectionText extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 40,\n runSpacing: 10,\n children: TextDirection.values\n .map((e) => Container(\n width: 120,\n color: Colors.cyanAccent.withAlpha(33),\n height: 120 * 0.618,\n child: Text(\n \" 张风捷特烈 toly \" * 10,\n textDirection: e,\n maxLines: 3,\n overflow: TextOverflow.ellipsis,\n ),\n ))\n .toList(),\n );\n }\n}"},{"id":null,"widgetId":2,"name":"文字装饰线","priority":3,"subtitle":"【fontFamily】 : 文字字体 【String】\n【decoration】: 装饰线 【TextDecoration】\n【decorationColor】: 装饰线颜色 【Color】\n【decorationThickness】: 装饰线粗 【double】\n【decorationStyle】: 装饰线样式 【TextDecorationStyle】","code":"import 'package:flutter/material.dart';\nclass DecorationText extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Text(\n \"19940328\",\n style: TextStyle(\n fontSize: 50,\n fontWeight: FontWeight.bold,\n decoration: TextDecoration.underline,\n decorationThickness: 3,\n decorationStyle: TextDecorationStyle.wavy,\n decorationColor: Colors.blue,\n fontStyle: FontStyle.italic,\n fontFamily: \"DancingScript\",\n letterSpacing: 10),\n );\n }\n}"},{"id":null,"widgetId":2,"name":"是否包裹与越界效果","priority":6,"subtitle":"【softWrap】 : 是否换行 【bool】\n【overflow】 : 越界效果 【TextOverflow】\n下面softWrap=false; overflow依次是:clip、fade、ellipsis、visible,","code":"import 'package:flutter/material.dart';\nclass SoftWrapText extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n runSpacing: 10,\n children: TextOverflow.values\n .map((e) => Container(\n width: 150,\n color: Colors.cyanAccent.withAlpha(33),\n height: 150 * 0.618 * 0.618,\n child: Text(\" 张风捷特烈 toly \" * 5,\n overflow: e,\n softWrap: false),\n ))\n .toList(),\n );\n }\n}\n"},{"id":null,"widgetId":2,"name":"文字阴影","priority":2,"subtitle":"【shadows】 : 文字 【List】\n【backgroundColor】: 背景颜色 【Color】","code":"import 'package:flutter/material.dart';\nclass ShadowText extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Text(\n \"张风捷特烈\",\n style: TextStyle(\n fontSize: 50,\n color: Colors.white,\n backgroundColor: Colors.black,\n shadows: [\n Shadow(\n color: Colors.cyanAccent,\n offset: Offset(1, 1),\n blurRadius: 10),\n Shadow(\n color: Colors.blue,\n offset: Offset(-0.1, 0.1),\n blurRadius: 10),\n ]),\n );\n }\n}"},{"id":null,"widgetId":6,"name":"用于显示一个图标","priority":1,"subtitle":"【入参】 :图标数据 【IconData】\n【size】 : 大小 【double】\n【color】: 颜色 【Color】","code":"import 'package:flutter/material.dart';\nclass CustomIcon extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n Icon(\n Icons.send,\n color: Colors.orange,\n size: 60,\n ),\n Icon(\n Icons.android,\n color: Colors.green,\n size: 100,\n ),\n ],\n );\n }\n}"},{"id":null,"widgetId":6,"name":"使用自定义图标","priority":2,"subtitle":"可在iconfont网站中下载图标字体进行使用","code":"import 'package:flutter/material.dart';\nimport '../../../../app/style/TolyIcon.dart';\nclass MyIcon extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 20,\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n TolyIcon.icon_search,\n TolyIcon.icon_star,\n TolyIcon.icon_layout,\n TolyIcon.icon_star_ok\n ]\n .map((e) => Icon(\n e,\n color: Colors.green,\n size: 60,\n ))\n .toList(),\n );\n }\n}\n\n"},{"id":null,"widgetId":169,"name":"文字样式-TextTheme","priority":1,"subtitle":"后代组件可以通过CupertinoTheme.of获取主题的数据进行使用。","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass TextCupertinoTheme extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var queryData = CupertinoTheme.of(context).textTheme;\n var styles = {\n \"tabLabelTextStyle: \": queryData.tabLabelTextStyle,\n \"actionTextStyle: \": queryData.actionTextStyle,\n \"navActionTextStyle: \": queryData.navActionTextStyle,\n \"textStyle: \": queryData.textStyle,\n \"navTitleTextStyle: \": queryData.navTitleTextStyle,\n \"pickerTextStyle: \": queryData.pickerTextStyle,\n \"dateTimePickerTextStyle: \": queryData.dateTimePickerTextStyle,\n \"navLargeTitleTextStyle: \": queryData.navLargeTitleTextStyle,\n };\n\n return Container(\n child: Column(\n children: styles.keys.map((e) => buildItem(e, styles[e])).toList(),\n ),\n );\n }\n\n Widget buildItem(String e, TextStyle style) => Column(\n children: [\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Row(\n mainAxisAlignment: MainAxisAlignment.spaceBetween,\n children: [\n Text(\n e,\n style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),\n ),\n Text(\n \"@toly\",\n style: style,\n )\n ],\n ),\n ),\n Divider(\n height: 1,\n )\n ],\n );\n}"},{"id":null,"widgetId":169,"name":"CupertinoThemeData的使用","priority":2,"subtitle":" \n和Theme一样可以通过指定的属性,让它们在后代中共享,不过属性较少。注意如果需要使用主题,不能在当前的context中获取。","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoTheme extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return CupertinoTheme(\n data: CupertinoThemeData(\n primaryColor: Colors.blue,\n primaryContrastingColor: Colors.green\n ),\n child: _ChildUseTheme());\n }\n}\n\nclass _ChildUseTheme extends StatelessWidget {\n const _ChildUseTheme({\n Key key,\n }) : super(key: key);\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n Container(\n width: 50,\n height: 50,\n color: CupertinoTheme.of(context).primaryContrastingColor,\n ),\n Container(\n width: 150,\n child: Slider(value: 0.8, onChanged: (v) => {})),\n Container( width: 150,child: Divider(color:CupertinoTheme.of(context).primaryContrastingColor,thickness: 1,))\n ]);\n }\n}"},{"id":null,"widgetId":25,"name":"FlatButton点击事件","priority":1,"subtitle":"【color】: 颜色 【Color】\n【splashColor】: 水波纹颜色 【Color】\n【child】: 子组件 【Widget】\n【textColor】: 子组件文字颜色 【Color】\n【highlightColor】: 长按高亮色 【Color】\n【padding】: 内边距 【EdgeInsetsGeometry】\n【onPressed】: 点击事件 【Function】","code":"import 'package:flutter/material.dart';\nclass CustomFlatButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return FlatButton(\n onPressed: ()=>{},\n padding: EdgeInsets.all(8),\n splashColor: Colors.green,\n child: Text(\"FlatButton\"),\n textColor: Color(0xffFfffff),\n color: Colors.blue,\n highlightColor: Color(0xffF88B0A),\n );\n }\n}\n"},{"id":null,"widgetId":133,"name":"SimpleDialogOption基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【onPressed】 : 点击事件 【Function()】","code":"import 'package:flutter/material.dart';\nclass CustomSimpleDialogOption extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Container(\n alignment: Alignment.center,\n width: double.infinity,\n height: 50,\n margin: EdgeInsets.all(5),\n color: Colors.grey.withAlpha(33),\n child: SimpleDialogOption(\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'),\n child: Text('张风捷特烈')),\n ),\n Container(\n height: 50,\n alignment: Alignment.center,\n width: double.infinity,\n color: Colors.grey.withAlpha(33),\n margin: EdgeInsets.all(5),\n child: SimpleDialogOption(\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'),\n child: Text('百里·巫缨')),\n ),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":31,"name":"BackButton属性","priority":1,"subtitle":"【color】: 颜色 【Color】\n【onPressed】: 点击事件 【Function】\n onPressed为空会退出当前栈","code":"import 'package:flutter/material.dart';\nclass CustomBackButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var data = [Colors.red,Colors.yellow,Colors.blue,Colors.green];\n return Wrap(\n spacing: 10,\n children: data.map((e)=>BackButton(\n color: e,\n )).toList()\n );\n }\n}\n"},{"id":null,"widgetId":202,"name":"Builder的使用","priority":1,"subtitle":"【builder】 : 组件构造器 【WidgetBuilder】\n同一个类中使用`XXX.of(context)`获取某类状态对象方法会存在`上下文滞后`的错误,使用Builder解决。","code":"import 'package:flutter/material.dart';\nclass BuilderDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: Scaffold(\n appBar: AppBar(\n title: Text('Builder'),\n ),\n floatingActionButton: Builder(\n builder: (ctx) => FloatingActionButton(\n onPressed: () {\n Scaffold.of(ctx)\n .showSnackBar(SnackBar(content: Text('hello builder')));\n },\n child: Icon(Icons.add),\n ),\n ),\n ),\n );\n }\n\n\n}\n"},{"id":null,"widgetId":18,"name":"SwitchListTile的密排属性","priority":3,"subtitle":"【dense】: 是否密排 【bool】","code":"import 'package:flutter/material.dart';\nclass DenseSwitchListTile extends StatefulWidget {\n @override\n _DenseSwitchListTileState createState() => _DenseSwitchListTileState();\n}\n\nclass _DenseSwitchListTileState extends State {\n var _value=false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(10),\n color: Colors.grey.withAlpha(22),\n child: SwitchListTile(\n value: _value,\n dense: true,\n selected: _value,\n activeColor: Colors.orangeAccent,\n secondary: Image.asset(\"assets/images/icon_head.png\"),\n title: Text(\"张风捷特烈\"),\n subtitle: Text(\"@万花过尽知无物\"),\n onChanged: (v) => setState(() => _value = !_value),\n ),\n );\n }\n}"},{"id":null,"widgetId":18,"name":"SwitchListTile的选中效果","priority":2,"subtitle":"【selected】: 是否选中 【bool】\n【inactiveThumbImage】: 未选中时圆圈图片 【ImageProvider】\n【activeThumbImage】: 选中时圆圈图片 【ImageProvider】","code":"import 'package:flutter/material.dart';\nclass SelectSwitchListTile extends StatefulWidget {\n @override\n _SelectSwitchListTileState createState() => _SelectSwitchListTileState();\n}\n\nclass _SelectSwitchListTileState extends State {\n var _value=false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(10),\n color: Colors.grey.withAlpha(22),\n child: SwitchListTile(\n value: _value,\n selected: _value,\n activeColor: Colors.orangeAccent,\n secondary: Image.asset(\"assets/images/icon_head.png\"),\n inactiveThumbImage: AssetImage(\"assets/images/pica.gif\"),\n activeThumbImage: AssetImage(\"assets/images/icon_head.png\"),\n title: Text(\"张风捷特烈\"),\n subtitle: Text(\"@万花过尽知无物\"),\n onChanged: (v) => setState(() => _value = !_value),\n ),\n );\n }\n}"},{"id":null,"widgetId":18,"name":"SwitchListTile的基本表现如下","priority":1,"subtitle":"【secondary】: 左侧组件 【Widget】\n【title】: 中间上组件 【Widget】\n【subtitle】: 中间下组件 【Widget】\n【inactiveThumbColor】: 未选中时圆圈颜色 【Color】\n【inactiveTrackColor】: 未选中滑槽颜色 【Color】\n【activeColor】: 选中时圆圈颜色 【Color】\n【activeTrackColor】: 选中滑槽颜色 【Color】\n【onChanged】: 选中事件 【Function(bool)】","code":"import 'package:flutter/material.dart';\nclass CustomSwitchListTile extends StatefulWidget {\n @override\n _CustomSwitchListTileState createState() => _CustomSwitchListTileState();\n}\n\nclass _CustomSwitchListTileState extends State {\n var _value=false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(10),\n color: Colors.grey.withAlpha(22),\n child: SwitchListTile(\n value: _value,\n inactiveThumbColor:Colors.cyanAccent ,\n inactiveTrackColor: Colors.blue.withAlpha(88),\n activeColor: Colors.orangeAccent,\n activeTrackColor: Colors.orange,\n secondary: Image.asset(\"assets/images/icon_head.png\"),\n title: Text(\"张风捷特烈\"),\n subtitle: Text(\"@万花过尽知无物\"),\n onChanged: (v) => setState(() => _value = !_value),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":102,"name":"DataTable的sort","priority":2,"subtitle":" \n【sortColumnIndex】 : 列号 【int】\n【columnSpacing】 : 列间距 【double】\n【sortAscending】 : 是否顺序 【bool】","code":"import 'package:flutter/material.dart';\nclass _BeanOp {\n final int id;\n final String name;\n final String type;\n bool select;\n\n _BeanOp(this.id, this.name, this.type, this.select);\n\n @override\n String toString() {\n return '_BeanOp{id: $id, name: $name, type: $type, select: $select}';\n }\n}\n\nclass SortDataTable extends StatefulWidget {\n @override\n _SortDataTableState createState() => _SortDataTableState();\n}\n\nclass _SortDataTableState extends State {\n var data = [\n _BeanOp(101, 'DataTable', 'StatelessWidget', false),\n _BeanOp(44, 'RangeSlider', 'StatefulWidget', false),\n _BeanOp(2, 'Text', 'StatelessWidget', false),\n _BeanOp(1, 'Image', 'StatefulWidget', false),\n ];\n\n bool _sortAscending = false;\n var selectData = <_BeanOp>[];\n\n @override\n Widget build(BuildContext context) {\n return DataTable(\n columnSpacing: 20,\n sortColumnIndex: 1,\n sortAscending: _sortAscending,\n columns: [\n DataColumn(\n label: Container(\n child: Checkbox(\n value: selectData.length == data.length,\n onChanged: _onSelectAll,\n ),\n ),\n ),\n DataColumn(label: Text('id'), numeric: false, onSort: _onSortId),\n DataColumn(label: Text('名称')),\n DataColumn(label: Text('类型')),\n ],\n rows: data\n .map((e) => DataRow(selected: false, cells: [\n DataCell(Checkbox(\n value: e.select,\n onChanged: (v) => _onSelectOne(v, e),\n )),\n DataCell(Text('${e.id}')),\n DataCell(Text('${e.name}'),\n showEditIcon: true, onTap: () {}),\n DataCell(Text('${e.type}')),\n ]))\n .toList());\n }\n\n _onSortId(int index, bool ascending) {\n setState(() {\n _sortAscending = ascending;\n data.sort(\n (a, b) => ascending ? a.id.compareTo(b.id) : b.id.compareTo(a.id));\n });\n }\n\n _onSelectOne(bool selected, _BeanOp e) {\n setState(() {\n if (selected) {\n //选中\n selectData.add(e);\n } else {\n selectData.remove(e);\n }\n e.select = selected;\n print(selectData);\n });\n }\n\n _onSelectAll(bool select) {\n setState(() {\n if (select) {\n data.forEach((e) => e.select = true);\n selectData = data.map((e) => e).toList();\n } else {\n data.forEach((e) => e.select = false);\n selectData = [];\n }\n });\n }\n}"},{"id":null,"widgetId":102,"name":"DataTable基本使用","priority":1,"subtitle":" \n【columns】 : 列 【List】\n【rows】 : 行 【List】","code":"import 'package:flutter/material.dart';\nclass _Bean {\n final int id;\n final String name;\n final String type;\n\n _Bean(this.id, this.name, this.type);\n}\n\nclass CustomDataTable extends StatelessWidget {\n final data = [\n _Bean(101, 'DataTable', 'StatelessWidget'),\n _Bean(44, 'RangeSlider', 'StatefulWidget'),\n _Bean(2, 'Text', 'StatelessWidget'),\n _Bean(1, 'Image', 'StatefulWidget'),\n ];\n\n final columns = ['id', '名称', '类型'];\n\n @override\n Widget build(BuildContext context) {\n return DataTable(\n columns: columns.map((e) => DataColumn(label: Text(e))).toList(),\n rows: data\n .map((e) => DataRow(cells: [\n DataCell(Text('${e.id}')),\n DataCell(Text('${e.name}')),\n DataCell(Text('${e.type}')),\n ]))\n .toList());\n }\n}"},{"id":null,"widgetId":4,"name":"样式用于显示文字","priority":2,"subtitle":"【style】 : 样式-3种枚举 【FlutterLogoStyle】\n【textColor】: 文字颜色 【Color】","code":"import 'package:flutter/material.dart';\nclass FlutterLogoWithText extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var data = {\n FlutterLogoStyle.horizontal:Colors.blue,\n FlutterLogoStyle.markOnly:Colors.red,\n FlutterLogoStyle.stacked:Colors.green,\n };\n\n return Wrap(\n spacing: 20,\n children: data.keys.map((e) => FlutterLogo(\n size: 80,\n style: e,\n textColor: data[e],\n ))\n .toList(),\n );\n }\n}"},{"id":null,"widgetId":4,"name":"用于显示一个FlutterLogo","priority":1,"subtitle":"【size】 : 大小 【double】\n【colors】: 颜色 【MaterialColor】","code":"import 'package:flutter/material.dart';\nclass CustomFlutterLogo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n\n var data = {\n Colors.blue:50.0,\n Colors.red:60.0,\n Colors.green:70.0,\n Colors.yellow:80.0,\n };\n return Wrap(\n children: data.keys\n .map((e) => FlutterLogo(\n size: data[e],\n colors: e,\n ))\n .toList(),\n );\n }\n}"},{"id":null,"widgetId":193,"name":"AboutListTile基本使用","priority":1,"subtitle":" \n【icon】 : 左图标 【Widget】\n【applicationIcon】 : 左上图标 【Widget】\n【applicationVersion】 : 版本号 【String】\n【applicationName】 : 应用名 【String】\n【applicationLegalese】 : 应用律术 【String】\n【aboutBoxChildren】 : 弹框内容组件 【List】","code":"import 'package:flutter/material.dart';\nclass AboutListTileDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return AboutListTile(\n icon: Icon(Icons.info),\n applicationIcon: FlutterLogo(),\n applicationName: 'Flutter Unit',\n applicationVersion: 'v0.0.1',\n applicationLegalese: 'Copyright© 2018-2020 张风捷特烈',\n aboutBoxChildren: [\n Padding(\n padding: const EdgeInsets.all(10.0),\n child: Text(\n ' FlutterUnit是【张风捷特烈】的开源项目,'\n '收录Flutter的200+组件,并附加详细介绍以及操作交互,'\n '希望帮助广大编程爱好者入门Flutter。'\n '更多知识可以关注掘金账号、公众号【编程之王】。',\n style: TextStyle(color: Color(0xff999999), fontSize: 16),\n textAlign: TextAlign.justify,\n ),\n ),\n ],\n );\n }\n}"},{"id":null,"widgetId":5,"name":"用于显示一个角标","priority":1,"subtitle":"【message】 : 显示的文字信息 【String】\n【location】 : 位置*4 【BannerLocation】\n【color】: 角标颜色 【Color】\n【child】: 孩子 【Widget】\n【textStyle】: 文字样式 【TextStyle】","code":"import 'package:flutter/material.dart';\nclass CustomBanner extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var data = {\n BannerLocation.topStart: Colors.red,\n BannerLocation.topEnd: Colors.blue,\n BannerLocation.bottomStart: Colors.green,\n BannerLocation.bottomEnd: Colors.yellow,\n };\n\n return Wrap(\n spacing: 10,\n runSpacing: 10,\n children: data.keys.map((e) =>\n Container(\n color: Color(0xffD8F5FF),\n width: 150,\n height: 150 * 0.618,\n child: Banner(\n message: \"Flutter 1.12发布\",\n location: e,\n color: data[e],\n child: Padding(\n padding: EdgeInsets.all(20),\n child: FlutterLogo(colors: Colors.blue,\n style: FlutterLogoStyle.horizontal,)),\n ),\n )).toList());\n }\n}\n"},{"id":null,"widgetId":127,"name":"AlertDialog基本使用","priority":1,"subtitle":" \n【title】 : 顶部组件 【Widget】\n【content】 : 内容组件 【Widget】\n【titleTextStyle】 : 顶部文字样式 【TextStyle】\n【contentTextStyle】 : 内容文字样式 【TextStyle】\n【titlePadding】 : 顶部内边距 【EdgeInsetsGeometry】\n【contentPadding】 : 内容内边距 【EdgeInsetsGeometry】\n【actions】 : 右下角组件列表 【List】\n【backgroundColor】 : 右下角组件列表 【背景色】\n【elevation】 : 右下角组件列表 【背景色】\n【shape】 : 影深 【double】","code":"import 'package:flutter/material.dart';\nclass CustomAlertDialog extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildRaisedButton(context),\n _buildAlertDialog(),\n ],\n );\n }\n\n Widget _buildRaisedButton(BuildContext context) => RaisedButton(\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n color: Colors.blue,\n onPressed: () {\n showDialog(context: context, builder: (ctx) => _buildAlertDialog());\n },\n child: Text(\n 'Just Show It !',\n style: TextStyle(color: Colors.white),\n ),\n );\n\n Widget _buildAlertDialog() {\n return AlertDialog(\n title: _buildTitle(),\n titleTextStyle: TextStyle(fontSize: 20, color: Colors.black),\n titlePadding: EdgeInsets.only(\n top: 5,\n left: 20,\n ),\n contentPadding: EdgeInsets.symmetric(horizontal: 5),\n backgroundColor: Colors.white,\n content: _buildContent(),\n actions: [\n Icon(Icons.android, color: Colors.blue,),\n Icon(Icons.add, color: Colors.blue,),\n Icon(Icons.g_translate, color: Colors.blue,),\n Icon(Icons.games, color: Colors.blue,),\n ],\n elevation: 4,\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n );\n }\n\n Widget _buildTitle() {\n return Row(\n //标题\n children: [\n Image.asset(\n \"assets/images/icon_head.png\",\n width: 30,\n height: 30,\n ),\n SizedBox(width: 10,),\n Expanded(\n child: Text(\n \"关于\",\n style: TextStyle(fontSize: 18),\n )),\n CloseButton()\n ],\n );\n }\n\n Widget _buildContent() {\n return Column(\n mainAxisSize: MainAxisSize.min,\n children: [\n Padding(\n padding: const EdgeInsets.all(10.0),\n child: Text(\n ' FlutterUnit是【张风捷特烈】的开源项目,'\n '收录Flutter的200+组件,并附加详细介绍以及操作交互,'\n '希望帮助广大编程爱好者入门Flutter。'\n '更多知识可以关注掘金账号、公众号【编程之王】。',\n style: TextStyle(color: Color(0xff999999), fontSize: 16),\n textAlign: TextAlign.justify,\n ),\n ),\n ],\n );\n }\n}"},{"id":null,"widgetId":71,"name":"Offstage基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【offstage】 : 是否消失 【bool】","code":"import 'package:flutter/material.dart';\nclass CustomOffstage extends StatefulWidget {\n @override\n _CustomOffstageState createState() => _CustomOffstageState();\n}\n\nclass _CustomOffstageState extends State {\n bool _off = false;\n\n @override\n Widget build(BuildContext context) {\n var radBox = Container(\n height: 50,\n width: 60,\n color: Colors.red,\n child: Switch(\n value: _off,\n onChanged: (v) => setState(() => _off = v)),\n );\n\n return Container(\n width: 250,\n height: 200,\n child: Row(\n children: [radBox, _buildOffStage(), radBox],\n ),\n );\n }\n\n Widget _buildOffStage() => Offstage(\n offstage: _off,\n child: Container(\n alignment: Alignment.center,\n height: 100,\n width: 100,\n color: Colors.blue,\n child: Text(\n \"Offstage\",\n style: TextStyle(fontSize: 20),\n ),\n ));\n}\n"},{"id":null,"widgetId":67,"name":"ClipRect基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【clipBehavior】 : 裁剪行为 【Clip】\n【clipper】 : 裁剪器 【CustomClipper】","code":"import 'package:flutter/material.dart';\nclass CustomClipRect extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ClipRect(\n child: SizedBox(\n height: 100,\n width: 100,\n child: Image.asset(\n \"assets/images/wy_300x200.jpg\",\n fit: BoxFit.cover,),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":279,"name":"返回按钮基本使用","priority":1,"subtitle":"【clipper】 : 裁剪器 【CustomClipper】\n【clipBehavior】 : 裁剪行为 【Clip】\n【child】 : 子组件 【Widget】\n【elevation】 : 阴影深 【double】\n【shadowColor】 : 阴影颜色 【Color】\n【color】: 颜色 【Color】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass PhysicalShapeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 200,\n height: 200,\n child: PhysicalShape(\n shadowColor: Colors.orange,\n elevation: 3,\n child: Image.asset(\n 'assets/images/caver.jpeg',\n fit: BoxFit.cover,\n ),\n clipBehavior: Clip.hardEdge,\n clipper: ShapeBorderClipper(\n shape: CircleBorder(side: BorderSide.none),\n ),\n color: Colors.deepPurpleAccent),\n );\n }\n}\n"},{"id":null,"widgetId":72,"name":"RotatedBox基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【quarterTurns】 : 旋转多少个90° 【int】","code":"import 'package:flutter/material.dart';\nclass CustomRotatedBox extends StatefulWidget {\n @override\n _CustomRotatedBoxState createState() => _CustomRotatedBoxState();\n}\n\nclass _CustomRotatedBoxState extends State {\n int _quarterTurns = 0;\n\n @override\n Widget build(BuildContext context) {\n return RotatedBox(\n quarterTurns: _quarterTurns,\n child: GestureDetector(\n onTap: () => setState(() => _quarterTurns++),\n child: Icon(\n Icons.android,\n size: 60,\n color: Colors.blue,\n )),\n );\n }\n}\n"},{"id":null,"widgetId":79,"name":"LimitedBox基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【maxHeight】 : 最大高 【double】\n【maxWidth】 : 最大宽 【double】","code":"import 'package:flutter/material.dart';\nclass CustomLimitedBox extends StatefulWidget {\n @override\n _CustomLimitedBoxState createState() => _CustomLimitedBoxState();\n}\n\nclass _CustomLimitedBoxState extends State {\n var _text = '';\n\n @override\n Widget build(BuildContext context) {\n var child = Container(\n alignment: Alignment.center,\n color: Colors.cyanAccent,\n width: 50,\n height: 50,\n child: Text(\"Static\"),\n );\n\n var box = LimitedBox(\n maxHeight: 60,\n maxWidth: 100,\n child: Container(color: Colors.orange, child: Text(_text)),\n );\n return Column(\n children: [\n Container(\n color: Colors.grey.withAlpha(22),\n width: 300,\n height: 100,\n child: Row(\n children: [child, UnconstrainedBox(child: box), child],\n ),\n ),\n _buildInput()\n ],\n );\n }\n\n Widget _buildInput() {\n return Padding(\n padding: const EdgeInsets.all(18.0),\n child: TextField(\n decoration: InputDecoration(\n border: OutlineInputBorder(),\n hintText: '请输入',\n ),\n onChanged: (v) {\n setState(() {\n _text = v;\n });\n },\n ),\n );\n }\n}\n"},{"id":null,"widgetId":83,"name":"OverflowBox基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【minWidth】 : 最小宽 【double】\n【minHeight】 : 最小高 【double】\n【maxHeight】 : 最大高 【double】\n【maxWidth】 : 最大宽 【double】\n【alignment】 : 对齐方式 【AlignmentGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomOverflowBox extends StatefulWidget {\n @override\n _CustomOverflowBoxState createState() => _CustomOverflowBoxState();\n}\n\nclass _CustomOverflowBoxState extends State {\n var _text = '';\n\n @override\n Widget build(BuildContext context) {\n var box = OverflowBox(\n alignment: Alignment.center,\n minHeight: 50,\n minWidth: 50,\n maxWidth: 200,\n maxHeight: 120,\n child: Container(\n color: Colors.orange,\n child: Text(_text),\n ),\n// child: Text(\"张风\"),\n );\n return Column(\n children: [\n Container(\n color: Colors.grey.withAlpha(33),\n width: 100,\n height: 100,\n child: box),\n _buildInput()\n ],\n );\n }\n\n Widget _buildInput() {\n return Padding(\n padding: const EdgeInsets.all(18.0),\n child: TextField(\n decoration: InputDecoration(\n border: OutlineInputBorder(),\n hintText: '请输入',\n ),\n onChanged: (v) {\n setState(() {\n _text = v;\n });\n },\n ),\n );\n }\n}\n"},{"id":null,"widgetId":166,"name":"CustomPaint绘线贝塞尔曲线","priority":2,"subtitle":" \n Flutter也支持贝塞尔曲线等复杂绘制。","code":"import 'dart:ui';\nimport 'package:flutter/material.dart';\nclass PlayBezier3Page extends StatefulWidget {\n @override\n _PlayBezier3PageState createState() => _PlayBezier3PageState();\n}\n\nclass _PlayBezier3PageState extends State {\n List _pos = [];\n int selectPos;\n\n @override\n void initState() {\n _initPoints();\n super.initState();\n }\n\n void _initPoints() {\n _pos = List();\n _pos.add(Offset(0, 0));\n _pos.add(Offset(60, -60));\n _pos.add(Offset(-90, -90));\n _pos.add(Offset(-120, -40));\n }\n\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n width: MediaQuery.of(context).size.width,\n child: RepaintBoundary(\n child: CustomPaint(\n painter: BezierPainter(pos: _pos, selectPos: selectPos),\n ),\n ),\n\n );\n }\n}\n\nclass BezierPainter extends CustomPainter {\n Paint _gridPaint;\n Path _gridPath;\n\n Paint _mainPaint;\n Path _mainPath;\n int selectPos;\n Paint _helpPaint;\n\n List pos;\n\n BezierPainter({this.pos, this.selectPos}) {\n _gridPaint = Paint()..style = PaintingStyle.stroke;\n _gridPath = Path();\n\n _mainPaint = Paint()\n ..color = Colors.orange\n ..style = PaintingStyle.stroke\n ..strokeWidth = 2;\n _mainPath = Path();\n\n _helpPaint = Paint()\n ..color = Colors.purple\n ..style = PaintingStyle.stroke\n ..strokeWidth = 2\n ..strokeCap = StrokeCap.round;\n }\n\n @override\n void paint(Canvas canvas, Size size) {\n canvas.clipRect(Offset.zero & size);\n canvas.translate(size.width / 2, size.height / 2);\n _drawGrid(canvas, size); //绘制格线\n _drawAxis(canvas, size); //绘制轴线\n\n _mainPath.moveTo(pos[0].dx, pos[0].dy);\n _mainPath.cubicTo(pos[1].dx, pos[1].dy, pos[2].dx, pos[2].dy, pos[3].dx, pos[3].dy);\n canvas.drawPath(_mainPath, _mainPaint);\n _drawHelp(canvas);\n _drawSelectPos(canvas);\n\n }\n\n @override\n bool shouldRepaint(CustomPainter oldDelegate) => false;\n\n void _drawGrid(Canvas canvas, Size size) {\n _gridPaint\n ..color = Colors.grey\n ..strokeWidth = 0.5;\n _gridPath = _buildGridPath(_gridPath, size);\n canvas.drawPath(_buildGridPath(_gridPath, size), _gridPaint);\n\n canvas.save();\n canvas.scale(1, -1); //沿x轴镜像\n canvas.drawPath(_gridPath, _gridPaint);\n canvas.restore();\n\n canvas.save();\n canvas.scale(-1, 1); //沿y轴镜像\n canvas.drawPath(_gridPath, _gridPaint);\n canvas.restore();\n\n canvas.save();\n canvas.scale(-1, -1); //沿原点镜像\n canvas.drawPath(_gridPath, _gridPaint);\n canvas.restore();\n }\n\n void _drawAxis(Canvas canvas, Size size) {\n canvas.drawPoints(\n PointMode.lines,\n [\n Offset(-size.width / 2, 0),\n Offset(size.width / 2, 0),\n Offset(0, -size.height / 2),\n Offset(0, size.height / 2),\n Offset(0, size.height / 2),\n Offset(0 - 7.0, size.height / 2 - 10),\n Offset(0, size.height / 2),\n Offset(0 + 7.0, size.height / 2 - 10),\n Offset(size.width / 2, 0),\n Offset(size.width / 2 - 10, 7),\n Offset(size.width / 2, 0),\n Offset(size.width / 2 - 10, -7),\n ],\n _gridPaint\n ..color = Colors.blue\n ..strokeWidth = 1.5);\n }\n\n Path _buildGridPath(Path path, Size size, {step = 20.0}) {\n for (int i = 0; i < size.height / 2 / step; i++) {\n path.moveTo(0, step * i);\n path.relativeLineTo(size.width / 2, 0);\n }\n for (int i = 0; i < size.width / 2 / step; i++) {\n path.moveTo(step * i, 0);\n path.relativeLineTo(\n 0,\n size.height / 2,\n );\n }\n return path;\n }\n\n void _drawHelp(Canvas canvas) {\n canvas.drawPoints(PointMode.lines, pos, _helpPaint..strokeWidth = 1);\n canvas.drawPoints(PointMode.points, pos, _helpPaint..strokeWidth = 8);\n }\n\n void _drawSelectPos(Canvas canvas) {\n if (selectPos == null) return;\n canvas.drawCircle(\n pos[selectPos],\n 10,\n _helpPaint\n ..color = Colors.green\n ..strokeWidth = 2);\n }\n}"},{"id":null,"widgetId":166,"name":"CustomPaint绘线图形","priority":1,"subtitle":" \n【painter】 : 绘画器 【CustomPainter】","code":"import 'package:flutter/material.dart';\nclass ClockPage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: 100,\n child:RepaintBoundary(\n child: CustomPaint(//使用CustomPaint盛放画布\n painter: ClockPainter(),\n ),\n ),\n )\n ;\n }\n}\n\nclass ClockPainter extends CustomPainter {\n Paint _paint;\n var _radius = 3.0; //小球半径\n Path _path = Path(); //画笔对象\n ClockPainter () {\n _paint = Paint()..color= Color(0xff45d0fd)..isAntiAlias=true;\n _path.addOval(Rect.fromCircle(radius: _radius, center: Offset(0, 0))); //小球路径\n }\n\n @override\n void paint(Canvas canvas, Size size) {\n print(size);\n canvas.clipRect(Offset.zero & size);\n canvas.translate(size.width/2-65*2, 0);\n renderDigit(1, canvas);//渲染数字\n canvas.translate(65, 0);//平移画布\n renderDigit(9, canvas);\n canvas.translate(65, 0); renderDigit(9, canvas);\n canvas.translate(65, 0); renderDigit(4, canvas);\n }\n //渲染数字 num :要显示的数字 canvas :画布\n void renderDigit(int num, Canvas canvas) {\n if (num > 10) { return; }\n for (int i = 0; i < digit[num].length; i++) {\n for (int j = 0; j < digit[num][j].length; j++) {\n if (digit[num][i][j] == 1) {\n canvas.save();\n double rX = j * 2 * (_radius + 1) + (_radius + 1); //第(i,j)个点圆心横坐标\n double rY = i * 2 * (_radius + 1) + (_radius + 1); //第(i,j)个点圆心纵坐标\n canvas.translate(rX, rY);\n canvas.drawPath(_path, _paint);\n canvas.restore();\n }\n }\n }\n }\n @override\n bool shouldRepaint(CustomPainter oldDelegate)=> false;\n}\n\nconst digit = [\n [\n [0, 0, 1, 1, 1, 0, 0],\n [0, 1, 1, 0, 1, 1, 0],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [0, 1, 1, 0, 1, 1, 0],\n [0, 0, 1, 1, 1, 0, 0]\n ], //0\n\n [\n [0, 0, 0, 1, 1, 0, 0],\n [0, 1, 1, 1, 1, 0, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [1, 1, 1, 1, 1, 1, 1]\n ], //1\n [\n [0, 1, 1, 1, 1, 1, 0],\n [1, 1, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 0, 1, 1, 0, 0, 0],\n [0, 1, 1, 0, 0, 0, 0],\n [1, 1, 0, 0, 0, 0, 0],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 1, 1, 1, 1, 1]\n ], //2\n [\n [1, 1, 1, 1, 1, 1, 1],\n [0, 0, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 0, 1, 1, 1, 0, 0],\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [0, 1, 1, 1, 1, 1, 0]\n ], //3\n\n [\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 0, 1, 1, 1, 0],\n [0, 0, 1, 1, 1, 1, 0],\n [0, 1, 1, 0, 1, 1, 0],\n [1, 1, 0, 0, 1, 1, 0],\n [1, 1, 1, 1, 1, 1, 1],\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 0, 1, 1, 1, 1]\n ], //4\n [\n [1, 1, 1, 1, 1, 1, 1],\n [1, 1, 0, 0, 0, 0, 0],\n [1, 1, 0, 0, 0, 0, 0],\n [1, 1, 1, 1, 1, 1, 0],\n [0, 0, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [0, 1, 1, 1, 1, 1, 0]\n ], //5\n [\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 1, 1, 0, 0, 0],\n [0, 1, 1, 0, 0, 0, 0],\n [1, 1, 0, 0, 0, 0, 0],\n [1, 1, 0, 1, 1, 1, 0],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [0, 1, 1, 1, 1, 1, 0]\n ], //6\n [\n [1, 1, 1, 1, 1, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 0, 1, 1, 0, 0, 0],\n [0, 0, 1, 1, 0, 0, 0],\n [0, 0, 1, 1, 0, 0, 0],\n [0, 0, 1, 1, 0, 0, 0]\n ], //7\n [\n [0, 1, 1, 1, 1, 1, 0],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [0, 1, 1, 1, 1, 1, 0],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [0, 1, 1, 1, 1, 1, 0]\n ], //8\n [\n [0, 1, 1, 1, 1, 1, 0],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 1, 1],\n [0, 1, 1, 1, 0, 1, 1],\n [0, 0, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 1, 1, 0],\n [0, 0, 0, 1, 1, 0, 0],\n [0, 1, 1, 0, 0, 0, 0]\n ], //9\n [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [0, 1, 1, 0],\n [0, 1, 1, 0],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [0, 1, 1, 0],\n [0, 1, 1, 0],\n [0, 0, 0, 0],\n [0, 0, 0, 0]\n ] //:\n];\n"},{"id":null,"widgetId":292,"name":"IgnorePointer基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【ignoring】 : 是否忽视事件 【bool】\n如下,Switch选中时ignoring为true,按钮事件将被锁定,无法点击。","code":"import 'package:flutter/material.dart';\nclass CustomIgnorePointer extends StatefulWidget {\n @override\n _CustomIgnorePointerState createState() => _CustomIgnorePointerState();\n}\n\nclass _CustomIgnorePointerState extends State {\n bool _ignore = false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Wrap(\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n GestureDetector(\n onTap: (){\n print('IgnorePointer');\n },\n child: IgnorePointer(\n ignoring: _ignore,\n child: _buildButton(),\n ),\n ),\n _buildSwitch(),\n Text(!_ignore ? '允许点击' : '点击已锁定')\n ],\n ),\n );\n }\n\n Widget _buildButton() => RaisedButton(\n color: Theme.of(context).primaryColor,\n child: Text(\n 'To About',\n style: TextStyle(color: Colors.white),\n ),\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'));\n\n _buildSwitch() => Switch(\n value: _ignore,\n onChanged: (v) {\n setState(() {\n _ignore = v;\n });\n });\n}\n"},{"id":null,"widgetId":287,"name":"LayoutBuilder基本认识","priority":1,"subtitle":" \n【builder】 : 布局构造器 【LayoutWidgetBuilder】","code":"import 'package:flutter/material.dart';\nclass CustomLayoutBuilder extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n print('CustomLayoutBuild');\n return Container(\n alignment: Alignment.center,\n height: 80,\n width: 150,\n color: Colors.green,\n child: LayoutBuilder(\n builder: (_, zone) {\n return Text(\n '父容器宽:${zone.maxWidth}\\n'\n '父容器高:${zone.maxHeight}',\n style: TextStyle(color: Colors.white, fontSize: 16),\n );\n },\n ),\n );\n }\n}\n"},{"id":null,"widgetId":287,"name":"LayoutBuilder的展开使用","priority":3,"subtitle":" \n使用TextPainter来检测文字的行数,实现展开或收起功能。","code":"import 'package:flutter/material.dart';\nclass SimpleExpandableText extends StatefulWidget {\n\n @override\n createState() => _SimpleExpandableTextState();\n}\n\nclass _SimpleExpandableTextState extends State {\n\n final text = '桃树、杏树、梨树,你不让我,我不让你,都开满了花赶趟儿。'\n '红的像火,粉的像霞,白的像雪。'\n '花里带着甜味儿;闭了眼,树上仿佛已经满是桃儿、杏儿、梨儿。'\n '花下成千成百的蜜蜂嗡嗡地闹着,大小的蝴蝶飞来飞去。'\n '野花遍地是:杂样儿,有名字的,没名字的,散在草丛里,像眼睛,像星星,还眨呀眨的。';\n\n bool expand = false;\n int maxLines =3;\n\n\n final style = TextStyle(fontSize: 15, color: Colors.grey, shadows: [\n Shadow(\n color: Colors.white, offset: Offset(1,1)\n )\n ]);\n\n @override\n build(context) => Container(\n decoration: BoxDecoration(\n color: Colors.cyanAccent.withAlpha(8),\n borderRadius: BorderRadiusDirectional.all(Radius.circular(20))),\n padding: EdgeInsets.all(15),\n child: LayoutBuilder(builder: (context, size) {\n\n final painter = TextPainter(\n text: TextSpan(text: text, style: style),\n maxLines: maxLines,\n textDirection: TextDirection.ltr,\n );\n painter.layout(maxWidth: size.maxWidth);\n if (!painter.didExceedMaxLines)\n return Text(text, style: style);\n\n return Column(\n mainAxisSize: MainAxisSize.min,\n crossAxisAlignment: CrossAxisAlignment.start,\n children: [\n Text(text, maxLines: expand ? null : 3, style: style),\n GestureDetector(\n onTap: () => setState(() {\n expand = !expand;\n }),\n child: Text(\n expand ? '<< 收起' : '展开 >>',\n style: TextStyle(color: Colors.blue),\n ),\n ),\n ],\n );\n }),\n );\n}\n\n"},{"id":null,"widgetId":287,"name":"LayoutBuilder的适应布局","priority":2,"subtitle":" \n可以根据区域的大小进行组件展示设计。\"\n比如在不同的宽度区域显示不同的布局结构。\"\n毕竟很多地方不容易获取父组件区域,使用LayoutBuilder就会非常爽口。","code":"import 'package:flutter/material.dart';\nclass FitByLayoutBuilder extends StatefulWidget {\n @override\n _FitByLayoutBuilderState createState() => _FitByLayoutBuilderState();\n}\n\nclass _FitByLayoutBuilderState extends State {\n double _width = 100;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Container(\n width: _width,\n child: LayoutBuilder(\n builder: (_, zone) {\n if (zone.maxWidth <= 150) {\n return _buildType1();\n } else {\n return _buildType2(zone);\n }\n },\n ),\n ),\n _buildSlider(),\n ],\n );\n }\n\n Widget _buildSlider() {\n return Slider(\n min: 50,\n max: 300,\n label: \"父宽:${_width.toStringAsFixed(1)}\",\n value: _width,\n onChanged: (v) => setState(() {\n _width = v;\n }));\n }\n\n Widget _buildType1() => Container(\n color: Colors.blue,\n child: Column(\n children: [\n _buildTitle(),\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: _buildContent(),\n ),\n ],\n ),\n );\n\n Widget _buildType2(BoxConstraints zone) => Container(\n height: 100,\n width: zone.maxWidth,\n color: Colors.orange,\n child: Row(\n children: [\n Container(\n margin: EdgeInsets.all(10),\n height: 80,\n width: 30,\n color: Colors.grey,\n ),\n Expanded(child: _buildContent())\n ],\n ),\n );\n\n Widget _buildTitle() => Container(\n margin: EdgeInsets.only(left: 10, right: 10, top: 10),\n color: Colors.grey,\n height: 30,\n );\n\n Widget _buildContent() => Wrap(\n runSpacing: 3,\n children: [\n Container(\n color: Colors.red,\n height: 30,\n ),\n Container(\n color: Colors.yellow,\n height: 30,\n ),\n Container(\n color: Colors.green,\n height: 30,\n ),\n ],\n );\n}\n"},{"id":null,"widgetId":86,"name":"Center基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】","code":"import 'package:flutter/material.dart';\nclass CustomCenter extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(5),\n width: 200,\n height: 100,\n color: Colors.grey.withAlpha(88),\n child: Center(\n child: Container(\n width: 80,\n height: 60,\n color: Colors.cyanAccent,\n )));\n }\n}\n"},{"id":null,"widgetId":68,"name":"ClipRRect基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【borderRadius】 : 边线半径 【BorderRadius】\n【clipBehavior】 : 裁剪行为 【Clip】\n【clipper】 : 裁剪器 【CustomClipper】","code":"import 'package:flutter/material.dart';\nclass CustomClipRRect extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ClipRRect(\n borderRadius: BorderRadius.all(Radius.elliptical(35, 30)),\n child: Image.asset(\n \"assets/images/wy_300x200.jpg\",\n width: 150,\n height: 100,\n ),\n );\n }\n}\n"},{"id":null,"widgetId":76,"name":"SizedBox基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【width】 : 宽 【double】\n【height】 : 高 【double】","code":"import 'package:flutter/material.dart';\nclass CustomSizedBox extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var child = Container(\n alignment: Alignment.center,\n color: Colors.cyanAccent,\n width: 50,\n height: 50,\n child: Text(\"Static\"),\n );\n\n var box = SizedBox(\n width: 80,\n height: 40,\n child: Container(\n color: Colors.orange,\n child: Icon(\n Icons.android,\n color: Colors.white,\n )),\n );\n\n return Container(\n color: Colors.grey.withAlpha(22),\n width: 200,\n height: 100,\n child: Row(\n children: [child, box, child],\n ),\n );\n }\n}\n"},{"id":null,"widgetId":77,"name":"AspectRatio基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【aspectRatio】 : 宽高比例 【double】","code":"import 'package:flutter/material.dart';\nclass CustomAspectRatio extends StatefulWidget {\n @override\n _CustomAspectRatioState createState() => _CustomAspectRatioState();\n}\n\nclass _CustomAspectRatioState extends State {\n var _ratio = 0.75;\n\n @override\n Widget build(BuildContext context) {\n var child = Container(\n alignment: Alignment.center,\n color: Colors.cyanAccent,\n width: 50,\n height: 50,\n child: Text(\"Static\"),\n );\n\n var box = AspectRatio(\n aspectRatio: _ratio,\n child: Container(\n color: Colors.orange,\n child: Icon(\n Icons.android,\n color: Colors.white,\n )),\n );\n\n return Column(\n children: [\n _buildSlider(),\n Container(\n color: Colors.grey.withAlpha(22),\n width: 300,\n height: 100,\n child: Row(\n children: [child, box, child],\n ),\n ),\n ],\n );\n }\n\n Widget _buildSlider() => Slider(\n divisions: 20,\n min: 0.1,\n max: 2.0,\n label: _ratio.toStringAsFixed(2),\n value: _ratio,\n onChanged: (v) => setState(() => _ratio = v));\n}\n"},{"id":null,"widgetId":69,"name":"ClipPath基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【clipBehavior】 : 裁剪行为 【Clip】\n【clipper】 : 裁剪器 【CustomClipper】","code":"import 'dart:math';\nimport 'package:flutter/material.dart';\nclass CustomClipPath extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ClipPath(\n clipper: ShapeBorderClipper(shape: _StarShapeBorder()),\n child: Image.asset(\n \"assets/images/wy_300x200.jpg\",\n width: 150,\n height: 100,\n fit: BoxFit.cover,\n ),\n );\n }\n}\n\nclass _StarShapeBorder extends ShapeBorder {\n final Path _path = Path();\n\n @override\n EdgeInsetsGeometry get dimensions => null;\n\n @override\n Path getInnerPath(Rect rect, {TextDirection textDirection}) {\n return null;\n }\n\n @override\n Path getOuterPath(Rect rect, {TextDirection textDirection}) =>\n nStarPath(20, rect.height / 2, rect.height / 2 * 0.85,\n dx: rect.width / 2, dy: rect.height / 2);\n\n @override\n void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}\n\n Path nStarPath(int num, double R, double r, {dx = 0, dy = 0}) {\n double perRad = 2 * pi / num;\n double radA = perRad / 2 / 2;\n double radB = 2 * pi / (num - 1) / 2 - radA / 2 + radA;\n _path.moveTo(cos(radA) * R + dx, -sin(radA) * R + dy);\n for (int i = 0; i < num; i++) {\n _path.lineTo(\n cos(radA + perRad * i) * R + dx, -sin(radA + perRad * i) * R + dy);\n _path.lineTo(\n cos(radB + perRad * i) * r + dx, -sin(radB + perRad * i) * r + dy);\n }\n _path.close();\n return _path;\n }\n\n @override\n ShapeBorder scale(double t) {\n return null;\n }\n}\n"},{"id":null,"widgetId":75,"name":"Baseline基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【baseline】 : 基线位置 【double】\n【baselineType】 : 基线类型 【TextBaseline】","code":"import 'package:flutter/material.dart';\nclass CustomBaseline extends StatefulWidget {\n @override\n _CustomBaselineState createState() => _CustomBaselineState();\n}\n\nclass _CustomBaselineState extends State {\n double _baseline=20;\n\n @override\n Widget build(BuildContext context) {\n\n var childBox = Text(\n '你好,Flutter',\n style: TextStyle(fontSize: 20,fontFamily: \"Menlo\"),\n );\n\n\n var baseline = Baseline(\n child: childBox,\n baseline: _baseline,\n baselineType: TextBaseline.alphabetic\n );\n\n return Column(\n children: [\n _buildSlider(),\n Container(\n width: 100/0.618,\n height: 100,\n color: Colors.grey.withAlpha(22),\n child: baseline,\n ),\n ],\n );\n }\n\n Widget _buildSlider() => Slider(\n divisions: 20,\n min: 0,\n max: 60,\n label: _baseline.toString(),\n value: _baseline,\n onChanged: (v) => setState(() => _baseline = v));\n}\n"},{"id":null,"widgetId":277,"name":"线性渐变着色","priority":2,"subtitle":" \n通过LinearGradient#createShader创建线性渐变着色器\n着色器相关知识详见【绘制专辑】","code":"import 'package:flutter/material.dart';\nclass LinearShaderMask extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 20,\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n ShaderMask(\n shaderCallback: _buildShader,\n child: Image.asset(\n 'assets/images/icon_head.png',\n height: 70,\n width: 70,\n ),\n ),\n ShaderMask(\n shaderCallback: _buildShader,\n child: Text(\n '张风捷特烈',\n style: TextStyle(fontSize: 40, color: Colors.white),\n ),\n ),\n ShaderMask(\n shaderCallback: _buildShader,\n child: Container(\n height: 100,\n color: Colors.white,\n width: 50,\n ),\n ),\n ],\n );\n }\n\n final colors = [Colors.red, Colors.yellow, Colors.blue];\n\n Shader _buildShader(Rect bounds) => LinearGradient(\n begin: Alignment.centerLeft,\n end: Alignment.centerRight,\n tileMode: TileMode.mirror,\n colors: colors)\n .createShader(bounds);\n}\n"},{"id":null,"widgetId":277,"name":"径向渐变着色","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【shaderCallback】 : 着色器回调 【ShaderCallback】\n【blendMode】 : 混色模式 【BlendMode】\n 通过RadialGradient#createShader创建径向渐变着色器。","code":"import 'package:flutter/material.dart';\nclass RadialShaderMask extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 20,\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n ShaderMask(\n shaderCallback: _buildShader,\n child: Image.asset(\n 'assets/images/icon_head.png',\n height: 70,\n width: 70,\n ),\n ),\n ShaderMask(\n shaderCallback: _buildShader,\n child: Text(\n '张风捷特烈',\n style: TextStyle(fontSize: 40, color: Colors.white),\n ),\n ),\n ShaderMask(\n shaderCallback: _buildShader,\n child: Container(\n height: 100,\n color: Colors.white,\n width: 50,\n ),\n ),\n ],\n );\n }\n\n final colors = [Colors.red, Colors.yellow, Colors.blue];\n\n Shader _buildShader(Rect bounds) => RadialGradient(\n center: Alignment.topLeft,\n radius: 1.0,\n tileMode: TileMode.mirror,\n colors: colors)\n .createShader(bounds);\n}"},{"id":null,"widgetId":88,"name":"ColorFiltered基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【colorFilter】 : 滤色器 【ColorFilter】","code":"import 'package:flutter/material.dart';\nimport '../../../../app/utils/color_utils.dart';\nclass CustomColorFiltered extends StatefulWidget {\n @override\n _CustomColorFilteredState createState() => _CustomColorFilteredState();\n}\n\nclass _CustomColorFilteredState extends State {\n Color _color = Colors.blue.withAlpha(88);\n\n @override\n Widget build(BuildContext context) {\n _color = ColorUtils.randomColor();\n return Column(\n children: [\n Wrap(spacing: 10, runSpacing: 10, children: [\n _buildRandomColor(),\n ...BlendMode.values\n .map((mode) => Column(\n children: [\n _buildChild(mode),\n SizedBox(\n height: 10,\n ),\n Text(\n mode.toString().split('.')[1],\n style: TextStyle(fontSize: 10),\n )\n ],\n ))\n .toList()\n ]),\n ],\n );\n ;\n }\n\n Widget _buildChild(m) => Container(\n width: 58,\n height: 58,\n child: ColorFiltered(\n child: Image(image: AssetImage(\"assets/images/icon_head.png\")),\n colorFilter: ColorFilter.mode(_color, m)),\n );\n\n Widget _buildRandomColor() => GestureDetector(\n onTap: () => setState(() {}),\n child: Container(\n alignment: Alignment.center,\n width: 60,\n height: 60,\n decoration: BoxDecoration(color: _color, shape: BoxShape.circle),\n child: Text('点我'),\n ),\n );\n}\n"},{"id":null,"widgetId":298,"name":"IntrinsicHeight基本使用","priority":1,"subtitle":"【child】 : 子组件 【Widget】\n如示例:左侧高可变动,中间高固定,右侧高取前两者的最高值。","code":"import 'package:flutter/material.dart';\nclass IntrinsicHeightDemo extends StatefulWidget {\n @override\n _IntrinsicHeightDemoState createState() => _IntrinsicHeightDemoState();\n}\n\nclass _IntrinsicHeightDemoState extends State {\n var _height =120.0;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n buildChild(_height),\n SizedBox(height: 10),\n _buildSlider()\n ],\n ),\n );\n }\n\n Widget buildChild(double leftHeight) {\n return IntrinsicHeight(\n child: Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n crossAxisAlignment: CrossAxisAlignment.start,\n children: [\n Container(\n height: leftHeight,\n width: 120,\n color: Colors.yellow,\n alignment: Alignment.center,\n child: Text(\"height:${leftHeight.toStringAsFixed(1)}\"),\n ),\n Container(\n color: Colors.blue,\n width: 150,\n height: 80,\n alignment: Alignment.center,\n child: Text(\"固定高\"),\n ),\n Container(\n color: Colors.red,\n width: 60,\n alignment: Alignment.center,\n child: Text(\"最高\"),\n )\n ],\n ),\n );\n }\n\n Widget _buildSlider() =>Slider(\n value: _height,\n max: 200.0,\n min: 30.0,\n divisions: 17,\n onChanged: (v)=> setState(() => _height= v),\n );\n}\n"},{"id":null,"widgetId":70,"name":"DecoratedBox底线装饰","priority":5,"subtitle":"通过UnderlineTabIndicator对象可指定底线,","code":"import 'package:flutter/material.dart';\nclass UnderlineTabIndicatorDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return DecoratedBox(\n decoration: UnderlineTabIndicator(\n insets: EdgeInsets.symmetric(horizontal: 5, vertical: -5),\n borderSide: BorderSide(color: Colors.orange, width: 2)),\n child: Icon(\n Icons.ac_unit,\n color: Colors.blue,\n size: 40,\n ),\n );\n }\n}"},{"id":null,"widgetId":70,"name":"DecoratedBox基本使用","priority":1,"subtitle":" \n【decoration】 : 装饰对象 【Decoration】\n【position】 : 前景色(左)/后景色(右) 【DecorationPosition】","code":"import 'package:flutter/material.dart';\nclass BoxDecorationDemo extends StatelessWidget {\n final rainbow = const [\n 0xffff0000,\n 0xffFF7F00,\n 0xffFFFF00,\n 0xff00FF00,\n 0xff00FFFF,\n 0xff0000FF,\n 0xff8B00FF\n ];\n\n @override\n Widget build(BuildContext context) {\n return DecoratedBox(\n position: DecorationPosition.background,\n decoration: BoxDecoration(\n gradient: LinearGradient(\n stops: [0.0, 1 / 6, 2 / 6, 3 / 6, 4 / 6, 5 / 6, 1.0],\n colors: rainbow.map((e) => Color(e)).toList()),\n borderRadius: BorderRadius.only(\n topLeft: Radius.circular(20), bottomRight: Radius.circular(20)),\n boxShadow: [\n const BoxShadow(\n color: Colors.orangeAccent,\n offset: Offset(1, 1),\n blurRadius: 10,\n spreadRadius: 1),\n ]),\n child: Icon(\n Icons.android,\n size: 80,\n color: Colors.black.withAlpha(123),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":70,"name":"FlutterLogoDecoration装饰","priority":6,"subtitle":"通过FlutterLogoDecoration对象可指定Flutter图标装饰(并没有什么太大的作用),","code":"import 'package:flutter/material.dart';\nclass FlutterLogoDecorationDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return DecoratedBox(\n decoration: FlutterLogoDecoration(\n darkColor: Colors.orange,\n lightColor: Colors.deepPurpleAccent,\n style: FlutterLogoStyle.stacked),\n child: SizedBox(\n width: 100,\n height: 100,\n ),\n );\n }\n}\n"},{"id":null,"widgetId":70,"name":"DecoratedBox形状装饰","priority":4,"subtitle":"通过ShapeDecoration对象可指定边线形状,","code":"import 'package:flutter/material.dart';\nclass ShapeDecorationDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return DecoratedBox(\n decoration: ShapeDecoration(\n shadows: [\n const BoxShadow(\n color: Colors.orangeAccent,\n offset: Offset(0, 0),\n blurRadius: 2,\n spreadRadius: 1),\n ],\n image: DecorationImage(\n fit: BoxFit.cover,\n image: AssetImage(\n 'assets/images/wy_200x300.jpg',\n )),\n shape: CircleBorder(\n side: BorderSide(width: 1.0, color: Colors.orangeAccent),\n )),\n child: SizedBox(\n height: 100,\n width: 100,\n child: Icon(\n Icons.ac_unit,\n color: Colors.white,\n size: 40,\n ),\n ),\n );\n }\n}"},{"id":null,"widgetId":70,"name":"DecoratedBox形状和图片装饰","priority":2,"subtitle":" \n【shape】 : 形状 【BoxShape】\n【image】 : 背景图片 【DecorationImage】,","code":"import 'package:flutter/material.dart';\nclass ShapeImageDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return DecoratedBox(\n decoration: BoxDecoration(\n shape: BoxShape.circle,\n image: DecorationImage(\n fit: BoxFit.cover,\n image: AssetImage(\n 'assets/images/wy_200x300.jpg',\n ))),\n child: SizedBox(\n height: 80,\n width: 80,\n child: Icon(\n Icons.ac_unit,\n color: Colors.white,\n size: 40,\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":70,"name":"DecoratedBox边线装饰","priority":3,"subtitle":"【border】 : 边线 【BoxBorder】,","code":"import 'package:flutter/material.dart';\nclass BorderDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return DecoratedBox(\n position: DecorationPosition.foreground,\n decoration: BoxDecoration(\n border: Border(\n bottom: BorderSide(color: Colors.orange, width: 2),\n top: BorderSide(color: Colors.orange, width: 2)),\n ),\n child: SizedBox(\n height: 80,\n width: 100,\n child: Image.asset(\n 'assets/images/wy_200x300.jpg',\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n}"},{"id":null,"widgetId":85,"name":"Align其他用法","priority":2,"subtitle":" \n由于Alignment对象可指定在父容器中宽高的分率位置\n可以使用Align实现一些复杂的排布需求,比如按指定的数学方程变化位置","code":"import 'dart:math';\nimport 'package:flutter/material.dart';\nclass Ball extends StatelessWidget {\n Ball({\n Key key,\n this.radius = 15,\n this.color = Colors.blue,\n }) : super(key: key);\n final double radius; //半径\n final Color color; //颜色\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: radius * 2,\n height: radius * 2,\n decoration: BoxDecoration(\n shape: BoxShape.circle,\n color: color,\n ),\n );\n }\n}\n\nclass SinLayout extends StatefulWidget {\n SinLayout({\n Key key,\n }) : super(key: key);\n\n @override\n _SinLayoutState createState() => _SinLayoutState();\n}\n\nclass _SinLayoutState extends State {\n var _x = 0.0; //Alignment坐标系上的x坐标\n\n @override\n Widget build(BuildContext context) {\n var item = Container(\n width: 300,\n height: 120,\n color: Colors.black.withAlpha(10),\n child: Align(\n child: Ball(\n color: Colors.orangeAccent,\n ),\n alignment: Alignment(_x, f(_x * pi)),\n ),\n );\n\n var slider = Slider(\n max: 180,\n min: -180,\n divisions: 360,\n label: \"${_x.toStringAsFixed(2)}π\",\n value: _x * 180,\n onChanged: (v) => setState(() => _x = v / 180));\n return Column(\n mainAxisSize: MainAxisSize.min,\n children: [slider, item],\n );\n }\n\n double f(x) {\n //映射函数 -- 可随意指定\n double y = sin(x);\n return y;\n }\n}\n"},{"id":null,"widgetId":278,"name":"BackdropFilter基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【filter】 : 过滤器 【ImageFilter】\nImageFilter.blur可以实现高斯模糊,指定x,y模糊因子。","code":"import 'dart:ui';\nimport 'package:flutter/material.dart';\nclass CustomBackdropFilter extends StatefulWidget {\n @override\n _CustomBackdropFilterState createState() => _CustomBackdropFilterState();\n}\n\nclass _CustomBackdropFilterState extends State {\n double _sigmaX = 1.2;\n double _sigmaY = 1.2;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Stack(\n children: [\n _buildImage(),\n Positioned.fill(\n child: ClipRect(\n child: BackdropFilter(\n filter: ImageFilter.blur(sigmaX: _sigmaX, sigmaY: _sigmaY),\n child: Container(\n color: Colors.black.withAlpha(0),\n ),\n ),\n ),\n )\n ],\n ),\n _buildSliders()\n ],\n );\n }\n\n Widget _buildImage() {\n return Wrap(\n spacing: 20,\n children: [\n Container(\n height: 150,\n width: 150,\n child: Image.asset(\n 'assets/images/sabar.jpg',\n fit: BoxFit.cover,\n ),\n ),\n Container(\n height: 150,\n width: 150,\n child: Image.asset(\n 'assets/images/wy_200x300.jpg',\n fit: BoxFit.cover,\n ),\n ),\n ],\n );\n }\n\n Widget _buildSliders() => Column(\n children: [\n Slider(\n min: 0,\n max: 4,\n value: _sigmaX,\n divisions: 360,\n label: 'x:' + _sigmaX.toStringAsFixed(1),\n onChanged: (v) {\n setState(() {\n _sigmaX = v;\n });\n }),\n Slider(\n min: 0,\n max: 4,\n value: _sigmaY,\n divisions: 360,\n label: 'beta:' + _sigmaY.toStringAsFixed(1),\n onChanged: (v) {\n setState(() {\n _sigmaY = v;\n });\n })\n ],\n );\n}\n"},{"id":null,"widgetId":85,"name":"Align基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【alignment】 : 对齐方式 【AlignmentGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomAlign extends StatelessWidget {\n final alignments = [\n Alignment.topLeft,\n Alignment.topCenter,\n Alignment.topRight,\n Alignment.centerLeft,\n Alignment.center,\n Alignment.centerRight,\n Alignment.bottomLeft,\n Alignment.bottomCenter,\n Alignment.bottomRight,\n ];\n\n final alignmentsInfo = [\n \"topLeft\",\n \"topCenter\",\n \"topRight\",\n \"centerLeft\",\n \"center\",\n \"centerRight\",\n \"bottomLeft\",\n \"bottomCenter\",\n \"bottomRight\",\n ];\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: alignments\n .toList()\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 100,\n height: 60,\n color: Colors.grey.withAlpha(88),\n child: Align(\n child: Container(\n width: 30,\n height: 30,\n color: Colors.cyanAccent,\n ),\n alignment: mode)),\n Text(alignmentsInfo[alignments.indexOf(mode)])\n ]))\n .toList());\n }\n}"},{"id":null,"widgetId":297,"name":"IntrinsicWidth基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n如示例:上面宽可变动,中间宽固定,下面宽取前两者的最高值。","code":"import 'package:flutter/material.dart';\nclass IntrinsicWidthDemo extends StatefulWidget {\n @override\n _IntrinsicWidthDemoState createState() => _IntrinsicWidthDemoState();\n}\n\nclass _IntrinsicWidthDemoState extends State {\n var _height =120.0;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n buildChild(_height),\n SizedBox(height: 10),\n _buildSlider()\n ],\n ),\n );\n }\n\n Widget buildChild(double leftWidth) {\n return IntrinsicWidth(\n child: Column(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n crossAxisAlignment: CrossAxisAlignment.start,\n children: [\n Container(\n height: 50,\n width: leftWidth,\n color: Colors.yellow,\n alignment: Alignment.center,\n child: Text(\"width:${leftWidth.toStringAsFixed(1)}\"),\n ),\n Container(\n color: Colors.blue,\n width: 150,\n height: 60,\n alignment: Alignment.center,\n child: Text(\"固定宽\"),\n ),\n Container(\n color: Colors.red,\n height: 40,\n alignment: Alignment.center,\n child: Text(\"最宽\"),\n )\n ],\n ),\n );\n }\n\n Widget _buildSlider() =>Slider(\n value: _height,\n max: 200.0,\n min: 80.0,\n divisions: 17,\n onChanged: (v)=> setState(() => _height= v),\n );\n}\n"},{"id":null,"widgetId":82,"name":"FractionallySizedBox基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【widthFactor】 : 宽分率 【double】\n【heightFactor】 : 高分率 【double】\n【alignment】 : 对齐方式 【AlignmentGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomFractionallySizedBox extends StatefulWidget {\n @override\n _CustomFractionallySizedBoxState createState() =>\n _CustomFractionallySizedBoxState();\n}\n\nclass _CustomFractionallySizedBoxState\n extends State {\n var _hf = 0.5;\n var _wf = 0.4;\n\n @override\n Widget build(BuildContext context) {\n var box = FractionallySizedBox(\n widthFactor: _wf,\n heightFactor: _hf,\n alignment: Alignment.center,\n child: Container(color: Colors.orange),\n );\n return Column(\n children: [\n Container(\n color: Colors.grey.withAlpha(22),\n width: 200,\n height: 100,\n child: box),\n _buildSlider()\n ],\n );\n }\n\n Widget _buildSlider() => Column(\n children: [\n Slider(\n divisions: 20,\n min: 0.0,\n max: 2,\n label: '宽分率:' + _wf.toStringAsFixed(1),\n value: _wf,\n onChanged: (v) => setState(() => _wf = v)),\n Slider(\n divisions: 20,\n min: 0.0,\n max: 2,\n label: '高分率:' + _hf.toStringAsFixed(1),\n value: _hf,\n onChanged: (v) => setState(() => _hf = v)),\n ],\n );\n}\n"},{"id":null,"widgetId":73,"name":"Opacity基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【opacity】 : 透明度0~1 【double】","code":"import 'package:flutter/material.dart';\nclass CustomOpacity extends StatefulWidget {\n @override\n _CustomOpacityState createState() => _CustomOpacityState();\n}\n\nclass _CustomOpacityState extends State {\n var _opacity = 0.2;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [_buildSlider(), _buildOpacity()],\n );\n }\n // 创建Opacity\n Widget _buildOpacity() => Opacity(\n opacity: _opacity,\n child: Image.asset(// 图片\n 'assets/images/icon_head.png',\n width: 100,\n ),\n );\n Widget _buildSlider() => Slider(\n divisions: 20,\n label: _opacity.toString(),\n value: _opacity,\n onChanged: (v) => setState(() => _opacity = v));\n}\n"},{"id":null,"widgetId":264,"name":"保存Widget成为图片","priority":2,"subtitle":"通过RenderRepaintBoundary可以获取子组件的Image信息,从而获取字节保存为图片文件。","code":"import 'dart:io';\nimport 'dart:typed_data';\nimport 'dart:ui';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/rendering.dart';\nimport 'package:path_provider/path_provider.dart';\nimport 'dart:ui' as ui;\nimport 'node1_base.dart';\nclass RepaintBoundarySave extends StatelessWidget {\n final GlobalKey _globalKey = GlobalKey();\n\n @override\n Widget build(BuildContext context) {\n return Stack(\n children: [\n RepaintBoundary(\n key: _globalKey,\n child: TempPlayBezier3Page(),\n ),\n Positioned(right: -10, child: _buildButton3(context))\n ],\n );\n }\n\n Widget _buildButton3(context) => MaterialButton(\n child: Icon(\n Icons.save_alt,\n size: 15,\n color: Colors.white,\n ),\n color: Colors.green,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: () async {\n var bits = await _widget2Image(_globalKey);\n var dir = await getApplicationSupportDirectory();\n var file = File(dir.path + \"/save_img.png\");\n var f = await file.writeAsBytes(bits);\n Scaffold.of(context).showSnackBar(SnackBar(\n backgroundColor: Theme.of(context).primaryColor,\n content: Text('保存成功后! 路径为:${f.path}'),\n ));\n });\n\n Future _widget2Image(GlobalKey key) async {\n RenderRepaintBoundary boundary = key.currentContext.findRenderObject();\n //获得 ui.image\n ui.Image img = await boundary.toImage();\n //获取图片字节\n var byteData = await img.toByteData(format: ui.ImageByteFormat.png);\n Uint8List bits = byteData.buffer.asUint8List();\n return bits;\n }\n}\n"},{"id":null,"widgetId":264,"name":"RepaintBoundary基本使用","priority":1,"subtitle":"【child】 : 子组件 【Widget】\n比如上面的绘制视图,即使shouldRepaint为false,在滑动中会也会不断执行paint方法,使用RepaintBoundary可以避免不必要的绘制。","code":"import 'dart:ui';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass RepaintBoundaryDemo extends StatelessWidget{\n @override\n Widget build(BuildContext context) {\n return RepaintBoundary(\n child: TempPlayBezier3Page(),\n );\n }\n}\n\nclass TempPlayBezier3Page extends StatefulWidget {\n @override\n _TempPlayBezier3PageState createState() => _TempPlayBezier3PageState();\n}\n\nclass _TempPlayBezier3PageState extends State {\n List _pos = [];\n int selectPos;\n\n @override\n void initState() {\n _initPoints();\n super.initState();\n }\n\n void _initPoints() {\n _pos = List();\n _pos.add(Offset(0, 0));\n _pos.add(Offset(60, -60));\n _pos.add(Offset(-90, -90));\n _pos.add(Offset(-120, -40));\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n width: MediaQuery.of(context).size.width,\n child: CustomPaint(\n painter: TempBezierPainter(pos: _pos, selectPos: selectPos),\n ),\n );\n }\n}\n\nclass TempBezierPainter extends CustomPainter {\n Paint _gridPaint;\n Path _gridPath;\n\n Paint _mainPaint;\n Path _mainPath;\n int selectPos;\n Paint _helpPaint;\n\n List pos;\n\n TempBezierPainter({this.pos, this.selectPos}) {\n _gridPaint = Paint()..style = PaintingStyle.stroke;\n _gridPath = Path();\n\n _mainPaint = Paint()\n ..color = Colors.orange\n ..style = PaintingStyle.stroke\n ..strokeWidth = 2;\n _mainPath = Path();\n\n _helpPaint = Paint()\n ..color = Colors.purple\n ..style = PaintingStyle.stroke\n ..strokeWidth = 2\n ..strokeCap = StrokeCap.round;\n }\n\n @override\n void paint(Canvas canvas, Size size) {\n print('----------Paint-------');\n canvas.clipRect(Offset.zero & size);\n canvas.translate(size.width / 2, size.height / 2);\n _drawGrid(canvas, size); //绘制格线\n _drawAxis(canvas, size); //绘制轴线\n\n _mainPath.moveTo(pos[0].dx, pos[0].dy);\n _mainPath.cubicTo(\n pos[1].dx, pos[1].dy, pos[2].dx, pos[2].dy, pos[3].dx, pos[3].dy);\n canvas.drawPath(_mainPath, _mainPaint);\n _drawHelp(canvas);\n _drawSelectPos(canvas);\n }\n\n @override\n bool shouldRepaint(CustomPainter oldDelegate) => false;\n\n void _drawGrid(Canvas canvas, Size size) {\n _gridPaint\n ..color = Colors.grey\n ..strokeWidth = 0.5;\n _gridPath = _buildGridPath(_gridPath, size);\n canvas.drawPath(_buildGridPath(_gridPath, size), _gridPaint);\n\n canvas.save();\n canvas.scale(1, -1); //沿x轴镜像\n canvas.drawPath(_gridPath, _gridPaint);\n canvas.restore();\n\n canvas.save();\n canvas.scale(-1, 1); //沿y轴镜像\n canvas.drawPath(_gridPath, _gridPaint);\n canvas.restore();\n\n canvas.save();\n canvas.scale(-1, -1); //沿原点镜像\n canvas.drawPath(_gridPath, _gridPaint);\n canvas.restore();\n }\n\n void _drawAxis(Canvas canvas, Size size) {\n canvas.drawPoints(\n PointMode.lines,\n [\n Offset(-size.width / 2, 0),\n Offset(size.width / 2, 0),\n Offset(0, -size.height / 2),\n Offset(0, size.height / 2),\n Offset(0, size.height / 2),\n Offset(0 - 7.0, size.height / 2 - 10),\n Offset(0, size.height / 2),\n Offset(0 + 7.0, size.height / 2 - 10),\n Offset(size.width / 2, 0),\n Offset(size.width / 2 - 10, 7),\n Offset(size.width / 2, 0),\n Offset(size.width / 2 - 10, -7),\n ],\n _gridPaint\n ..color = Colors.blue\n ..strokeWidth = 1.5);\n }\n\n Path _buildGridPath(Path path, Size size, {step = 20.0}) {\n for (int i = 0; i < size.height / 2 / step; i++) {\n path.moveTo(0, step * i);\n path.relativeLineTo(size.width / 2, 0);\n }\n for (int i = 0; i < size.width / 2 / step; i++) {\n path.moveTo(step * i, 0);\n path.relativeLineTo(\n 0,\n size.height / 2,\n );\n }\n return path;\n }\n\n void _drawHelp(Canvas canvas) {\n canvas.drawPoints(PointMode.lines, pos, _helpPaint..strokeWidth = 1);\n canvas.drawPoints(PointMode.points, pos, _helpPaint..strokeWidth = 8);\n }\n\n void _drawSelectPos(Canvas canvas) {\n if (selectPos == null) return;\n canvas.drawCircle(\n pos[selectPos],\n 10,\n _helpPaint\n ..color = Colors.green\n ..strokeWidth = 2);\n }\n}\n"},{"id":null,"widgetId":81,"name":"UnConstrainedBox基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【constrainedAxis】 : 仍受约束的轴*2 【Axis】\n【alignment】 : 对齐方式 【AlignmentGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomUnConstrainedBox extends StatefulWidget {\n @override\n _CustomUnConstrainedBoxState createState() => _CustomUnConstrainedBoxState();\n}\n\nclass _CustomUnConstrainedBoxState extends State {\n var _value = false;\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 20,\n children: [_buildUnconstrainedBox(), _buildConstrainedAxis()],\n );\n }\n\n Widget _buildUnconstrainedBox() {\n var child = Container(\n color: Colors.cyanAccent,\n width: 60,\n height: 60,\n child: Switch(\n value: _value,\n onChanged: (v) {\n setState(() {\n _value = v;\n });\n },\n ),\n );\n\n return Column(\n children: [\n Container(\n color: Colors.grey.withAlpha(22),\n width: 150,\n height: 100,\n child: _value\n ? UnconstrainedBox(alignment: Alignment.center, child: child)\n : child,\n ),\n Text(_value ? \"已解除约束\" : \"子组件受约束\")\n ],\n );\n }\n\n Widget _buildConstrainedAxis() {\n return Column(\n children: [\n Container(\n color: Colors.grey.withAlpha(22),\n width: 150,\n height: 100,\n child: UnconstrainedBox(\n alignment: Alignment.center,\n constrainedAxis: Axis.vertical,\n child: Container(\n color: Colors.cyanAccent,\n width: 60,\n height: 60,\n )),\n ),\n Text(\"竖直方向仍约束\")\n ],\n );\n }\n}\n"},{"id":null,"widgetId":201,"name":"AnimatedSize基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【duration】 : 动画时长 【Duration】\n【alignment】 : 对齐方式 【AlignmentGeometry】\n【curve】 : 动画曲线 【Duration】\n【vsync】 : vsync 【TickerProvider】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedSize extends StatefulWidget {\n @override\n _CustomAnimatedSizeState createState() => _CustomAnimatedSizeState();\n}\n\nclass _CustomAnimatedSizeState extends State\n with SingleTickerProviderStateMixin {\n final double start = 100;\n final double end = 200;\n\n double _width;\n\n @override\n void initState() {\n _width = start;\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildSwitch(),\n Container(\n color: Colors.grey.withAlpha(22),\n width: 200,\n height: 100,\n alignment: Alignment.center,\n child: AnimatedSize(\n vsync: this,\n duration: Duration(seconds: 1),\n curve: Curves.fastOutSlowIn,\n alignment: Alignment(0, 0),\n child: Container(\n height: 40,\n width: _width,\n alignment: Alignment.center,\n color: Colors.blue,\n child: Text(\n '张风捷特烈',\n style: TextStyle(color: Colors.white),\n ),\n ),\n ),\n ),\n ],\n );\n }\n\n Widget _buildSwitch() => Switch(\n value: _width == end,\n onChanged: (v) {\n setState(() {\n _width = v ? end : start;\n });\n });\n}\n"},{"id":null,"widgetId":89,"name":"FadeTransition基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【opacity】 : 动画 【Animation】","code":"import 'package:flutter/material.dart';\nclass CustomFadeTransition extends StatefulWidget {\n @override\n _CustomFadeTransitionState createState() => _CustomFadeTransitionState();\n}\n\nclass _CustomFadeTransitionState extends State\n with SingleTickerProviderStateMixin {\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 2));\n _ctrl.forward();\n super.initState();\n }\n\n @override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Container(\n color: Colors.grey.withAlpha(22),\n width: 100,\n height: 100,\n child: FadeTransition(\n opacity: CurvedAnimation(parent: _ctrl, curve: Curves.linear),\n child: Icon(Icons.android, color: Colors.green, size: 60),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":285,"name":"CustomSingleChildLayout的偏移使用","priority":2,"subtitle":" \n可以利用代理的偏移能力,对子组件进行偏移定位。","code":"import 'package:flutter/material.dart';\nclass OffSetWidgetDemo extends StatelessWidget {\n final data = [\n {\n 'offset': Offset(20, 20),\n 'direction': Direction.topLeft,\n },\n {\n 'offset': Offset(20, -15),\n 'direction': Direction.topRight,\n },\n {\n 'offset': Offset(-15, 20),\n 'direction': Direction.bottomLeft,\n },\n {\n 'offset': Offset(-15, 20),\n 'direction': Direction.bottomLeft,\n },\n {\n 'offset': Offset(15, 20),\n 'direction': Direction.bottomLeft,\n },\n {\n 'offset': Offset(-15, -15),\n 'direction': Direction.topRight,\n },\n ];\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 20,\n runSpacing: 20,\n children: data\n .map((e) => Container(\n width: 150,\n height: 100,\n alignment: Alignment.topRight,\n color: Colors.grey.withAlpha(11),\n child: OffSetWidget(\n offset: e['offset'],\n direction: e['direction'],\n child: Icon(\n Icons.android,\n size: 30,\n color: Colors.green,\n ),\n )))\n .toList());\n }\n}\n\nclass OffSetWidget extends StatelessWidget {\n final Offset offset;\n final Widget child;\n final Direction direction;\n\n OffSetWidget(\n {this.offset = Offset.zero,\n this.child,\n this.direction = Direction.topLeft});\n\n @override\n Widget build(BuildContext context) {\n return CustomSingleChildLayout(\n delegate: _OffSetDelegate(offset: offset, direction: direction),\n child: child,\n );\n }\n}\n\nenum Direction { topLeft, topRight, bottomLeft, bottomRight }\n\nclass _OffSetDelegate extends SingleChildLayoutDelegate {\n final Offset offset;\n final Direction direction;\n\n _OffSetDelegate(\n {this.offset = Offset.zero, this.direction = Direction.topLeft});\n\n @override\n bool shouldRelayout(_OffSetDelegate oldDelegate) =>\n offset != oldDelegate.offset;\n\n @override\n Offset getPositionForChild(Size size, Size childSize) {\n var w = size.width;\n var h = size.height;\n var wc = childSize.width;\n var hc = childSize.height;\n\n switch (direction) {\n case Direction.topLeft:\n return offset;\n case Direction.topRight:\n return offset.translate(w - wc - offset.dx * 2, 0);\n case Direction.bottomLeft:\n return offset.translate(0, h - hc - offset.dy * 2);\n case Direction.bottomRight:\n return offset.translate(w - wc - offset.dx * 2, h - hc - offset.dy * 2);\n }\n return offset;\n }\n}\n"},{"id":null,"widgetId":285,"name":"CustomSingleChildLayout基本使用","priority":1,"subtitle":" \n【delegate】 : 代理 【SingleChildLayoutDelegate】","code":"import 'package:flutter/material.dart';\nclass CustomSingleChildLayoutDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n print('-------CustomSingleChildLayoutDemo------');\n return Container(\n width: 300,\n height: 200,\n color: Colors.grey.withAlpha(11),\n child: CustomSingleChildLayout(\n delegate: _TolySingleChildLayoutDelegate(),\n child: Container(\n color: Colors.orange,\n ),\n ),\n );\n }\n}\n\nclass _TolySingleChildLayoutDelegate extends SingleChildLayoutDelegate {\n @override\n bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {\n return true;\n }\n\n @override\n Size getSize(BoxConstraints constraints) {\n print('----getSize:----constraints:$constraints----');\n return super.getSize(constraints);\n }\n\n @override\n Offset getPositionForChild(Size size, Size childSize) {\n print('----getPositionForChild: size:$size----childSize:$childSize----');\n return Offset(size.width / 2, 0);\n }\n\n @override\n BoxConstraints getConstraintsForChild(BoxConstraints constraints) {\n print('----getConstraintsForChild:----constraints:$constraints----');\n return BoxConstraints(\n maxWidth: constraints.maxWidth / 2,\n maxHeight: constraints.maxHeight / 2,\n minHeight: constraints.maxHeight / 4,\n minWidth: constraints.maxWidth / 4,\n );\n }\n}"},{"id":null,"widgetId":74,"name":"Padding单独边距边距","priority":2,"subtitle":" \nEdgeInsets.only用来限定相同的四边边距","code":"import 'package:flutter/material.dart';\nclass PaddingOnly extends StatelessWidget {\n\n @override\n Widget build(BuildContext context) {\n return Container(\n color: Colors.grey.withAlpha(22),\n width: 200,\n height: 150,\n child: Padding(\n padding: EdgeInsets.only(top:10,left: 10),\n child: _buildChild(),\n ),\n );\n }\n\n Widget _buildChild() {\n return Container(\n alignment: Alignment.center,\n color: Colors.cyanAccent,\n width: 100,\n height: 100,\n child: Text(\"孩子\"),\n );\n }\n}"},{"id":null,"widgetId":74,"name":"Padding方向边距","priority":3,"subtitle":" \nEdgeInsets.symmetric用来限定水平和竖直方向的边距","code":"import 'package:flutter/material.dart';\nclass PaddingSymmetric extends StatelessWidget {\n\n @override\n Widget build(BuildContext context) {\n return Container(\n color: Colors.grey.withAlpha(22),\n width: 200,\n height: 150,\n child: Padding(\n padding: EdgeInsets.symmetric(vertical: 30,horizontal: 10),\n child: _buildChild(),\n ),\n );\n }\n\n Widget _buildChild() {\n return Container(\n alignment: Alignment.center,\n color: Colors.cyanAccent,\n width: 100,\n height: 100,\n child: Text(\"孩子\"),\n );\n }\n}"},{"id":null,"widgetId":74,"name":"Padding四面等边距","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【padding】 : 内四边距 【EdgeInsetsGeometry】\"\nEdgeInsets.all用来限定相同的四边边距","code":"import 'package:flutter/material.dart';\nclass PaddingAll extends StatelessWidget {\n\n @override\n Widget build(BuildContext context) {\n return Container(\n color: Colors.grey.withAlpha(22),\n width: 200,\n height: 150,\n child: Padding(\n padding: EdgeInsets.all(20),\n child: _buildChild(),\n ),\n );\n }\n\n Widget _buildChild() {\n return Container(\n alignment: Alignment.center,\n color: Colors.cyanAccent,\n width: 100,\n height: 100,\n child: Text(\"孩子\"),\n );\n }\n}"},{"id":null,"widgetId":66,"name":"ClipOval基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【clipBehavior】 : 裁剪行为 【Clip】\n【clipper】 : 裁剪器 【CustomClipper】","code":"import 'package:flutter/material.dart';\nclass CustomClipOval extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 20,\n children: [\n ClipOval(\n\n child: Image.asset(\n \"assets/images/wy_300x200.jpg\",\n width: 150,\n height: 100,\n ),\n ),\n ClipOval(\n child: Image.asset(\n \"assets/images/wy_300x200.jpg\",\n width: 100,\n height: 100,\n fit: BoxFit.cover,\n ),\n ),\n ],\n );\n }\n}"},{"id":null,"widgetId":263,"name":"FractionalTranslation基本使用","priority":1,"subtitle":"【translation】 : 偏移分度值 【Offset】\n【child】: 子组件 【Widget】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass FractionalTranslationDemo extends StatefulWidget {\n @override\n _FractionalTranslationDemoState createState() =>\n _FractionalTranslationDemoState();\n}\n\nclass _FractionalTranslationDemoState extends State {\n var dx = 0.0;\n var dy = 0.0;\n\n @override\n Widget build(BuildContext context) {\n print(dx);\n return Column(\n mainAxisSize: MainAxisSize.min,\n children: [\n Container(\n width: 200,\n height: 100,\n alignment: Alignment.topLeft,\n color: Colors.grey.withAlpha(33),\n child: FractionalTranslation(\n translation: Offset(dx, dy),\n child: Icon(\n Icons.android,\n color: Colors.green,\n ),\n ),\n ),\n _buildSliderX(),\n _buildSliderY()\n ],\n );\n }\n\n Widget _buildSliderX() => Slider(\n min: -2.0,\n max: 10.0,\n value: dx,\n divisions: 100,\n label: 'dx:${dx.toStringAsFixed(1)}',\n onChanged: (v) => setState(() => dx = v),\n );\n\n\n Widget _buildSliderY() => Slider(\n min: -2.0,\n max: 6.0,\n value: dy,\n divisions: 100,\n label: 'dy:${dy.toStringAsFixed(1)}',\n onChanged: (v) => setState(() => dy = v),\n );\n}\n"},{"id":null,"widgetId":80,"name":"BoxConstraints基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【minWidth】 : 最小宽 【double】\n【minHeight】 : 最小高 【double】\n【maxHeight】 : 最大高 【double】\n【maxWidth】 : 最大宽 【double】","code":"import 'package:flutter/material.dart';\nclass CustomConstrainedBox extends StatefulWidget {\n @override\n _CustomConstrainedBoxState createState() => _CustomConstrainedBoxState();\n}\n\nclass _CustomConstrainedBoxState extends State {\n var _text = '';\n\n @override\n Widget build(BuildContext context) {\n var child = Container(\n alignment: Alignment.center,\n color: Colors.cyanAccent,\n width: 40,\n height: 40,\n child: Text(\"Static\"),\n );\n\n var box = ConstrainedBox(\n constraints: BoxConstraints(\n minHeight: 50,\n minWidth: 20,\n maxHeight: 80,\n maxWidth: 150,\n ),\n child: Container(color: Colors.orange, child: Text(_text)),\n );\n return Column(\n children: [\n Container(\n color: Colors.grey.withAlpha(22),\n width: 300,\n height: 100,\n child: Row(\n children: [child, UnconstrainedBox(child: box), child],\n ),\n ),\n _buildInput(),\n ],\n );\n }\n\n Widget _buildInput() {\n return Padding(\n padding: const EdgeInsets.all(18.0),\n child: TextField(\n decoration: InputDecoration(\n border: OutlineInputBorder(),\n hintText: '请输入',\n ),\n onChanged: (v) {\n setState(() {\n _text = v;\n });\n },\n ),\n );\n }\n}\n"},{"id":null,"widgetId":87,"name":"FittedBox基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【fit】 : 适应模式 【BoxFit】\n【alignment】 : 对齐方式 【AlignmentGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomFittedBox extends StatefulWidget {\n @override\n _CustomFittedBoxState createState() => _CustomFittedBoxState();\n}\n\nclass _CustomFittedBoxState extends State {\n double _childW = 20;\n double _childH = 30;\n\n final rainbow = [\n 0xffff0000,\n 0xffFF7F00,\n 0xffFFFF00,\n 0xff00FF00,\n 0xff00FFFF,\n 0xff0000FF,\n 0xff8B00FF\n ];\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Wrap(\n spacing: 10,\n runSpacing: 10,\n children: BoxFit.values\n .map((mode) => Column(\n children: [\n _buildChild(mode),\n SizedBox(\n height: 10,\n ),\n Text(mode.toString().split('.')[1])\n ],\n ))\n .toList()),\n _buildSlider()\n ],\n );\n }\n\n Widget _buildChild(BoxFit m) {\n return Container(\n color: Colors.grey.withAlpha(44),\n width: 80,\n height: 60,\n child: FittedBox(\n fit: m,\n child: Container(\n width: _childW,\n height: _childH,\n decoration: BoxDecoration(\n //添加渐变色\n gradient: LinearGradient(\n stops: [0.0, 1 / 6, 2 / 6, 3 / 6, 4 / 6, 5 / 6, 1.0],\n colors: rainbow.map((e) => Color(e)).toList()),\n ),\n ),\n ),\n );\n }\n\n Widget _buildSlider() => Column(\n children: [\n Slider(\n min: 10,\n max: 150,\n divisions: 100,\n label: '子宽度:' + _childW.toStringAsFixed(1),\n value: _childW,\n onChanged: (v) => setState(() => _childW = v)),\n Slider(\n min: 10,\n max: 150,\n divisions: 100,\n label: '子高度:' + _childH.toStringAsFixed(1),\n value: _childH,\n onChanged: (v) => setState(() => _childH = v)),\n ],\n );\n}\n"},{"id":null,"widgetId":78,"name":"平移变换translationValues","priority":2,"subtitle":" \n平移x由R0C3数控制,入参为数值,表示平移长度\n平移y由R1C3数控制,入参为数值,表示平移长度\n平移z由R2C3数控制,入参为数值,表示平移长度","code":"import 'package:flutter/material.dart';\nimport 'matrix4_shower.dart';\nclass TranslationTransform extends StatefulWidget {\n @override\n _TranslationTransformState createState() => _TranslationTransformState();\n}\n\nclass _TranslationTransformState extends State {\n Matrix4 _m4;\n double _x = 0;\n double _y = 0;\n double _z = 0;\n\n @override\n void initState() {\n _m4 = Matrix4.identity();\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [_buildTransform(), Matrix4Shower(_m4)],\n ),\n _buildSliders()\n ],\n );\n }\n\n Widget _buildTransform() {\n _m4 = Matrix4.translationValues(_x, _y, _z);\n return Transform(\n transform: _m4,\n child: Container(\n color: Colors.cyanAccent,\n width: 100,\n height: 100,\n child: Image.asset(\n 'assets/images/wy_300x200.jpg',\n fit: BoxFit.cover,\n )),\n );\n }\n\n Widget _buildSliders() => Column(\n children: [\n Slider(\n min: -100,\n max: 100,\n value: _x,\n divisions: 360,\n label: 'x:${_x.toStringAsFixed(1)}',\n onChanged: (v) {\n setState(() {\n _x = v;\n });\n }),\n Slider(\n min: -100,\n max: 100,\n value: _y,\n divisions: 360,\n label: 'y:${_y.toStringAsFixed(1)}',\n onChanged: (v) {\n setState(() {\n _y = v;\n });\n }),\n Slider(\n min: -100,\n max: 100,\n value: _z,\n divisions: 360,\n label: 'z:${_z.toStringAsFixed(1)}',\n onChanged: (v) {\n setState(() {\n _z = v;\n });\n })\n ],\n );\n}\n"},{"id":null,"widgetId":78,"name":"斜切变换skew","priority":1,"subtitle":" \n斜切x由R0C1数控制,入参为弧度值,表示斜切角度\n斜切y由R1C0数控制,入参为弧度值,表示斜切角度","code":"import 'dart:math';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'matrix4_shower.dart';\nclass SkewTransform extends StatefulWidget {\n @override\n _SkewTransformState createState() => _SkewTransformState();\n}\n\nclass _SkewTransformState extends State {\n Matrix4 _m4;\n double _alpha = 0;\n double _beta = 0;\n\n @override\n void initState() {\n _m4 = Matrix4.identity();\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [_buildTransform(), Matrix4Shower(_m4)],\n ),\n _buildSliders()\n ],\n );\n }\n\n Widget _buildTransform() {\n _m4 = Matrix4.skew(_alpha, _beta);\n return Transform(\n transform: _m4,\n child: Container(\n color: Colors.cyanAccent,\n width: 100,\n height: 100,\n child: Image.asset(\n 'assets/images/wy_300x200.jpg',\n fit: BoxFit.cover,\n )),\n );\n }\n\n Widget _buildSliders() => Column(\n children: [\n Slider(\n min: -pi,\n max: pi,\n value: _alpha,\n divisions: 360,\n label: 'alpha:' + (_alpha * 180 / pi).toStringAsFixed(1) + \"°\",\n onChanged: (v) {\n setState(() {\n _alpha = v;\n });\n }),\n Slider(\n min: -pi,\n max: pi,\n value: _beta,\n divisions: 360,\n label: 'beta:' + (_beta * 180 / pi).toStringAsFixed(1) + \"°\",\n onChanged: (v) {\n setState(() {\n _beta = v;\n });\n })\n ],\n );\n}"},{"id":null,"widgetId":78,"name":"透视变换rotation","priority":5,"subtitle":" \n由R3C1、R3C2、R3C3控制透视","code":"import 'dart:math';\nimport 'package:flutter/material.dart';\nclass R3C2 extends StatefulWidget {\n @override\n _R3C2State createState() => _R3C2State();\n}\n\nclass _R3C2State extends State {\n Matrix4 _m4;\n double _value = 0;\n double _rad = 0;\n\n @override\n Widget build(BuildContext context) {\n _m4 = Matrix4.identity()\n// ..setEntry(3, 0, _value) // x\n// ..setEntry(3, 1, _value)// y\n ..setEntry(3, 2, _value) // z\n ..rotateY(_rad)\n// ..rotateX(_rad)\n ;\n return Column(\n children: [\n Transform(\n transform: _m4,\n child: Container(\n color: Colors.cyanAccent,\n width: 100,\n height: 100,\n child: Image.asset(\n 'assets/images/wy_300x200.jpg',\n fit: BoxFit.cover,\n )),\n ),\n _buildSliders()\n ],\n );\n }\n\n Widget _buildSliders() => Column(\n children: [\n Slider(\n min: -0.01,\n max: 0.01,\n value: _value,\n divisions: 360,\n label: 'x:${_value.toStringAsFixed(5)}',\n onChanged: (v) {\n setState(() {\n _value = v;\n });\n }),\n Slider(\n min: -pi,\n max: pi,\n value: _rad,\n divisions: 360,\n label: '角度:' + (_rad * 180 / pi).toStringAsFixed(1) + \"°\",\n onChanged: (v) {\n setState(() {\n _rad = v;\n });\n }),\n ],\n );\n}"},{"id":null,"widgetId":78,"name":"旋转变换rotation","priority":4,"subtitle":" \nx旋转由R1C1、R1C2、R2C1、R2C2控制,入参表示弧度\ny旋转由R0C0、R0C2、R2C0、R2C2控制,入参表示弧度\nz旋转由R0C0、R0C1、R1C0、R1C1控制,","code":"import 'dart:math';\nimport 'package:flutter/material.dart';\nimport 'matrix4_shower.dart';\nclass RotateTransform extends StatefulWidget {\n @override\n _RotateTransformState createState() => _RotateTransformState();\n}\n\nclass _RotateTransformState extends State {\n Matrix4 _m4;\n double _x = 0;\n int _rotateFlag = 1;\n\n @override\n void initState() {\n _m4 = Matrix4.identity();\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [_buildTransform(), Matrix4Shower(_m4)],\n ),\n _buildSliders()\n ],\n );\n }\n\n Widget _buildTransform() {\n if (_rotateFlag == 1) {\n _m4 = Matrix4.rotationX(_x);\n } else if (_rotateFlag == 2) {\n _m4 = Matrix4.rotationY(_x);\n } else {\n _m4 = Matrix4.rotationZ(_x);\n }\n\n return Transform(\n transform: _m4,\n child: Container(\n color: Colors.cyanAccent,\n width: 100,\n height: 100,\n child: Image.asset(\n 'assets/images/wy_300x200.jpg',\n fit: BoxFit.cover,\n )),\n );\n }\n\n final Map map = {\n 1: 'rotationX',\n 2: 'rotationY',\n 3: 'rotationZ',\n };\n\n Widget _buildSliders() => Column(\n children: [\n Wrap(\n children: map.keys.map((key) => _buildChild(key)).toList(),\n ),\n Slider(\n min: -pi,\n max: pi,\n value: _x,\n divisions: 360,\n label: 'x:${_x.toStringAsFixed(1)}',\n onChanged: (v) {\n setState(() {\n _x = v;\n });\n }),\n ],\n );\n\n Padding _buildChild(int key) {\n return Padding(\n padding: const EdgeInsets.all(4.0),\n child: FilterChip(\n selectedColor: Colors.orange.withAlpha(55),\n selectedShadowColor: Colors.blue,\n shadowColor: Colors.orangeAccent,\n pressElevation: 5,\n elevation: 3,\n avatar: CircleAvatar(child: Text(key.toString())),\n label: Text(map[key]),\n selected: _rotateFlag == key,\n onSelected: (bool value) {\n print(map[key]);\n setState(() {\n _x = 0;\n if (value) {\n _rotateFlag = key;\n }\n });\n },\n ),\n );\n }\n}\n"},{"id":null,"widgetId":78,"name":"缩放变换diagonal3Values","priority":3,"subtitle":" \n缩放x由R0C0数控制,入参为数值,表示缩放分率\n缩放y由R1C2数控制,入参为数值,表示缩放分率\n缩放z由R2C2数控制,入参为数值,表示缩放分率","code":"import 'package:flutter/material.dart';\nimport 'matrix4_shower.dart';\nclass ScaleTransform extends StatefulWidget {\n @override\n _ScaleTransformState createState() => _ScaleTransformState();\n}\n\nclass _ScaleTransformState extends State {\n Matrix4 _m4;\n double _x = 1.0;\n double _y = 1.0;\n double _z = 1.0;\n\n @override\n void initState() {\n _m4 = Matrix4.identity();\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [_buildTransform(), Matrix4Shower(_m4)],\n ),\n _buildSliders()\n ],\n );\n }\n\n Widget _buildTransform() {\n _m4 = Matrix4.diagonal3Values(_x, _y, _z);\n return Transform(\n transform: _m4,\n child: Container(\n color: Colors.cyanAccent,\n width: 100,\n height: 100,\n child: Image.asset(\n 'assets/images/wy_300x200.jpg',\n fit: BoxFit.cover,\n )),\n );\n }\n\n Widget _buildSliders() => Column(\n children: [\n Slider(\n min: -2,\n max: 2,\n value: _x,\n divisions: 360,\n label: 'x:${_x.toStringAsFixed(1)}',\n onChanged: (v) {\n setState(() {\n _x = v;\n });\n }),\n Slider(\n min: -2,\n max: 2,\n value: _y,\n divisions: 360,\n label: 'y:${_y.toStringAsFixed(1)}',\n onChanged: (v) {\n setState(() {\n _y = v;\n });\n }),\n Slider(\n min: -2,\n max: 2,\n value: _z,\n divisions: 360,\n label: 'z:${_z.toStringAsFixed(1)}',\n onChanged: (v) {\n setState(() {\n _z = v;\n });\n })\n ],\n );\n}"},{"id":null,"widgetId":295,"name":"AbsorbPointer基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【absorbing】 : 是否吸收事件 【bool】\n如下,Switch选中时absorbing为true,按钮事件将被吸收,无法点击。","code":"import 'package:flutter/material.dart';\nclass CustomAbsorbPointer extends StatefulWidget {\n @override\n _CustomAbsorbPointerState createState() => _CustomAbsorbPointerState();\n}\n\nclass _CustomAbsorbPointerState extends State {\n bool _absorbing = false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Wrap(\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n GestureDetector(\n onTap: (){\n print('AbsorbPointer');\n },\n child: AbsorbPointer(\n absorbing: _absorbing,\n child: _buildButton(),\n ),\n ),\n _buildSwitch(),\n Text(!_absorbing ? '允许点击' : '事件已被吸收')\n ],\n ),\n );\n }\n\n Widget _buildButton() => RaisedButton(\n color: Theme.of(context).primaryColor,\n child: Text(\n 'To About',\n style: TextStyle(color: Colors.white),\n ),\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'));\n\n _buildSwitch() => Switch(\n value: _absorbing,\n onChanged: (v) {\n setState(() {\n _absorbing = v;\n });\n });\n}\n"},{"id":null,"widgetId":84,"name":"SizedOverflowBox基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【size】 : 尺寸偏移 【Size】\n【alignment】 : 对齐方式 【AlignmentGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomSizedOverflowBox extends StatefulWidget {\n\n @override\n _CustomSizedOverflowBoxState createState() => _CustomSizedOverflowBoxState();\n}\n\nclass _CustomSizedOverflowBoxState extends State {\n double _x = 50;\n double _y = 44;\n\n @override\n Widget build(BuildContext context) {\n var box = SizedOverflowBox(\n alignment: Alignment.bottomRight,\n size: Size(_x, _y),\n child: Container(width: 30, height: 50, color: Colors.orange),\n );\n return Column(\n children: [\n Container(\n alignment: Alignment.topLeft,\n color: Colors.grey.withAlpha(88),\n width: 250,\n height: 60,\n child: box),\n _buildSlider()\n ],\n );\n }\n\n Widget _buildSlider() =>\n Column(\n children: [\n Slider(\n divisions: 100,\n min: 0,\n max: 250,\n label: 'x:' + _x.toStringAsFixed(1),\n value: _x,\n onChanged: (v) => setState(() => _x = v)),\n Slider(\n divisions: 100,\n min: 0,\n max: 100,\n label: 'y:' + _y.toStringAsFixed(1),\n value: _y,\n onChanged: (v) => setState(() => _y = v)),\n\n ],\n );\n}\n"},{"id":null,"widgetId":192,"name":"SliverOpacity基本使用","priority":1,"subtitle":" \n【opacity】 : 透明度 【double】\n【sliver】 : 子组件 【Function()】","code":"import 'package:flutter/material.dart';\nclass SliverOpacityDemo extends StatelessWidget {\n final data = List.generate(128, (i) => Color(0xFF6600FF - 2 * i));\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 300,\n child: CustomScrollView(\n slivers: [\n _buildSliverAppBar(),\n SliverPadding(\n padding: EdgeInsets.only(top: 10),\n sliver: SliverOpacity(opacity: 0.2, sliver: _buildSliverGrid()))\n ],\n ),\n );\n }\n\n Widget _buildSliverGrid() => SliverGrid.extent(\n childAspectRatio: 1 / 0.618,\n maxCrossAxisExtent: 180,\n crossAxisSpacing: 5,\n mainAxisSpacing: 5,\n children: data\n .map((e) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 60,\n color: e,\n child: Text(\n colorString(e),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n );\n\n Widget _buildSliverAppBar() {\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: _buildLeading(),\n title: Text('张风捷特烈'),\n actions: _buildActions(),\n elevation: 5,\n pinned: true,\n backgroundColor: Colors.orange,\n flexibleSpace: FlexibleSpaceBar(\n //伸展处布局\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n background: Image.asset(\n \"assets/images/caver.jpeg\",\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n\n Widget _buildLeading() => Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png'));\n\n List _buildActions() => [\n IconButton(\n onPressed: () {},\n icon: Icon(\n Icons.star_border,\n color: Colors.white,\n ),\n )\n ];\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":183,"name":"CustomScrollView基本使用","priority":1,"subtitle":" \n【slivers】 : 子组件列表 【List】\n【reverse】 : 是否反向 【bool】\n【scrollDirection】 : 滑动方向 【Axis】\n【controller】 : 控制器 【ScrollController】","code":"import 'package:flutter/material.dart';\nclass CustomScrollViewDemo extends StatelessWidget {\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 300,\n child: CustomScrollView(\n anchor: 0,\n scrollDirection: Axis.vertical,\n reverse: false,\n slivers: [_buildSliverAppBar(), _buildSliverFixedExtentList()],\n ),\n );\n }\n\n Widget _buildSliverFixedExtentList() => SliverFixedExtentList(\n itemExtent: 60,\n delegate: SliverChildBuilderDelegate(\n (_, int index) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: data[index],\n child: Text(\n colorString(data[index]),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ),\n childCount: data.length),\n );\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n\n _buildSliverAppBar() {\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png')),\n flexibleSpace: FlexibleSpaceBar(\n //伸展处布局\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n title: Text(\n '张风捷特烈',\n style: TextStyle(color: Colors.black, //标题\n shadows: [\n Shadow(color: Colors.blue, offset: Offset(1, 1), blurRadius: 2)\n ]),\n ),\n background: Image.asset(\n \"assets/images/caver.jpeg\", fit: BoxFit.cover,\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":191,"name":"SliverPadding基本使用","priority":1,"subtitle":" \n【sliver】 : 子组件 【Widget】\n【padding】 : 内边距 【EdgeInsetsGeometry】","code":"import 'package:flutter/material.dart';\nclass SliverPaddingDemo extends StatelessWidget {\n final data = List.generate(128, (i) => Color(0xFF6600FF - 2 * i));\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 300,\n child: CustomScrollView(\n slivers: [_buildSliverAppBar(), SliverPadding(\n padding: EdgeInsets.only(top: 10),\n sliver\n : _buildSliverGrid())],\n ),\n );\n }\n\n Widget _buildSliverGrid() => SliverGrid.extent(\n childAspectRatio: 1 / 0.618,\n maxCrossAxisExtent: 180,\n crossAxisSpacing: 5,\n mainAxisSpacing: 5,\n children: data\n .map((e) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 60,\n color: e,\n child: Text(\n colorString(e),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n );\n\n Widget _buildSliverAppBar() {\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: _buildLeading(),\n title: Text('张风捷特烈'),\n actions: _buildActions(),\n elevation: 5,\n pinned: true,\n backgroundColor: Colors.orange,\n flexibleSpace: FlexibleSpaceBar(\n //伸展处布局\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n background: Image.asset(\n \"assets/images/caver.jpeg\",\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n\n Widget _buildLeading() => Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png'));\n\n List _buildActions() => [\n IconButton(\n onPressed: () {},\n icon: Icon(\n Icons.star_border,\n color: Colors.white,\n ),\n )\n ];\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":188,"name":"SliverList基本使用","priority":1,"subtitle":" \nSliverGrid.count 指定轴向数量构造\nSliverGrid.extent 指定轴向长度构造\n属性特征同GridView,可详见之","code":"import 'package:flutter/material.dart';\nclass SliverGirdDemo extends StatelessWidget {\n final data = List.generate(128, (i) => Color(0xFF6600FF - 2 * i));\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 300,\n child: CustomScrollView(\n slivers: [_buildSliverAppBar(), _buildSliverList()],\n ),\n );\n }\n\n Widget _buildSliverList() => SliverGrid.extent(\n childAspectRatio: 1 / 0.618,\n maxCrossAxisExtent: 180,\n crossAxisSpacing: 5,\n mainAxisSpacing: 5,\n children: data\n .map((e) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 60,\n color: e,\n child: Text(\n colorString(e),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n );\n\n Widget _buildSliverAppBar() {\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: _buildLeading(),\n title: Text('张风捷特烈'),\n actions: _buildActions(),\n elevation: 5,\n pinned: true,\n backgroundColor: Colors.orange,\n flexibleSpace: FlexibleSpaceBar(\n //伸展处布局\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n background: Image.asset(\n \"assets/images/caver.jpeg\",\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n\n Widget _buildLeading() => Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png'));\n\n List _buildActions() => [\n IconButton(\n onPressed: () {},\n icon: Icon(\n Icons.star_border,\n color: Colors.white,\n ),\n )\n ];\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":184,"name":"SliverAppBar基本使用","priority":1,"subtitle":" \n【leading】 : 左侧组件 【Widget】\n【title】 : 中间组件 【Widget】\n【actions】 : 尾部组件列表 【List】\n【floating】 : 是否浮动 【bool】\n【pinned】 : 是否顶部停留 【bool】\n【snap】 : 是否半收展 【bool】\n【bottom】 : 底部组件 【PreferredSizeWidget】\n【expandedHeight】 : 延展高度 【double】\n【elevation】 : 影深 【double】\n【flexibleSpace】 : 延展空间 【FlexibleSpaceBar】\n【backgroundColor】 : 背景色 【Color】\n【controller】 : 控制器 【ScrollController】\n snap为true时必需floating为true","code":"import 'package:flutter/material.dart';\nclass SliverAppBarDemo extends StatefulWidget {\n @override\n _SliverAppBarDemoState createState() => _SliverAppBarDemoState();\n}\n\nclass _SliverAppBarDemoState extends State {\n bool _floating = false;\n bool _pinned = false;\n bool _snap = false;\n\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildTool(),\n Container(\n height: 300,\n child: CustomScrollView(\n slivers: [\n _buildSliverAppBar(),\n _buildSliverFixedExtentList()\n ],\n ),\n ),\n ],\n );\n }\n\n Widget _buildSliverAppBar() {\n print(_floating);\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: _buildLeading(),\n title: Text('张风捷特烈'),\n actions: _buildActions(),\n elevation: 5,\n floating: _floating,\n pinned: _pinned,\n snap: _snap,\n backgroundColor: Colors.orange,\n flexibleSpace: FlexibleSpaceBar(//伸展处布局\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n background: Image.asset(\n \"assets/images/caver.jpeg\",\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n\n Widget _buildLeading() => Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png'));\n\n List _buildActions() => [\n IconButton(\n onPressed: () {},\n icon: Icon(\n Icons.star_border,\n color: Colors.white,\n ),\n )\n ];\n\n Widget _buildSliverFixedExtentList() => SliverFixedExtentList(\n itemExtent: 60,\n delegate: SliverChildBuilderDelegate(\n (_, int index) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: data[index],\n child: Text(\n colorString(data[index]),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ),\n childCount: data.length),\n );\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n\n Widget _buildTool() {\n return Row(\n mainAxisAlignment: MainAxisAlignment.center,\n children: [\n Wrap(\n direction: Axis.vertical,\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n Text('floating'),\n Switch(\n value: _floating,\n onChanged: (v) {\n if(_snap&&!v){\n _snap =false;\n }\n setState(() => _floating = v);\n }),\n ],\n ),\n Wrap(\n direction: Axis.vertical,\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n Text('pinned'),\n Switch(\n value: _pinned,\n onChanged: (v) => setState(() => _pinned = v)),\n ],\n ) ,Wrap(\n direction: Axis.vertical,\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n Text('snap'),\n Switch(\n value: _snap,\n onChanged: (v) {\n if(_floating){\n setState(() => _snap = v);\n }\n\n }),\n ],\n )\n ],\n );\n }\n}\n"},{"id":null,"widgetId":185,"name":"SliverList基本使用","priority":1,"subtitle":" \n【delegate】 : 孩子代理 【SliverChildDelegate】","code":"import 'package:flutter/material.dart';\nclass SliverListDemo extends StatelessWidget {\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 300,\n child: CustomScrollView(\n slivers: [_buildSliverAppBar(), _buildSliverList()],\n ),\n );\n }\n\n Widget _buildSliverList() => SliverList(\n delegate: SliverChildBuilderDelegate(\n (_, int index) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 60,\n color: data[index],\n child: Text(\n colorString(data[index]),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ),\n childCount: data.length),\n );\n\n Widget _buildSliverAppBar() {\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: _buildLeading(),\n title: Text('张风捷特烈'),\n actions: _buildActions(),\n elevation: 5,\n pinned: true,\n backgroundColor: Colors.orange,\n flexibleSpace: FlexibleSpaceBar(\n //伸展处布局\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n background: Image.asset(\n \"assets/images/caver.jpeg\",\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n\n Widget _buildLeading() => Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png'));\n\n List _buildActions() => [\n IconButton(\n onPressed: () {},\n icon: Icon(\n Icons.star_border,\n color: Colors.white,\n ),\n )\n ];\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":307,"name":"SliverOverlapAbsorber基本使用","priority":1,"subtitle":" \n【sliver】 : 子组件 【Widget】\n【handle】 : *处理器 【SliverOverlapAbsorberHandle】\n如果不使用SliverOverlapAbsorber和SliverOverlapInjector组件,NestedScrollView的内容会和头部栏重叠。","code":"import 'package:flutter/material.dart';\nclass SliverOverlapAbsorberDemo extends StatelessWidget {\n final _tabs = ['风神传', '封妖志', \"幻将录\", \"永恒传说\"];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: MediaQuery.of(context).size.height - 200,\n child: Scaffold(\n body: DefaultTabController(\n length: _tabs.length,\n child: NestedScrollView(\n headerSliverBuilder:\n (BuildContext context, bool innerBoxIsScrolled) {\n return [\n SliverOverlapAbsorber(\n handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),\n sliver: SliverAppBar(\n title: const Text('旷古奇书'),\n pinned: true,\n elevation: 6, //影深\n expandedHeight: 220.0,\n forceElevated: innerBoxIsScrolled, //为true时展开有阴影\n flexibleSpace: FlexibleSpaceBar(\n background: Image.asset(\n \"assets/images/wy_300x200_filter.jpg\",\n fit: BoxFit.cover,\n ),\n ),\n bottom: TabBar(\n tabs: _tabs\n .map((String name) => Tab(text: name,))\n .toList(),\n ),\n ),\n ),\n ];\n },\n body: _buildTabBarView(),\n ),\n ),\n ));\n }\n\n Widget _buildTabBarView() {\n return TabBarView(\n children: _tabs.map((String name) {\n return SafeArea(\n top: false,\n bottom: false,\n child: Builder(\n builder: (BuildContext context) {\n return CustomScrollView(\n key: PageStorageKey(name),\n slivers: [\n SliverOverlapInjector(\n handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),\n ),\n SliverPadding(\n padding: const EdgeInsets.all(8.0),\n sliver: SliverFixedExtentList(\n itemExtent: 48.0,\n delegate: SliverChildBuilderDelegate(\n (BuildContext context, int index) {\n return ListTile(\n title: Text('《$name》 第 $index章'),\n );\n },\n childCount: 50,\n ),\n ),\n ),\n ],\n );\n },\n ),\n );\n }).toList(),\n );\n }\n}\n"},{"id":null,"widgetId":186,"name":"SliverFixedExtentList基本使用","priority":1,"subtitle":" \n【itemExtent】 : 主轴方向强迫长度 【double】\n【delegate】 : 孩子代理 【SliverChildDelegate】","code":"import 'package:flutter/material.dart';\nclass SliverFixedExtentListDemo extends StatefulWidget {\n @override\n _SliverFixedExtentListDemoState createState() => _SliverFixedExtentListDemoState();\n}\n\nclass _SliverFixedExtentListDemoState extends State {\n final data = [\n Colors.orange[50],\n Colors.orange[100],\n Colors.orange[200],\n Colors.orange[300],\n Colors.orange[400],\n Colors.orange[500],\n Colors.orange[600],\n Colors.orange[700],\n Colors.orange[800],\n Colors.orange[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 300,\n child: CustomScrollView(\n slivers: [_buildSliverAppBar(), _buildSliverList()],\n ),\n );\n }\n\n Widget _buildSliverList() => SliverFixedExtentList(\n itemExtent: 50,\n delegate: SliverChildBuilderDelegate(\n (_, int index) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 60,\n color: data[index],\n child: Text(\n colorString(data[index]),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ),\n childCount: data.length),\n );\n\n Widget _buildSliverAppBar() {\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: _buildLeading(),\n title: Text('张风捷特烈'),\n actions: _buildActions(),\n elevation: 5,\n pinned: true,\n backgroundColor: Colors.orange,\n flexibleSpace: FlexibleSpaceBar(\n //伸展处布局\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n background: Image.asset(\n \"assets/images/caver.jpeg\",\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n\n Widget _buildLeading() => Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png'));\n\n List _buildActions() => [\n IconButton(\n onPressed: () {},\n icon: Icon(\n Icons.star_border,\n color: Colors.white,\n ),\n )\n ];\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":189,"name":"SliverToBoxAdapter基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】","code":"import 'package:flutter/material.dart';\nclass SliverToBoxAdapterDemo extends StatelessWidget {\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 300,\n child: CustomScrollView(\n slivers: [\n _buildSliverAppBar(),\n _buildCommonWidget(),\n _buildSliverList()\n ],\n ),\n );\n }\n\n Widget _buildCommonWidget() => SliverToBoxAdapter(\n child: Container(\n padding: EdgeInsets.symmetric(horizontal: 10),\n color: Colors.grey.withAlpha(22),\n child: ListTile(\n leading: Image.asset(\"assets/images/icon_head.png\"),\n title: Text(\"以梦为马\"),\n subtitle: Text(\"海子\"),\n selected: true,\n contentPadding: EdgeInsets.all(5),\n trailing: Icon(Icons.more_vert),\n ),\n ),\n );\n\n Widget _buildSliverList() => SliverList(\n delegate: SliverChildBuilderDelegate(\n (_, int index) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 60,\n color: data[index],\n child: Text(\n colorString(data[index]),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ),\n childCount: data.length),\n );\n\n Widget _buildSliverAppBar() {\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: _buildLeading(),\n title: Text('张风捷特烈'),\n actions: _buildActions(),\n elevation: 2,\n pinned: true,\n backgroundColor: Colors.orange,\n flexibleSpace: FlexibleSpaceBar(\n //伸展处布局\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n background: Image.asset(\n \"assets/images/caver.jpeg\",\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n\n Widget _buildLeading() => Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png'));\n\n List _buildActions() => [\n IconButton(\n onPressed: () {},\n icon: Icon(\n Icons.star_border,\n color: Colors.white,\n ),\n )\n ];\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":308,"name":"SliverOverlapInjector基本使用","priority":1,"subtitle":" \n【sliver】 : 子组件 【Widget】\n【handle】 : *处理器 【SliverOverlapAbsorberHandle】\n如果不使用SliverOverlapAbsorber和SliverOverlapInjector组件,NestedScrollView的内容会和头部栏重叠。","code":"import 'package:flutter/material.dart';\nclass SliverOverlapInjectorDemo extends StatelessWidget {\n final _tabs = ['风神传', '封妖志', \"幻将录\", \"永恒传说\"];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: MediaQuery.of(context).size.height - 200,\n child: Scaffold(\n body: DefaultTabController(\n length: _tabs.length,\n child: NestedScrollView(\n headerSliverBuilder:\n (BuildContext context, bool innerBoxIsScrolled) {\n return [\n SliverOverlapAbsorber(\n handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),\n sliver: SliverAppBar(\n title: const Text('旷古奇书'),\n pinned: true,\n elevation: 6, //影深\n expandedHeight: 220.0,\n forceElevated: innerBoxIsScrolled, //为true时展开有阴影\n flexibleSpace: FlexibleSpaceBar(\n background: Image.asset(\n \"assets/images/wy_300x200_filter.jpg\",\n fit: BoxFit.cover,\n ),\n ),\n bottom: TabBar(\n tabs: _tabs\n .map((String name) => Tab(text: name,))\n .toList(),\n ),\n ),\n ),\n ];\n },\n body: _buildTabBarView(),\n ),\n ),\n ));\n }\n\n Widget _buildTabBarView() {\n return TabBarView(\n children: _tabs.map((String name) {\n return SafeArea(\n top: false,\n bottom: false,\n child: Builder(\n builder: (BuildContext context) {\n return CustomScrollView(\n key: PageStorageKey(name),\n slivers: [\n SliverOverlapInjector(\n handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),\n ),\n SliverPadding(\n padding: const EdgeInsets.all(8.0),\n sliver: SliverFixedExtentList(\n itemExtent: 48.0,\n delegate: SliverChildBuilderDelegate(\n (BuildContext context, int index) {\n return ListTile(\n title: Text('《$name》 第 $index章'),\n );\n },\n childCount: 50,\n ),\n ),\n ),\n ],\n );\n },\n ),\n );\n }).toList(),\n );\n }\n}\n"},{"id":null,"widgetId":187,"name":"SliverFillViewport基本使用","priority":1,"subtitle":" \n【viewportFraction】 : 视口分率 【double】\n【delegate】 : 孩子代理 【SliverChildDelegate】","code":"import 'package:flutter/material.dart';\nclass SliverFillViewportDemo extends StatefulWidget {\n @override\n _SliverFillViewportDemoState createState() => _SliverFillViewportDemoState();\n}\n\nclass _SliverFillViewportDemoState extends State {\n final data = [\n Colors.orange[50],\n Colors.orange[100],\n Colors.orange[200],\n Colors.orange[300],\n Colors.orange[400],\n Colors.orange[500],\n Colors.orange[600],\n Colors.orange[700],\n Colors.orange[800],\n Colors.orange[900],\n ];\n var _viewportFraction = 0.5;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildTool(),\n Container(\n height: 300,\n child: CustomScrollView(\n slivers: [_buildSliverAppBar(), _buildSliverList()],\n ),\n ),\n ],\n );\n }\n\n Widget _buildSliverList() => SliverFillViewport(\n viewportFraction: _viewportFraction,\n delegate: SliverChildBuilderDelegate(\n (_, int index) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 60,\n color: data[index],\n child: Text(\n colorString(data[index]),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ),\n childCount: data.length),\n );\n\n Widget _buildSliverAppBar() {\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: _buildLeading(),\n title: Text('张风捷特烈'),\n actions: _buildActions(),\n elevation: 5,\n pinned: true,\n backgroundColor: Colors.orange,\n flexibleSpace: FlexibleSpaceBar(\n //伸展处布局\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n background: Image.asset(\n \"assets/images/caver.jpeg\",\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n\n Widget _buildLeading() => Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png'));\n\n List _buildActions() => [\n IconButton(\n onPressed: () {},\n icon: Icon(\n Icons.star_border,\n color: Colors.white,\n ),\n )\n ];\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n\n _buildTool() {\n return Slider(\n value: _viewportFraction,\n min: 0.01,\n divisions: 20,\n label: _viewportFraction.toStringAsFixed(1),\n max: 2.0,\n onChanged: (v) => setState(() => _viewportFraction = v));\n }\n}\n"},{"id":null,"widgetId":196,"name":"FlexibleSpaceBar基本使用","priority":1,"subtitle":" \n【title】 : 标题组件 【Widget】\n【titlePadding】 : 标题间距 【EdgeInsetsGeometry】\n【collapseMode】 : 折叠模式 【CollapseMode】\n【stretchModes】 : 延伸模式 【List】\n【background】 : 背景组件 【Widget】\n【centerTitle】 : 是否居中 【bool】","code":"import 'package:flutter/material.dart';\nclass FlexibleSpaceBarDemo extends StatelessWidget {\n\n final data = [\n Colors.blue[50],\n Colors.blue[100],\n Colors.blue[200],\n Colors.blue[300],\n Colors.blue[400],\n Colors.blue[500],\n Colors.blue[600],\n Colors.blue[700],\n Colors.blue[800],\n Colors.blue[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return\n Container(\n height: 300,\n child: CustomScrollView(\n slivers: [\n _buildSliverAppBar(),\n _buildSliverFixedExtentList()\n ],\n ),\n );\n }\n\n Widget _buildSliverAppBar() {\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: _buildLeading(),\n actions: _buildActions(),\n pinned: true,\n backgroundColor: Colors.blue,\n flexibleSpace: FlexibleSpaceBar(//伸展处布局\n centerTitle: false,\n title: Text('张风捷特烈',style: TextStyle(shadows: [\n Shadow(color: Colors.blue, offset: Offset(1, 1), blurRadius: 2)\n ]),),\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n stretchModes: [StretchMode.blurBackground,StretchMode.zoomBackground],\n background: Image.asset(\n \"assets/images/caver.jpeg\",\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n\n Widget _buildLeading() => Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png'));\n\n List _buildActions() => [\n IconButton(\n onPressed: () {},\n icon: Icon(\n Icons.star_border,\n color: Colors.white,\n ),\n )\n ];\n\n Widget _buildSliverFixedExtentList() => SliverFixedExtentList(\n itemExtent: 60,\n delegate: SliverChildBuilderDelegate(\n (_, int index) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: data[index],\n child: Text(\n colorString(data[index]),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ),\n childCount: data.length),\n );\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":190,"name":"SliverPersistentHeader基本使用","priority":1,"subtitle":" \n【delegate】 : 代理 【SliverPersistentHeaderDelegate】\n【floating】 : 是否浮动 【bool】\n【pinned】 : 是否顶部停留 【bool】","code":"import 'dart:math';\nimport 'package:flutter/material.dart';\nclass SliverPersistentHeaderDemo extends StatelessWidget {\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 500,\n child: CustomScrollView(\n slivers: [\n _buildSliverAppBar(),\n _buildPersistentHeader('袅缈岁月,青丝银发',Color(0xffe7fcc9)),\n _buildCommonWidget(),\n _buildPersistentHeader('以梦为马,不负韶华',Color(0xffcca4ff)),\n _buildSliverList()\n ],\n ),\n );\n }\n\n Widget _buildCommonWidget() => SliverToBoxAdapter(\n child: Container(\n padding: EdgeInsets.symmetric(horizontal: 10),\n color: Colors.grey.withAlpha(22),\n child: ListTile(\n leading: Image.asset(\"assets/images/icon_head.png\"),\n title: Text(\"以梦为马\"),\n subtitle: Text(\"海子\"),\n selected: true,\n contentPadding: EdgeInsets.all(5),\n trailing: Icon(Icons.more_vert),\n ),\n ),\n );\n Widget _buildPersistentHeader(String text,Color color) => SliverPersistentHeader(\n pinned: true,\n delegate: _SliverDelegate(\n minHeight: 40.0,\n maxHeight: 100.0,\n child: Container(\n color: color,\n child: Center(\n child: Text(text, style: TextStyle(\n fontSize: 18,\n shadows: [Shadow(color: Colors.white, offset: Offset(1, 1))]),\n ),\n )),\n ));\n\n Widget _buildSliverList() => SliverList(\n delegate: SliverChildBuilderDelegate(\n (_, int index) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 60,\n color: data[index],\n child: Text(\n colorString(data[index]),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ),\n childCount: data.length),\n );\n\n Widget _buildSliverAppBar() {\n return SliverAppBar(\n expandedHeight: 190.0,\n leading: _buildLeading(),\n title: Text('张风捷特烈'),\n actions: _buildActions(),\n elevation: 2,\n pinned: true,\n backgroundColor: Colors.orange,\n flexibleSpace: FlexibleSpaceBar(\n //伸展处布局\n titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距\n collapseMode: CollapseMode.parallax, //视差效果\n background: Image.asset(\n \"assets/images/caver.jpeg\",\n fit: BoxFit.cover,\n ),\n ),\n );\n }\n\n Widget _buildLeading() => Container(\n margin: EdgeInsets.all(10),\n child: Image.asset('assets/images/icon_head.png'));\n\n List _buildActions() => [\n IconButton(\n onPressed: () {},\n icon: Icon(\n Icons.star_border,\n color: Colors.white,\n ),\n )\n ];\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n\n\nclass _SliverDelegate extends SliverPersistentHeaderDelegate {\n _SliverDelegate({\n @required this.minHeight,\n @required this.maxHeight,\n @required this.child,\n });\n\n final double minHeight; //最小高度\n final double maxHeight; //最大高度\n final Widget child; //孩子\n\n @override\n double get minExtent => minHeight;\n\n @override\n double get maxExtent => max(maxHeight, minHeight);\n\n @override\n Widget build(\n BuildContext context, double shrinkOffset, bool overlapsContent) {\n return new SizedBox.expand(child: child);\n }\n\n @override //是否需要重建\n bool shouldRebuild(_SliverDelegate oldDelegate) {\n return maxHeight != oldDelegate.maxHeight ||\n minHeight != oldDelegate.minHeight ||\n child != oldDelegate.child;\n }\n}"},{"id":null,"widgetId":312,"name":"PerformanceOverlay基本使用","priority":1,"subtitle":"使用PerformanceOverlay.allEnabled可以开始所有的监测项。","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass PerformanceOverlayDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return PerformanceOverlay.allEnabled(\n\n );\n }\n}\n\n"},{"id":null,"widgetId":197,"name":"ErrorWidget基本使用","priority":1,"subtitle":" \n入参 : 显示信息 【Object】","code":"import 'package:flutter/material.dart';\nclass ErrorWidgetDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: ErrorWidget(\n 'I am Error ErrorWidget\\n'\n 'But now, there has no error.'\n ),\n );\n }\n}\n"},{"id":null,"widgetId":110,"name":"Table基本使用","priority":1,"subtitle":" \n【children】 : 组件列表 【List】\n【columnWidths】 : 列宽 【Map】\n【defaultColumnWidth】 : 默认列宽 【TableColumnWidth】\n【border】 : 边线 【TableBorder】\n【textDirection】 : 文字方向 【TextDirection】\n【defaultVerticalAlignment】 : 单元格竖直方向对齐模式 【TableCellVerticalAlignment】","code":"import 'package:flutter/material.dart';\nclass CustomTable extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var title = _ItemBean(\"单位称\", \"量纲\", \"单位\", \"单位名称\", \"单位符号\");\n var m = _ItemBean(\"长度\", \"L\", \"1m\", \"米\", \"m\");\n var kg = _ItemBean(\"质量\", \"M\", \"1Kg\", \"千克\", \"Kg\");\n var s = _ItemBean(\"时间\", \"T\", \"1s\", \"秒\", \"s\");\n var a = _ItemBean(\"安培\", \"Ι\", \"1A\", \"安培\", \"A\");\n var k = _ItemBean(\"热力学温度\", \"θ\", \"1K\", \"开尔文\", \"K\");\n var mol = _ItemBean(\"物质的量\", \"N\", \"1mol\", \"摩尔\", \"mol\");\n var cd = _ItemBean(\"发光强度\", \"J\", \"1cd\", \"坎德拉\", \"cd\");\n\n var data = <_ItemBean>[title, m, kg, s, a, k, mol, cd];\n\n return SingleChildScrollView(\n scrollDirection: Axis.horizontal,\n child: Table(\n columnWidths: const {\n 0: FixedColumnWidth(80.0),\n 1: FixedColumnWidth(80.0),\n 2: FixedColumnWidth(80.0),\n 3: FixedColumnWidth(80.0),\n 4: FixedColumnWidth(80.0),\n },\n defaultVerticalAlignment: TableCellVerticalAlignment.middle,\n border: TableBorder.all(\n color: Colors.orangeAccent, width: 1.0, style: BorderStyle.solid),\n children: data\n .map((item) => TableRow(children: [\n Center(\n child: Text(\n item.name,\n style: TextStyle(color: Colors.blue),\n )),\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Center(child: Text(item.symbol)),\n ),\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Center(child: Text(item.unitSymbol)),\n ),\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Center(child: Text(item.unitName)),\n ),\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Center(child: Text(item.unit)),\n ),\n ]))\n .toList(),\n ),\n );\n }\n}\n\nclass _ItemBean {\n String name;\n String symbol;\n String unit;\n String unitName;\n String unitSymbol;\n\n _ItemBean(this.name, this.symbol, this.unit, this.unitName, this.unitSymbol);\n}\n"},{"id":null,"widgetId":313,"name":"RawImage基本使用","priority":1,"subtitle":"【image】 : 图片 【ui.Image】\n【width】 : 宽 【int】\n【height】: 高 【int】\n【isAntiAlias】: 是否抗锯齿 【bool】\n【filterQuality】: 过滤质量 【FilterQuality】\n很多属性同Image,详见之.","code":"import 'dart:async';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'dart:ui' as ui;\nclass RawImageDemo extends StatefulWidget {\n @override\n _RawImageDemoState createState() => _RawImageDemoState();\n}\n\nclass _RawImageDemoState extends State {\n ui.Image _image;\n\n @override\n void initState() {\n super.initState();\n _loadImageFromAssets('assets/images/icon_head.png');\n }\n\n @override\n Widget build(BuildContext context) {\n if (_image == null)\n return Container(\n width: 80,\n height: 80,\n );\n\n return Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [\n Column(\n mainAxisSize: MainAxisSize.min,\n children: [\n RawImage(\n image: _image,\n width: 150,\n height: 150,\n isAntiAlias: true,\n filterQuality: FilterQuality.high,\n ),\n Text('isAntiAlias: true'),\n Text('FilterQuality.high')\n ],\n ),\n Column(\n mainAxisSize: MainAxisSize.min,\n children: [\n RawImage(\n image: _image,\n width: 150,\n height: 150,\n isAntiAlias: false,\n ),\n Text('isAntiAlias: false'),\n Text('FilterQuality.low')\n ],\n ),\n ],\n );\n }\n\n void _loadImageFromAssets(String name) async {\n _image = await loadImageByProvider(AssetImage(name));\n\n setState(() {});\n }\n\n //通过ImageProvider读取Image\n Future loadImageByProvider(\n ImageProvider provider, {\n ImageConfiguration config = ImageConfiguration.empty,\n }) async {\n Completer completer = Completer(); //完成的回调\n ImageStreamListener listener;\n ImageStream stream = provider.resolve(config); //获取图片流\n listener = ImageStreamListener((ImageInfo frame, bool sync) {\n //监听\n final ui.Image image = frame.image;\n completer.complete(image); //完成\n stream.removeListener(listener); //移除监听\n });\n stream.addListener(listener); //添加监听\n return completer.future; //返回\n }\n}\n"},{"id":null,"widgetId":338,"name":"ButtonBarTheme基本使用","priority":1,"subtitle":"可指定ButtonBarThemeData数据属性为【后代】的ButtonBar组件设置默认样式,如对齐方式、样式、边距等。也可以用ButtonBarTheme.of获取ButtonBar的主题属性。","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass ButtonBarThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ButtonBarTheme(\n child: TempButtonBar(),\n data: ButtonBarTheme.of(context).copyWith(\n alignment: MainAxisAlignment.center,\n buttonPadding: EdgeInsets.symmetric(horizontal: 6),\n overflowDirection: VerticalDirection.up,\n buttonMinWidth: 150,\n buttonHeight: 30,\n buttonTextTheme: ButtonTextTheme.primary));\n }\n}\n\nclass TempButtonBar extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ButtonBar(\n alignment: MainAxisAlignment.center,\n children: [\n RaisedButton(\n color: Colors.blue, child: Text(\"1.Raised\"), onPressed: () {}),\n OutlineButton(child: Text(\"2.Outline\"), onPressed: () {}),\n FlatButton(\n color: Colors.blue,\n onPressed: () {},\n child: Text(\"3.Flat\"),\n )\n ],\n );\n }\n}\n"},{"id":null,"widgetId":329,"name":"DividerTheme使用","priority":1,"subtitle":" \n属性参数与Divider类似,可以通过DividerTheme.of获取分割线主题数据,\"\n也可以为DividerTheme【后代】的分割线设置默认样式,包括颜色、粗细、高度等。","code":"import 'package:flutter/material.dart';\nclass DividerThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return DividerTheme(\n data: DividerThemeData(\n color: Colors.orange,\n thickness: 2,\n space: 10,\n indent: 10,\n endIndent: 10,\n\n ),\n child: Wrap(\n spacing: 10,\n children: [\n Divider(),\n Divider(),\n Divider(),\n Divider(),\n Divider(),\n Container(\n height: 100,\n child: Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [\n VerticalDivider(),\n VerticalDivider(),\n VerticalDivider(),\n VerticalDivider(),\n VerticalDivider(),\n ],\n ),\n )\n ],\n ),\n );\n }\n}"},{"id":null,"widgetId":334,"name":"ListTileTheme基本使用","priority":1,"subtitle":"可指定ListTileThemeData数据属性为【后代】的ListTile组件设置默认样式,如样式、颜色、装饰、边距等。也可以用ListTileTheme.of获取ListTile的主题属性。","code":"import 'package:flutter/material.dart';\nclass ListTileThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ListTileTheme(\n dense: false,\n style: ListTileStyle.list,\n selectedColor: Colors.blue,\n contentPadding: EdgeInsets.only(left: 15,right: 15,top: 5,bottom: 5),\n iconColor: Colors.purple,\n textColor: Colors.orange,\n child: _ListTileSimple(),\n );\n }\n}\n\nclass _ListTileSimple extends StatefulWidget {\n @override\n _ListTileSimpleState createState() => _ListTileSimpleState();\n}\n\nclass _ListTileSimpleState extends State<_ListTileSimple> {\n bool _selected = false;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.all(10),\n color: Colors.grey.withAlpha(22),\n child: ListTile(\n leading: Image.asset(\"assets/images/icon_head.png\"),\n selected: _selected,\n title: Text(\"以梦为马\"),\n subtitle: Text(\"海子\"),\n trailing: Icon(Icons.more_vert),\n onTap: () => setState(() => _selected = !_selected),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":106,"name":"Expanded基本使用","priority":1,"subtitle":" \n【child】 : 孩子 【Widget】\n【flex】 : 剩余空间分配占比 【int】","code":"import 'package:flutter/material.dart';\nimport '../../../../app/utils/color_utils.dart';\nclass CustomExpended extends StatefulWidget {\n @override\n _CustomExpendedState createState() => _CustomExpendedState();\n}\n\nclass _CustomExpendedState extends State {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n buildRow([0, 0, 0]),\n SizedBox(height: 10,),\n buildRow([0, 0, 1]),\n SizedBox(height: 10,),\n buildRow([1, 1, 1]),\n SizedBox(height: 10,),\n buildRow([2, 3, 3]),\n ],\n ),\n );\n }\n\n Widget buildRow(List num) {\n return Row(\n children: num.map((e) => Expanded(\n flex: e,\n child: Container(\n alignment: Alignment.center,\n width: 50,\n height: 50,\n color: ColorUtils.randomColor(),\n child: Text(\n 'flex=$e',\n style: TextStyle(color: Colors.white),\n ),\n ),\n )).toList());\n }\n}\n"},{"id":null,"widgetId":181,"name":"DropDownButtonHideUnderline使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】,","code":"import 'package:flutter/material.dart';\nclass CustomDropDownButtonHideUnderline extends StatefulWidget {\n @override\n _CustomDropDownButtonHideUnderlineState createState() =>\n _CustomDropDownButtonHideUnderlineState();\n}\n\nclass _CustomDropDownButtonHideUnderlineState\n extends State {\n Color _color = Colors.red;\n final _colors = [Colors.red, Colors.yellow, Colors.blue, Colors.green];\n final _info = [\"红色\", \"黄色\", \"蓝色\", \"绿色\"];\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: [\n Container(\n margin: EdgeInsets.symmetric(horizontal: 20),\n width: 50,\n height: 50,\n color: _color,\n ),\n DropdownButtonHideUnderline(\n child: DropdownButton(\n value: _color,\n elevation: 1,\n icon: Icon(\n Icons.expand_more,\n size: 20,\n color: _color,\n ),\n items: _buildItems(),\n onChanged: (v) => setState(() => _color = v)),\n ),\n ],\n );\n }\n\n List> _buildItems() => _colors\n .map((e) => DropdownMenuItem(\n value: e,\n child: Text(\n _info[_colors.indexOf(e)],\n style: TextStyle(color: e),\n )))\n .toList();\n}\n"},{"id":null,"widgetId":331,"name":"SliderTheme使用","priority":1,"subtitle":" \n可通过SliderTheme.of获取Slider主题数据对象,其中包含大量属性用于对Slider的设定。\"\n可以为ButtonTheme【后代】的按钮组件设置默认样式,包括颜色、形状、尺寸等。","code":"import 'dart:math';\nimport 'package:flutter/material.dart';\nclass SliderThemeDemo extends StatefulWidget {\n @override\n _SliderThemeDemoState createState() => _SliderThemeDemoState();\n}\n\nclass _SliderThemeDemoState extends State {\n var _bliss = 0.5;\n\n @override\n Widget build(BuildContext context) {\n return SliderTheme(\n data: SliderTheme.of(context).copyWith(activeTrackColor: Colors.orange),\n child: Slider(\n min: 0.0,\n max: 200.0,\n divisions: 10,\n label: \"${_bliss.toStringAsFixed(1)}\",\n onChanged: (double value) {\n setState(() {\n _bliss = value;\n });\n },\n value: _bliss,\n ),\n );\n }\n}"},{"id":null,"widgetId":333,"name":"TooltipTheme基本使用","priority":1,"subtitle":"可指定TooltipThemeData数据属性为【后代】的Tooltip组件设置默认样式,如装饰、文字样式、显示时长、边距等。也可以用TooltipTheme.of获取Tooltip的主题属性。","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass TooltipThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return TooltipTheme(\n child: TempTooltip(),\n data: TooltipTheme.of(context).copyWith(\n preferBelow: false,\n padding: EdgeInsets.all(5),\n verticalOffset: 20,\n margin: EdgeInsets.all(2),\n textStyle: TextStyle(\n color: Colors.red,\n shadows: [Shadow(color: Colors.white, offset: Offset(1, 1))]),\n decoration: BoxDecoration(boxShadow: [\n BoxShadow(\n color: Colors.orangeAccent,\n offset: Offset(1, 1),\n blurRadius: 8)\n ])));\n }\n}\n\nclass TempTooltip extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [\n Tooltip(\n message: \"天王盖地虎\",\n child: Icon(Icons.info_outline)),\n Tooltip(\n message: \"宝塔镇河妖\",\n child: Icon(Icons.info_outline)),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":331,"name":"SliderTheme对Slider的样式定制","priority":2,"subtitle":" \n通过thumbShape和valueIndicatorShape可以对Slider进行样式定制。\"\n注: 本例参考flutter-gallery中的SlideDemo","code":"import 'dart:math';\nimport 'package:flutter/material.dart';\nclass DIYSliderTheme extends StatefulWidget {\n @override\n _DIYSliderThemeState createState() => _DIYSliderThemeState();\n}\n\nclass _DIYSliderThemeState extends State {\n var _bliss = 0.5;\n\n @override\n Widget build(BuildContext context) {\n final ThemeData theme = Theme.of(context);\n\n return SliderTheme(\n data: theme.sliderTheme.copyWith(\n activeTrackColor: Colors.deepPurple,\n inactiveTrackColor: Colors.blue.withAlpha(55),\n activeTickMarkColor: theme.colorScheme.onSurface.withOpacity(0.7),\n inactiveTickMarkColor: theme.colorScheme.surface.withOpacity(0.7),\n overlayColor: theme.colorScheme.onSurface.withOpacity(0.12),\n thumbColor: Colors.deepPurple,\n valueIndicatorColor: Colors.deepPurpleAccent,\n thumbShape: _CustomThumbShape(),\n valueIndicatorShape: _CustomValueIndicatorShape(),\n valueIndicatorTextStyle: theme.accentTextTheme.body2\n .copyWith(color: theme.colorScheme.onSurface),\n ),\n child: Slider(\n min: 0.0,\n max: 200.0,\n divisions: 10,\n label: \"${_bliss.toStringAsFixed(1)}\",\n onChanged: (double value) {\n setState(() {\n _bliss = value;\n });\n },\n value: _bliss,\n ),\n );\n }\n}\n\nclass _CustomThumbShape extends SliderComponentShape {\n static const double _thumbSize = 4.0;\n static const double _disabledThumbSize = 3.0;\n\n @override\n Size getPreferredSize(bool isEnabled, bool isDiscrete) {\n return isEnabled\n ? const Size.fromRadius(_thumbSize)\n : const Size.fromRadius(_disabledThumbSize);\n }\n\n static final Animatable sizeTween = Tween(\n begin: _disabledThumbSize,\n end: _thumbSize,\n );\n\n @override\n void paint(PaintingContext context, Offset center,\n {Animation activationAnimation,\n Animation enableAnimation,\n bool isDiscrete,\n TextPainter labelPainter,\n RenderBox parentBox,\n SliderThemeData sliderTheme,\n TextDirection textDirection,\n double value,\n double textScaleFactor,\n Size sizeWithOverflow}) {\n final Canvas canvas = context.canvas;\n final ColorTween colorTween = ColorTween(\n begin: sliderTheme.disabledThumbColor,\n end: sliderTheme.thumbColor,\n );\n final double size = _thumbSize * sizeTween.evaluate(enableAnimation);\n final Path thumbPath = _downTriangle(size, center);\n canvas.drawPath(\n thumbPath, Paint()..color = colorTween.evaluate(enableAnimation));\n }\n}\n\nPath _upTriangle(double size, Offset thumbCenter) =>\n _downTriangle(size, thumbCenter, invert: true);\n\nPath _downTriangle(double size, Offset thumbCenter, {bool invert = false}) {\n final Path thumbPath = Path();\n final double height = sqrt(3.0) / 2.0;\n final double centerHeight = size * height / 3.0;\n final double halfSize = size / 2.0;\n final double sign = invert ? -1.0 : 1.0;\n thumbPath.moveTo(\n thumbCenter.dx - halfSize, thumbCenter.dy + sign * centerHeight);\n thumbPath.lineTo(thumbCenter.dx, thumbCenter.dy - 2.0 * sign * centerHeight);\n thumbPath.lineTo(\n thumbCenter.dx + halfSize, thumbCenter.dy + sign * centerHeight);\n thumbPath.close();\n return thumbPath;\n}\n\nclass _CustomValueIndicatorShape extends SliderComponentShape {\n static const double _indicatorSize = 4.0;\n static const double _disabledIndicatorSize = 3.0;\n static const double _slideUpHeight = 30.0;\n\n @override\n Size getPreferredSize(bool isEnabled, bool isDiscrete) {\n return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize);\n }\n\n static final Animatable sizeTween = Tween(\n begin: _disabledIndicatorSize,\n end: _indicatorSize,\n );\n\n @override\n void paint(PaintingContext context, Offset center,\n {Animation activationAnimation,\n Animation enableAnimation,\n bool isDiscrete,\n TextPainter labelPainter,\n RenderBox parentBox,\n SliderThemeData sliderTheme,\n TextDirection textDirection,\n double value,\n double textScaleFactor,\n Size sizeWithOverflow}) {\n final Canvas canvas = context.canvas;\n final ColorTween enableColor = ColorTween(\n begin: sliderTheme.disabledThumbColor,\n end: sliderTheme.valueIndicatorColor,\n );\n final Tween slideUpTween = Tween(\n begin: 0.0,\n end: _slideUpHeight,\n );\n final double size = _indicatorSize * sizeTween.evaluate(enableAnimation);\n final Offset slideUpOffset =\n Offset(0.0, -slideUpTween.evaluate(activationAnimation));\n final Path thumbPath = _upTriangle(size, center + slideUpOffset);\n final Color paintColor = enableColor\n .evaluate(enableAnimation)\n .withAlpha((255.0 * activationAnimation.value).round());\n canvas.drawPath(\n thumbPath,\n Paint()..color = paintColor,\n );\n canvas.drawLine(\n center,\n center + slideUpOffset,\n Paint()\n ..color = paintColor\n ..style = PaintingStyle.stroke\n ..strokeWidth = 2.0);\n labelPainter.paint(\n canvas,\n center +\n slideUpOffset +\n Offset(-labelPainter.width / 2.0, -labelPainter.height - 4.0));\n }\n}\n"},{"id":null,"widgetId":328,"name":"ChipTheme基本使用","priority":1,"subtitle":"可指定ChipThemeData数据属性为【后代】的Chip类型组件设置默认样式,属性和Chip属性类似,如阴影、颜色、边距、形状、文字样式等。也可以用ChipTheme.of获取Chip的主题数据。","code":"import 'package:flutter/material.dart';\nclass ChipThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ChipTheme(\n data: ChipTheme.of(context).copyWith(\n selectedColor: Colors.orange.withAlpha(55),\n selectedShadowColor: Colors.blue,\n shadowColor: Colors.orangeAccent,\n pressElevation: 5,\n elevation: 3,\n ),\n child: CustomFilterChip(),\n );\n }\n}\n\nclass CustomFilterChip extends StatefulWidget {\n @override\n _CustomFilterChipState createState() => _CustomFilterChipState();\n}\n\nclass _CustomFilterChipState extends State {\n final Map map = {\n 'A': 'Ant',\n 'B': 'Bug',\n 'C': 'Cat',\n 'D': 'Dog',\n };\n List _selected = [];\n\n @override\n Widget build(BuildContext context) {\n return Column(\n mainAxisAlignment: MainAxisAlignment.center,\n children: [\n Wrap(\n children: map.keys.map((key) => _buildChild(key)).toList(),\n ),\n Container(\n padding: EdgeInsets.all(10),\n child: Text('您已选择: ${_selected.join(', ')}')),\n ],\n );\n }\n\n Padding _buildChild(String key) {\n return Padding(\n padding: const EdgeInsets.all(4.0),\n child: FilterChip(\n avatar: CircleAvatar(child: Text(key)),\n label: Text(map[key]),\n selected: _selected.contains(map[key]),\n onSelected: (bool value) {\n setState(() {\n if (value) {\n _selected.add(map[key]);\n } else {\n _selected.removeWhere((name) => name == map[key]);\n }\n });\n },\n ),\n );\n }\n}\n"},{"id":null,"widgetId":315,"name":"LayoutId使用场景","priority":1,"subtitle":" \n【id】 : 标识id 【Object】\n【child】 : 子组件 【Widget】","code":"import 'dart:io';\nimport 'package:flutter/material.dart';\nclass LayoutIdDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 300,\n height: 150,\n color: Colors.grey.withAlpha(33),\n child: CustomMultiChildLayout(\n delegate: CornerCustomMultiChildLayout(\n padding:EdgeInsets.only(left: 10,top: 5,right: 10,bottom: 5),\n ),\n children: [\n LayoutId(id: CornerType.topLeft, child: Box50(Colors.red)),\n LayoutId(id: CornerType.topRight, child: Box50(Colors.yellow)),\n LayoutId(id: CornerType.bottomLeft, child: Box50(Colors.blue)),\n LayoutId(id: CornerType.bottomRight, child: Box50(Colors.green)),\n ],\n ),\n );\n }\n}\n\n// 50 颜射盒\nclass Box50 extends StatelessWidget {\n final Color color;\n Box50(this.color);\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 50,\n height: 50,\n color: color,\n );\n }\n}\n\n\nenum CornerType{\n topLeft,\n topRight,\n bottomLeft,\n bottomRight\n}\n\n\nclass CornerCustomMultiChildLayout extends MultiChildLayoutDelegate{\n final EdgeInsets padding;\n \n CornerCustomMultiChildLayout({this.padding = EdgeInsets.zero});\n\n @override\n void performLayout(Size size) {\n if (hasChild(CornerType.topLeft)) {\n layoutChild(CornerType.topLeft, BoxConstraints.loose(size));\n positionChild(CornerType.topLeft, Offset.zero.translate(padding.left, padding.top));\n }\n if (hasChild(CornerType.topRight)) {\n var childSize = layoutChild(CornerType.topRight, BoxConstraints.loose(size));\n positionChild(CornerType.topRight, Offset(size.width-childSize.width,0).translate(-padding.right, padding.top));\n }\n if (hasChild(CornerType.bottomLeft)) {\n var childSize = layoutChild(CornerType.bottomLeft, BoxConstraints.loose(size));\n positionChild(CornerType.bottomLeft, Offset(0,size.height-childSize.height).translate(padding.left, -padding.bottom));\n }\n if (hasChild(CornerType.bottomRight)) {\n var childSize = layoutChild(CornerType.bottomRight, BoxConstraints.loose(size));\n positionChild(CornerType.bottomRight, Offset(size.width-childSize.width,size.height-childSize.height).translate(-padding.right, -padding.bottom));\n }\n }\n\n @override\n bool shouldRelayout(CornerCustomMultiChildLayout oldDelegate) => oldDelegate.padding!=padding;\n \n}\n\n"},{"id":null,"widgetId":327,"name":"MaterialBannerTheme基本使用","priority":1,"subtitle":"可指定MaterialBannerThemeData数据属性为【后代】的MaterialBanner组件设置默认样式,如背景色、边距、文字样式等。也可以用MaterialBannerTheme.of获取MaterialBanner的主题数据。","code":"import 'package:flutter/material.dart';\nclass MaterialBannerThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return MaterialBannerTheme(\n data: MaterialBannerTheme.of(context).copyWith(\n backgroundColor: Colors.purple,\n padding: EdgeInsetsDirectional.only(start: 16.0, top: 2.0,end: 2),\n leadingPadding:EdgeInsetsDirectional.only(end: 16.0) ,\n contentTextStyle: TextStyle(color: Colors.white),\n ),\n child: _MaterialBannerDemo(),\n );\n }\n}\n\n\nclass _MaterialBannerDemo extends StatelessWidget {\n final info =\n 'A banner displays an important, succinct message, and provides actions for users to address. '\n 'A user action is required for itto be dismissed.';\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [MaterialBanner(\n content: Text(info),\n leading: Icon(Icons.warning, color: Colors.yellow),\n actions: [\n RaisedButton(\n color: Colors.white,\n onPressed: () {},\n child: Text(\n 'I KNOW',\n style: TextStyle(\n color: Colors.purple,\n fontWeight: FontWeight.bold,\n fontSize: 14),\n ),\n ),\n\n RaisedButton(\n color: Colors.white,\n onPressed: () {},\n child: Text(\n 'I IGNORE',\n style: TextStyle(\n color: Colors.purple,\n fontWeight: FontWeight.bold,\n fontSize: 14),\n ),\n ),\n ],\n )],\n );\n }\n}"},{"id":null,"widgetId":109,"name":"Flexible基本使用","priority":1,"subtitle":" \n【child】 : 孩子 【Widget】\n【fit】 : 适应模式*2 【FlexFit】\n【flex】 : 剩余空间分配占比 【int】","code":"import 'package:flutter/material.dart';\nclass CustomFlexible extends StatefulWidget {\n @override\n _CustomFlexibleState createState() => _CustomFlexibleState();\n}\n\nclass _CustomFlexibleState extends State {\n double _width = 300.0;\n bool _loose = false;\n\n @override\n Widget build(BuildContext context) {\n return Column(children: [\n Container(\n color: Colors.grey.withAlpha(33),\n width: _width,\n padding: EdgeInsets.all(8.0),\n child: Row(\n children: [\n Flexible(\n flex: 2,\n child: Container(\n alignment: Alignment.center,\n height: 50,\n color: Colors.red,\n child: Text(\n 'flex=2',\n style: TextStyle(color: Colors.white),\n ),\n ),\n ),\n Flexible(\n flex: 3,\n child: Container(\n alignment: Alignment.center,\n height: 50,\n color: Colors.blue,\n child: Text(\n 'flex=3',\n style: TextStyle(color: Colors.white),\n ),\n ),\n ),\n Flexible(\n flex: 4,\n fit: _loose?FlexFit.loose:FlexFit.tight,\n child: Container(\n constraints: BoxConstraints(maxWidth: 60),\n alignment: Alignment.center,\n height: 50,\n color: Colors.green,\n child: Text(\n 'flex=4 \\nfit:${_loose?'loose':'tight'}',\n style: TextStyle(color: Colors.white),\n ),\n ),\n )\n ],\n )),\n _buildOp()\n ]);\n }\n\n Widget _buildOp() {\n return Row(\n children: [\n Switch(\n value: _loose,\n onChanged: (v) => setState(() => _loose = v)),\n Expanded(\n child: Slider(\n divisions: 10,\n min: 100,\n max: 350,\n value: _width,\n onChanged: (v) => setState(() => _width = v)),\n ),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":180,"name":"ScrollConfiguration基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【behavior】 : 滑动行为 【ScrollBehavior】\n 可以使用ScrollConfiguration让ListView无蓝色阴影","code":"import 'package:flutter/material.dart';\nclass CustomScrollConfiguration extends StatelessWidget {\n final data = [\n Colors.cyan[50],\n Colors.cyan[100],\n Colors.cyan[200],\n Colors.cyan[300],\n Colors.cyan[400],\n Colors.cyan[500],\n Colors.cyan[600],\n Colors.cyan[700],\n Colors.cyan[800],\n Colors.cyan[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: ScrollConfiguration(\n behavior: NoScrollBehavior(), child: _buildListView()),\n );\n }\n\n Widget _buildListView() => ListView(\n padding: EdgeInsets.symmetric(horizontal: 5),\n children: data\n .map((color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n );\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n\nclass NoScrollBehavior extends ScrollBehavior {\n @override\n Widget buildViewportChrome(\n BuildContext context, Widget child, AxisDirection axisDirection) =>\n child;\n}\n"},{"id":null,"widgetId":108,"name":"Positioned基本使用","priority":1,"subtitle":" \n【child】 : 组件 【Widget】\n【top】 : 到父顶距离 【double】\n【right】 : 到父右距离 【double】\n【left】 : 到父左距离 【double】\n【bottom】 : 到父底距离 【double】","code":"import 'package:flutter/material.dart';\nclass CustomPositioned extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var yellowBox = Container(\n color: Colors.yellow,\n height: 100,\n width: 100,\n );\n\n var redBox = Container(\n color: Colors.red,\n height: 90,\n width: 90,\n );\n\n var greenBox = Container(\n color: Colors.green,\n height: 80,\n width: 80,\n );\n\n var cyanBox = Container(\n color: Colors.cyanAccent,\n height: 70,\n width: 70,\n );\n\n return Container(\n width: 200,\n height: 120,\n color: Colors.grey.withAlpha(33),\n child: Stack(\n children: [\n yellowBox,\n redBox,\n Positioned(top: 20, left: 20, child: greenBox),\n Positioned(\n child: cyanBox,\n bottom: 10,\n right: 10,\n )\n ],\n ));\n }\n}\n"},{"id":null,"widgetId":332,"name":"ToggleButtonsTheme基本使用","priority":1,"subtitle":"可指定ToggleButtonsThemeData数据属性为【后代】的ToggleButtons组件设置默认样式,如边框样式、颜色、装饰等。也可以用ToggleButtonsTheme.of获取ToggleButtons的主题数据。","code":"import 'package:flutter/material.dart';\nclass ToggleButtonsThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ToggleButtonsTheme(\n data: ToggleButtonsTheme.of(context).copyWith(\n borderWidth: 1,\n borderColor: Colors.orangeAccent,\n selectedBorderColor: Colors.blue,\n splashColor: Colors.purple.withAlpha(66),\n borderRadius: BorderRadius.circular(10),\n selectedColor: Colors.red,\n fillColor: Colors.green.withAlpha(11),\n ),\n child: _ToggleButtonsSimple(),\n );\n }\n}\n\n\nclass _ToggleButtonsSimple extends StatefulWidget {\n @override\n _ToggleButtonsSimpleState createState() => _ToggleButtonsSimpleState();\n}\n\nclass _ToggleButtonsSimpleState extends State<_ToggleButtonsSimple> {\n var _isSelected = [true, false, false];\n\n @override\n Widget build(BuildContext context) {\n return ToggleButtons(\n children: [\n Icon(Icons.skip_previous),\n Icon(Icons.pause),\n Icon(Icons.skip_next),\n ],\n isSelected: _isSelected,\n onPressed: (value) => setState(() {\n _isSelected = _isSelected.map((e) => false).toList();\n _isSelected[value] = true;\n }),\n );\n }\n}"},{"id":null,"widgetId":167,"name":"MediaQuery获取数据信息","priority":1,"subtitle":" \nMediaQuery.of(context)可以获取MediaQueryData","code":"import 'package:flutter/material.dart';\nclass CustomMediaQuery extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var queryData = MediaQuery.of(context);\n var data = {\n \"size\": queryData.size,\n \"devicePixelRatio\": queryData.devicePixelRatio.toStringAsFixed(1),\n \"textScaleFactor\": queryData.textScaleFactor.toStringAsFixed(1),\n \"platformBrightness\": queryData.platformBrightness,\n \"padding\": queryData.padding,\n \"viewInsets\": queryData.viewInsets,\n \"systemGestureInsets\": queryData.padding,\n \"viewPadding\": queryData.padding,\n \"physicalDepth\": queryData.padding,\n \"alwaysUse24HourFormat\": queryData.padding,\n \"accessibleNavigation\": queryData.alwaysUse24HourFormat,\n \"invertColors\": queryData.invertColors,\n \"highContrast\": queryData.highContrast,\n \"disableAnimations\": queryData.disableAnimations,\n \"boldText\": queryData.boldText,\n };\n\n return Container(\n height: 200,\n color: Colors.grey.withAlpha(11),\n child:ListView(\n children: data.keys.map((e) => buildItem(e, data)).toList(),\n ),\n );\n }\n\n Widget buildItem(String e, Map data) => Column(\n children: [\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Row(\n mainAxisAlignment: MainAxisAlignment.spaceBetween,\n children: [\n Text(\n e,\n style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),\n ),\n Text(\n data[e].toString(),\n style: TextStyle(fontSize: 16, color: Colors.orange),\n )\n ],\n ),\n ),\n Divider(\n height: 1,\n )\n ],\n );\n}\n\n\n"},{"id":null,"widgetId":325,"name":"IconTheme使用","priority":1,"subtitle":" \n可以通过IconTheme.of获取图标主题数据,也可以为IconTheme【后代】的图标组件设置默认样式,包括颜色、透明度、尺寸。","code":"import 'package:flutter/material.dart';\nclass IconThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return IconTheme(\n data: IconThemeData(\n color: Colors.purple,\n opacity: 1.0,\n size: 30\n ),\n child: Wrap(\n spacing: 10,\n children: [\n Icon(Icons.add),\n Icon(Icons.ac_unit),\n Icon(Icons.g_translate),\n Icon(Icons.remove)\n ],\n ),\n );\n }\n}"},{"id":null,"widgetId":324,"name":"DefaultTextStyle使用","priority":1,"subtitle":" \n各属性同Text,详见之。\n其功能是: 设置默认的文字样式应用于【后代组件】,注意后代组件也可以指定自身的样式","code":"import 'package:flutter/material.dart';\nclass DefaultTextStyleDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return DefaultTextStyle(\n style: TextStyle(\n fontSize: 18,\n color: Colors.blue,\n decoration: TextDecoration.underline),\n child: Wrap(\n spacing: 5,\n children: [\n Text(\"Hello,\",),\n FlutterLogo(),\n Text(\"Flutter\",style: TextStyle(color: Colors.red),),\n Text(\"Unit.\"),\n ],\n ),\n );\n }\n}\n"},{"id":null,"widgetId":330,"name":"PopupMenuTheme基本使用","priority":1,"subtitle":"可指定PopupMenuThemeData数据属性为【后代】的PopupMenuButton组件设置默认样式,如形状、影深、颜色、文字样式等。也可以用PopupMenuTheme.of获取PopupMenu的主题数据。","code":"import 'package:flutter/material.dart';\nclass PopupMenuThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return PopupMenuTheme(\n data: PopupMenuTheme.of(context).copyWith(\n color: Colors.orangeAccent,\n elevation: 1,\n textStyle: TextStyle(color: Colors.white),\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.only(\n topLeft: Radius.circular(20),\n bottomRight: Radius.circular(20),\n topRight: Radius.circular(5),\n bottomLeft: Radius.circular(5),\n )),\n ),\n child: _PopupMenuButtonSimple(),\n );\n }\n}\n\nclass _PopupMenuButtonSimple extends StatefulWidget {\n @override\n _PopupMenuButtonSimpleState createState() => _PopupMenuButtonSimpleState();\n}\n\nclass _PopupMenuButtonSimpleState extends State<_PopupMenuButtonSimple> {\n final map = {\n \"关于\": Icons.info_outline,\n \"帮助\": Icons.help_outline,\n \"反馈\": Icons.add_comment,\n };\n\n @override\n Widget build(BuildContext context) {\n return PopupMenuButton(\n itemBuilder: (context) => buildItems(),\n offset: Offset(0, 50),\n onSelected: print,\n onCanceled: () => print('onCanceled'),\n );\n }\n\n List> buildItems() {\n return map.keys\n .toList()\n .map((e) => PopupMenuItem(\n value: e,\n child: Wrap(\n spacing: 6,\n children: [\n Icon(\n map[e],\n ),\n Text(e),\n ],\n )))\n .toList();\n }\n}"},{"id":null,"widgetId":326,"name":"ButtonTheme使用","priority":1,"subtitle":" \n属性参数同MaterialButton,可以通过ButtonTheme.of获取按钮主题数据,\"\n也可以为ButtonTheme【后代】的按钮组件设置默认样式,包括颜色、形状、尺寸等。","code":"import 'package:flutter/material.dart';\nclass ButtonThemeDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return ButtonTheme(\n buttonColor: Colors.orange,\n splashColor: Colors.blue,\n minWidth: 40,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n child: Wrap(\n spacing: 10,\n children: [\n RaisedButton(onPressed: (){},child: Icon(Icons.add)),\n FlatButton(onPressed: (){},child: Icon(Icons.add)),\n OutlineButton(onPressed: (){},child: Icon(Icons.add)),\n MaterialButton(onPressed: (){},child: Icon(Icons.add)),\n ],\n ),\n );\n }\n}"},{"id":null,"widgetId":161,"name":"IndexedStack基本使用","priority":1,"subtitle":" \n【children】 : 子组件列表 【Lis】\n【alignment】 : 对齐方式 【AlignmentGeometry】\n【index】 : 当前显示组件 【int】","code":"import 'package:flutter/material.dart';\nclass CustomIndexedStack extends StatefulWidget {\n @override\n _CustomIndexedStackState createState() => _CustomIndexedStackState();\n}\n\nclass _CustomIndexedStackState extends State {\n var _index = 1;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildSwitch(),\n Container(\n width: 200,\n height: 100,\n color: Colors.grey.withAlpha(33),\n child: IndexedStack(\n index: _index,\n children: [\n Container(\n color: Colors.red,\n width: 80,\n height: 80,\n ),\n Positioned(\n bottom: 10,\n right: 10,\n child: Container(\n color: Colors.blue,\n width: 80,\n height: 80,\n ),\n )\n ],\n ),\n ),\n ],\n );\n }\n\n Widget _buildSwitch() => Switch(\n value: _index == 0,\n onChanged: (v) => setState(() => _index = v ? 0 : 1),\n );\n}\n"},{"id":null,"widgetId":340,"name":"Viewport的基本使用","priority":1,"subtitle":"【offset】 : *视口偏移量 【ViewportOffset】\n【cacheExtentStyle】: 预加载类型 【CacheExtentStyle】\n【cacheExtent】: 预加载量 【double】\n【axisDirection】: 滑动方向 【AxisDirection】\n【slivers】: 子Sliver组件集 【List】\n【anchor】: 锚点 【double】\n可以运行这些代码,查看ColorItem的构建情况,128个色条并非一次性全部构建。","code":"import 'package:flutter/gestures.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/rendering.dart';\nclass ViewportDemo extends StatelessWidget {\n final data = List.generate(128, (i) => Color(0xFF6600FF - 2 * i));\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 250,\n child: Scrollable(\n axisDirection: AxisDirection.down,\n physics: BouncingScrollPhysics(),\n dragStartBehavior: DragStartBehavior.start,\n viewportBuilder: (ctx, position) => Viewport(\n axisDirection: AxisDirection.down,\n cacheExtent: 200,\n anchor: 0,\n cacheExtentStyle: CacheExtentStyle.pixel,\n offset: position,\n slivers: [_buildSliverList()],\n ),\n ),\n );\n }\n\n Widget _buildSliverList() => SliverList(\n delegate: SliverChildBuilderDelegate(\n (_, int index) =>ColorItem(color: data[index],),\n childCount: data.length),\n );\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n\nclass ColorItem extends StatefulWidget {\n final Color color;\n\n ColorItem({Key key,this.color}) : super(key: key);\n @override\n _ColorItemState createState() => _ColorItemState();\n}\n\nclass _ColorItemState extends State {\n\n @override\n void initState() {\n super.initState();\n print('-----initState----${colorString(widget.color)}-----------');\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n margin: EdgeInsets.only(top: 1),\n alignment: Alignment.center,\n width: 100,\n height: 60,\n color: widget.color,\n child: Text(\n colorString(widget.color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n );\n }\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":341,"name":"CustomMultiChildLayout基本使用","priority":1,"subtitle":" \n【children】 : 子组件集 【List】\n【delegate】 : 布局代理 【MultiChildLayoutDelegate】","code":"import 'dart:io';\nimport 'package:flutter/material.dart';\nclass CustomMultiChildLayoutDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 300,\n height: 150,\n color: Colors.grey.withAlpha(33),\n child: CustomMultiChildLayout(\n delegate: CornerCustomMultiChildLayout(\n padding:EdgeInsets.only(left: 10,top: 5,right: 10,bottom: 5),\n ),\n children: [\n LayoutId(id: CornerType.topLeft, child: Box50(Colors.red)),\n LayoutId(id: CornerType.topRight, child: Box50(Colors.yellow)),\n LayoutId(id: CornerType.bottomLeft, child: Box50(Colors.blue)),\n LayoutId(id: CornerType.bottomRight, child: Box50(Colors.green)),\n ],\n ),\n );\n }\n}\n\n// 50 颜射盒\nclass Box50 extends StatelessWidget {\n final Color color;\n Box50(this.color);\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 50,\n height: 50,\n color: color,\n );\n }\n}\n\n\nenum CornerType{\n topLeft,\n topRight,\n bottomLeft,\n bottomRight\n}\n\n\nclass CornerCustomMultiChildLayout extends MultiChildLayoutDelegate{\n final EdgeInsets padding;\n \n CornerCustomMultiChildLayout({this.padding = EdgeInsets.zero});\n\n @override\n void performLayout(Size size) {\n if (hasChild(CornerType.topLeft)) {\n layoutChild(CornerType.topLeft, BoxConstraints.loose(size));\n positionChild(CornerType.topLeft, Offset.zero.translate(padding.left, padding.top));\n }\n if (hasChild(CornerType.topRight)) {\n var childSize = layoutChild(CornerType.topRight, BoxConstraints.loose(size));\n positionChild(CornerType.topRight, Offset(size.width-childSize.width,0).translate(-padding.right, padding.top));\n }\n if (hasChild(CornerType.bottomLeft)) {\n var childSize = layoutChild(CornerType.bottomLeft, BoxConstraints.loose(size));\n positionChild(CornerType.bottomLeft, Offset(0,size.height-childSize.height).translate(padding.left, -padding.bottom));\n }\n if (hasChild(CornerType.bottomRight)) {\n var childSize = layoutChild(CornerType.bottomRight, BoxConstraints.loose(size));\n positionChild(CornerType.bottomRight, Offset(size.width-childSize.width,size.height-childSize.height).translate(-padding.right, -padding.bottom));\n }\n }\n\n @override\n bool shouldRelayout(CornerCustomMultiChildLayout oldDelegate) => oldDelegate.padding!=padding;\n \n}\n\n"},{"id":null,"widgetId":96,"name":"Column基本使用","priority":1,"subtitle":" \n【children】 : 组件列表 【List】\n【mainAxisAlignment】 : 主轴对齐 【MainAxisAlignment】\n【crossAxisAlignment】 : 交叉轴对齐 【CrossAxisAlignment】\n【textBaseline】 : 文字基线 【TextBaseline】\n【verticalDirection】 : 竖直方向 【VerticalDirection】\n【mainAxisSize】 : 主轴尺寸 【MainAxisSize】","code":"import 'package:flutter/material.dart';\nclass CustomColumn extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildTitle(),\n _buildContent(context),\n ],\n );\n }\n\n Widget _buildTitle() {\n return Container(\n height: 70,\n color: Color(0x4484FFFF),\n child: Row(\n children: [\n Padding(\n child: Icon(\n Icons.add_location,\n size: 30,\n color: Colors.pink,\n ),\n padding: EdgeInsets.only(left: 25, right: 20),\n ),\n Expanded(\n child: Text(\n \"附近\",\n style: TextStyle(fontSize: 18),\n ),\n ),\n Padding(\n child: Icon(Icons.keyboard_arrow_right, color: Colors.black38),\n padding: EdgeInsets.only(right: 25),\n ),\n ],\n ));\n }\n\n Widget _buildContent(ctx) => Container(\n width: MediaQuery.of(ctx).size.width,\n color: Colors.orangeAccent,\n height: 100,\n child: Icon(\n Icons.android,\n size: 50,\n color: Colors.white,\n ),\n );\n}\n"},{"id":null,"widgetId":342,"name":"ListView的基本使用","priority":1,"subtitle":"【mainAxis】 : 主轴方向 【Axis】\n【reverse】: 是否反向 【bool】\n【children】: 子组件集 【List】","code":"import 'package:flutter/gestures.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/rendering.dart';\nclass ListBodyDemo extends StatelessWidget {\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 300,\n child: ListView(\n children: [\n ListBody(\n mainAxis: Axis.vertical,\n reverse: false,\n children: [\n Container(color: Colors.red, height: 50.0,),\n Container(color: Colors.orange, height: 50.0,),\n Container(color: Colors.yellow, height: 50.0,),\n ],\n ),\n Container(color: Colors.green, height: 80.0,),\n ListBody(\n mainAxis: Axis.vertical,\n reverse: false,\n children: [\n Container(color: Colors.blue, height: 50.0,),\n Container(color: Colors.indigo, height: 50.0,),\n Container(color: Colors.purple, height: 50.0,),\n ],\n )\n ]\n ),\n );\n }\n}\n"},{"id":null,"widgetId":95,"name":"Row基本使用","priority":1,"subtitle":" \n【children】 : 组件列表 【List】\n【mainAxisAlignment】 : 主轴对齐 【MainAxisAlignment】\n【crossAxisAlignment】 : 交叉轴对齐 【CrossAxisAlignment】\n【textBaseline】 : 文字基线 【TextBaseline】\n【verticalDirection】 : 竖直方向 【VerticalDirection】\n【mainAxisSize】 : 主轴尺寸 【MainAxisSize】","code":"import 'package:flutter/material.dart';\nclass CustomRow extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 70,\n color: Color(0x4484FFFF),\n child: Row(\n children: [\n Padding(\n child: Icon(\n Icons.add_location,\n size: 30,\n color: Colors.pink,\n ),\n padding: EdgeInsets.only(left: 25, right: 20),\n ),\n Expanded(\n child: Text(\n \"附近\",\n style: TextStyle(fontSize: 18),\n ),\n ),\n Padding(\n child: Icon(Icons.keyboard_arrow_right, color: Colors.black38),\n padding: EdgeInsets.only(right: 25),\n ),\n ],\n ));\n }\n}\n"},{"id":null,"widgetId":98,"name":"Wrap的textDirection属性","priority":4,"subtitle":" \n【textDirection】 : 文字方向 【TextDirection】","code":"import 'package:flutter/material.dart';\nclass TextDirectionWrap extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: TextDirection.values\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 160,\n height: 100,\n color: Colors.grey.withAlpha(88),\n child: _buildItem(mode)),\n Text(mode.toString().split('.')[1])\n ]))\n .toList());\n }\n\n final yellowBox = Container(\n color: Colors.yellow,\n height: 30,\n width: 50,\n );\n\n final redBox = Container(\n color: Colors.red,\n height: 40,\n width: 40,\n );\n final greenBox = Container(\n color: Colors.green,\n height: 40,\n width: 20,\n );\n final blackBox = Container(\n color: Colors.black,\n height: 10,\n width: 10,\n );\n final purpleBox = Container(\n color: Colors.purple,\n height: 20,\n width: 20,\n );\n final orangeBox = Container(\n color: Colors.orange,\n height: 80,\n width: 20,\n );\n final cyanBox = Container(\n color: Colors.cyanAccent,\n height: 10,\n width: 20,\n );\n\n _buildItem(mode) => Wrap(\n textDirection: mode,\n runSpacing: 10,\n spacing: 10,\n children: [\n yellowBox, redBox, greenBox, cyanBox,\n blackBox, purpleBox, orangeBox,\n ],\n );\n}\n"},{"id":null,"widgetId":98,"name":"Wrap的基础用法","priority":1,"subtitle":" \n【children】 : 组件列表 【List】\n【spacing】 : 主轴条目间距 【double】\n【runSpacing】 : 交叉轴条目间距 【double】\n【direction】 : 主轴对齐 【Axis】","code":"import 'package:flutter/material.dart';\nclass DirectionWrap extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: Axis.values\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 160,\n height: 100,\n color: Colors.grey.withAlpha(33),\n child: _buildItem(mode)),\n Text(mode.toString().split('.')[1])\n ]))\n .toList());\n }\n final yellowBox = Container(\n color: Colors.yellow,\n height: 30,\n width: 50,\n );\n\n final redBox = Container(\n color: Colors.red,\n height: 40,\n width: 40,\n );\n final greenBox = Container(\n color: Colors.green,\n height: 40,\n width: 20,\n );\n final blackBox = Container(\n color: Colors.black,\n height: 10,\n width: 10,\n );\n final purpleBox = Container(\n color: Colors.purple,\n height: 20,\n width: 20,\n );\n final orangeBox = Container(\n color: Colors.orange,\n height: 80,\n width: 20,\n );\n final cyanBox = Container(\n color: Colors.cyanAccent,\n height: 10,\n width: 20,\n );\n\n _buildItem(mode) => Wrap(\n direction: mode,\n runSpacing: 10,\n spacing: 10,\n children: [\n yellowBox, redBox, greenBox, cyanBox,\n blackBox, purpleBox, orangeBox,\n ],\n );\n}"},{"id":null,"widgetId":98,"name":"Wrap的verticalDirection属性","priority":5,"subtitle":" \n【verticalDirection】 : 竖直方向 【VerticalDirection】","code":"import 'package:flutter/material.dart';\nclass VerticalDirectionWrap extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: VerticalDirection.values\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 160,\n height: 100,\n color: Colors.grey.withAlpha(88),\n child: _buildItem(mode)),\n Text(mode.toString().split('.')[1])\n ]))\n .toList());\n }\n\n final yellowBox = Container(\n color: Colors.yellow,\n height: 30,\n width: 50,\n );\n\n final redBox = Container(\n color: Colors.red,\n height: 40,\n width: 40,\n );\n final greenBox = Container(\n color: Colors.green,\n height: 40,\n width: 20,\n );\n final blackBox = Container(\n color: Colors.black,\n height: 10,\n width: 10,\n );\n final purpleBox = Container(\n color: Colors.purple,\n height: 20,\n width: 20,\n );\n final orangeBox = Container(\n color: Colors.orange,\n height: 80,\n width: 20,\n );\n final cyanBox = Container(\n color: Colors.cyanAccent,\n height: 10,\n width: 20,\n );\n\n _buildItem(mode) => Wrap(\n verticalDirection: mode,\n direction: Axis.vertical,\n runSpacing: 10,\n spacing: 10,\n children: [\n yellowBox, redBox, greenBox, cyanBox,\n blackBox, purpleBox, orangeBox,\n ],\n );\n}"},{"id":null,"widgetId":98,"name":"Wrap的alignment属性","priority":2,"subtitle":" \n【alignment】 : 主轴对齐 【WrapAlignment】","code":"import 'package:flutter/material.dart';\nclass WrapAlignmentWrap extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: WrapAlignment.values\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 160,\n height: 100,\n color: Colors.grey.withAlpha(88),\n child: _buildItem(mode)),\n Text(mode.toString().split('.')[1])\n ]))\n .toList());\n }\n\n final yellowBox = Container(\n color: Colors.yellow,\n height: 30,\n width: 50,\n );\n\n final redBox = Container(\n color: Colors.red,\n height: 40,\n width: 40,\n );\n final greenBox = Container(\n color: Colors.green,\n height: 40,\n width: 20,\n );\n final blackBox = Container(\n color: Colors.black,\n height: 10,\n width: 10,\n );\n final purpleBox = Container(\n color: Colors.purple,\n height: 20,\n width: 20,\n );\n final orangeBox = Container(\n color: Colors.orange,\n height: 80,\n width: 20,\n );\n final cyanBox = Container(\n color: Colors.cyanAccent,\n height: 10,\n width: 20,\n );\n\n _buildItem(mode) => Wrap(\n alignment: mode,\n runSpacing: 10,\n spacing: 10,\n children: [\n yellowBox, redBox,\n greenBox, cyanBox,\n blackBox, purpleBox,\n orangeBox,\n ],\n );\n}"},{"id":null,"widgetId":98,"name":"Wrap的crossAxisAlignment属性","priority":3,"subtitle":" \n【crossAxisAlignment】 : 交叉轴对齐 【CrossAxisAlignment】","code":"import 'package:flutter/material.dart';\nclass CrossAxisAlignmentWrap extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: WrapCrossAlignment.values\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 160,\n height: 100,\n color: Colors.grey.withAlpha(88),\n child: _buildItem(mode)),\n Text(mode.toString().split('.')[1])\n ]))\n .toList());\n }\n\n final yellowBox = Container(\n color: Colors.yellow,\n height: 30,\n width: 50,\n );\n\n final redBox = Container(\n color: Colors.red,\n height: 40,\n width: 40,\n );\n final greenBox = Container(\n color: Colors.green,\n height: 40,\n width: 20,\n );\n final blackBox = Container(\n color: Colors.black,\n height: 10,\n width: 10,\n );\n final purpleBox = Container(\n color: Colors.purple,\n height: 20,\n width: 20,\n );\n final orangeBox = Container(\n color: Colors.orange,\n height: 80,\n width: 20,\n );\n final cyanBox = Container(\n color: Colors.cyanAccent,\n height: 10,\n width: 20,\n );\n\n _buildItem(mode) => Wrap(\n crossAxisAlignment: mode,\n runSpacing: 10,\n spacing: 10,\n children: [\n yellowBox, redBox,\n greenBox, cyanBox,\n blackBox, purpleBox,\n orangeBox,\n ],\n );\n}"},{"id":null,"widgetId":101,"name":"RichText基本使用","priority":1,"subtitle":" \n【text】 : 文字 【TextSpan】\n 其他属性与Text相同,详见之。","code":"import 'package:flutter/material.dart';\nimport '../../../../app/utils/color_utils.dart';\nclass CustomRichText extends StatelessWidget {\n final str = \" 发光强度简称光强,国际单位是(坎德拉)简写cd。\"\n \"1cd是指光源在指定方向的单位立体角内发出的光通量。\"\n \"光源辐射是均匀时,则光强为I=F/Ω,Ω为立体角,单位为球面度(sr),F为光通量,\"\n \"单位是流明,对于点光源由I=F/4π 。光亮度是表示发光面明亮程度的,\"\n \"指发光表面在指定方向的发光强度与垂直且指定方向的发光面的面积之比,\"\n \"单位是坎德拉/平方米。对于一个漫散射面,尽管各个方向的光强和光通量不同,\"\n \"但各个方向的亮度都是相等的。电视机的荧光屏就是近似于这样的漫散射面,\"\n \"所以从各个方向上观看图像,都有相同的亮度感。\";\n\n @override\n Widget build(BuildContext context) {\n return Padding(\n padding: const EdgeInsets.only(\n left: 10.0,\n right: 10,\n ),\n child: RichText(\n text: TextSpan(\n children: str\n .split(\"\")\n .map((str) => TextSpan(\n text: str,\n style: TextStyle(\n fontSize: 14, color: ColorUtils.randomColor())))\n .toList())),\n );\n }\n}\n"},{"id":null,"widgetId":97,"name":"Stack基本使用","priority":1,"subtitle":" \n【children】 : 组件列表 【List】\n【textDirection】 : 孩子排布方向 【MainAxisAlignment】\n【alignment】 : 对齐方式 【AlignmentGeometry】\n【overflow】 : 溢出模式 【Overflow】\n【fit】 : 适应模式 【StackFit】","code":"import 'package:flutter/material.dart';\nclass CustomStack extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var yellowBox = Container(\n color: Colors.yellow,\n height: 100,\n width: 100,\n );\n\n var redBox = Container(\n color: Colors.red,\n height: 90,\n width: 90,\n );\n\n var greenBox = Container(\n color: Colors.green,\n height: 80,\n width: 80,\n );\n\n var cyanBox = Container(\n color: Colors.cyanAccent,\n height: 70,\n width: 70,\n );\n\n return Container(\n width: 200,\n height: 120,\n color: Colors.grey.withAlpha(33),\n child: Stack(\n textDirection: TextDirection.rtl,\n fit: StackFit.loose,\n alignment: Alignment.topRight,\n overflow: Overflow.clip,\n children: [yellowBox, redBox, greenBox, cyanBox],\n ),\n );\n }\n}"},{"id":null,"widgetId":97,"name":"Stack和Positioned结合使用","priority":2,"subtitle":" \nPositioned组件只能用与Stack中,可以指定左上右下的距离对某个组件进行位置精确安放。","code":"import 'package:flutter/material.dart';\nclass PositionedStack extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var yellowBox = Container(\n color: Colors.yellow,\n height: 100,\n width: 100,\n );\n\n var redBox = Container(\n color: Colors.red,\n height: 90,\n width: 90,\n );\n\n var greenBox = Container(\n color: Colors.green,\n height: 80,\n width: 80,\n );\n\n var cyanBox = Container(\n color: Colors.cyanAccent,\n height: 70,\n width: 70,\n );\n\n return Container(\n width: 200,\n height: 120,\n color: Colors.grey.withAlpha(33),\n child: Stack(\n children: [yellowBox, redBox, greenBox,\n Positioned(\n child: cyanBox,\n bottom: 10,\n right: 10,\n )\n ],\n ));\n }\n}\n"},{"id":null,"widgetId":101,"name":"RichText包含其他组件","priority":2,"subtitle":" \n使用WidgetSpan来承载普通组件,作为RichText的内容","code":"import 'package:flutter/material.dart';\nclass RichTextWithWidget extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 300,\n height: 60,\n alignment: Alignment.center,\n child: Text('暂不支持Web')\n// RichText(\n// text: TextSpan(\n// text: 'hello ',\n// style: TextStyle(color: Colors.black, fontSize: 18),\n// children: [\n// WidgetSpan(\n// child: Image.asset(\n// 'assets/images/icon_head.png',\n// width: 30,\n// ),\n// alignment: PlaceholderAlignment.baseline,\n// baseline: TextBaseline.ideographic),\n// TextSpan(\n// text: ' , welcome to ',\n// style: TextStyle(color: Colors.blue, fontSize: 18),\n// ),\n// WidgetSpan(\n// child: FlutterLogo(),\n// alignment: PlaceholderAlignment.baseline,\n// baseline: TextBaseline.ideographic),\n// TextSpan(\n// text: ' .\\n',\n// ),\n// TextSpan(\n// text: 'focus me on ',\n// style: TextStyle(color: Colors.orange, fontSize: 16),\n// ),\n// TextSpan(\n// text: 'https://github.com/toly1994328',\n// style: TextStyle(\n// color: Colors.blue,\n// fontSize: 18,\n// decoration: TextDecoration.underline),\n// ),\n// TextSpan(\n// text: ' .\\n',\n// ),\n// ],\n// ),\n// ),\n );\n }\n}\n"},{"id":null,"widgetId":94,"name":"Flex垂直方向顺序","priority":4,"subtitle":" \n【verticalDirection】 : 垂直方向顺序 【VerticalDirection】","code":"import 'package:flutter/material.dart';\nclass VerticalDirectionFlex extends StatelessWidget {\n\n final redBox= Container(\n color: Colors.red,\n height: 30,\n width: 40,\n );\n\n final blueBox= Container(\n color: Colors.blue,\n height: 20,\n width: 30,\n );\n\n final greenBox= Container(\n color: Colors.green,\n height: 20,\n width: 20,\n );\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n runSpacing: 5,\n children: VerticalDirection.values\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 160,\n height: 80,\n color: Colors.grey.withAlpha(33),\n child: _buildItem(mode)),\n Text(mode.toString().split('.')[1])\n ]))\n .toList());\n }\n\n _buildItem(mode) => Flex(\n direction: Axis.vertical,\n verticalDirection: mode,\n children: [\n blueBox, redBox, greenBox\n ],\n );\n}\n"},{"id":null,"widgetId":94,"name":"Flex水平方向顺序","priority":5,"subtitle":" \n【textDirection】 : 水平方向顺序 【TextDirection】","code":"import 'package:flutter/material.dart';\nclass TextDirectionFlex extends StatelessWidget {\n\n final redBox= Container(\n color: Colors.red,\n height: 30,\n width: 40,\n );\n\n final blueBox= Container(\n color: Colors.blue,\n height: 20,\n width: 30,\n );\n\n final greenBox= Container(\n color: Colors.green,\n height: 20,\n width: 20,\n );\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n runSpacing: 5,\n children: TextDirection.values\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 160,\n height: 80,\n color: Colors.grey.withAlpha(33),\n child: _buildItem(mode)),\n Text(mode.toString().split('.')[1])\n ]))\n .toList());\n }\n\n _buildItem(mode) => Flex(\n direction: Axis.horizontal,\n textDirection: mode,\n children: [\n blueBox, redBox, greenBox\n ],\n );\n}"},{"id":null,"widgetId":94,"name":"Flex主轴对齐方式","priority":2,"subtitle":" \n【mainAxisAlignment】 : 主轴对齐 【MainAxisAlignment】","code":"import 'package:flutter/material.dart';\nclass MainAxisAlignmentFlex extends StatelessWidget {\n\n final redBox= Container(\n color: Colors.red,\n height: 30,\n width: 40,\n );\n\n final blueBox= Container(\n color: Colors.blue,\n height: 20,\n width: 30,\n );\n\n final greenBox= Container(\n color: Colors.green,\n height: 20,\n width: 20,\n );\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n runSpacing: 5,\n children: MainAxisAlignment.values\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 160,\n height: 80,\n color: Colors.grey.withAlpha(33),\n child: _buildItem(mode)),\n Text(mode.toString().split('.')[1])\n ]))\n .toList());\n }\n\n _buildItem(mode) => Flex(\n direction: Axis.horizontal,\n mainAxisAlignment: mode,\n children: [\n blueBox, redBox, greenBox\n ],\n );\n}"},{"id":null,"widgetId":94,"name":"Flex的排布方向","priority":1,"subtitle":" \n【children】 : 组件列表 【List】\n【direction】 : 方向 【Axis】","code":"import 'package:flutter/material.dart';\nclass DirectionFlex extends StatelessWidget {\n\n final redBox= Container(\n color: Colors.red,\n height: 30,\n width: 40,\n );\n\n final blueBox= Container(\n color: Colors.blue,\n height: 20,\n width: 30,\n );\n\n final greenBox= Container(\n color: Colors.green,\n height: 20,\n width: 20,\n );\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: Axis.values\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 160,\n height: 80,\n color: Colors.grey.withAlpha(33),\n child: _buildItem(mode)),\n Text(mode.toString().split('.')[1])\n ]))\n .toList());\n }\n\n _buildItem(mode) => Flex(\n direction: mode,\n children: [\n blueBox, redBox, greenBox\n ],\n );\n}"},{"id":null,"widgetId":94,"name":"Flex交叉轴对齐方式","priority":3,"subtitle":" \n【crossAxisAlignment】 : 交叉轴对齐 【CrossAxisAlignment】","code":"import 'package:flutter/material.dart';\nclass CrossAxisAlignmentFlex extends StatelessWidget {\n\n final redBox= Container(\n color: Colors.red,\n height: 30,\n width: 40,\n );\n\n final blueBox= Container(\n color: Colors.blue,\n height: 20,\n width: 30,\n );\n\n final greenBox= Container(\n color: Colors.green,\n height: 20,\n width: 20,\n );\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n runSpacing: 5,\n children: CrossAxisAlignment.values\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 160,\n height: 80,\n color: Colors.grey.withAlpha(33),\n child: _buildItem(mode)),\n Text(mode.toString().split('.')[1])\n ]))\n .toList());\n }\n\n _buildItem(mode) => Flex(\n direction: Axis.horizontal,\n crossAxisAlignment: mode,\n textBaseline: TextBaseline.alphabetic,\n children: [\n blueBox, redBox, greenBox\n ],\n );\n}"},{"id":null,"widgetId":99,"name":"Flow圆形排布","priority":1,"subtitle":" \n【children】 : 组件列表 【List】\n【delegate】 : 代理 【FlowDelegate】","code":"import 'dart:math';\nimport 'package:flutter/material.dart';\nclass CircleFlow extends StatelessWidget {\n final data = List.generate(\n 16,\n (index) => index.isEven\n ? \"assets/images/icon_head.png\"\n : \"assets/images/wy_300x200.jpg\");\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 300,\n height: 300,\n alignment: Alignment.center,\n child: Flow(\n delegate: _CircleFlowDelegate(),\n children: data\n .map((e) => CircleAvatar(backgroundImage: AssetImage(e)))\n .toList(),\n ),\n );\n }\n}\n\nclass _CircleFlowDelegate extends FlowDelegate {\n @override //绘制孩子的方法\n void paintChildren(FlowPaintingContext context) {\n double radius = context.size.shortestSide / 2;\n print(context.getChildSize(0));\n var count = context.childCount;\n var perRad = 2 * pi / count;\n for (int i = 0; i < count; i++) {\n var cSizeX = context.getChildSize(i).width / 2;\n var cSizeY = context.getChildSize(i).height / 2;\n\n var offsetX = (radius - cSizeX) * cos(i * perRad) + radius;\n var offsetY = (radius - cSizeY) * sin(i * perRad) + radius;\n context.paintChild(i,\n transform: Matrix4.translationValues(\n offsetX - cSizeX, offsetY - cSizeY, 0.0));\n }\n }\n\n @override\n bool shouldRepaint(FlowDelegate oldDelegate) {\n return true;\n }\n}\n"},{"id":null,"widgetId":99,"name":"Flow圆形与动画结合","priority":2,"subtitle":" \n通过动画来更改周围组件的位置实现效果","code":"import 'dart:math';\nimport 'package:flutter/material.dart';\nclass BurstFlow extends StatefulWidget {\n static final data = List.generate(\n 16,\n (index) => index.isEven\n ? \"assets/images/icon_head.png\"\n : \"assets/images/wy_300x200.jpg\");\n static final show = Container(\n width: 300,\n height: 300,\n alignment: Alignment.center,\n child: BurstFlow(\n children: data\n .map((e) => CircleAvatar(backgroundImage: AssetImage(e)))\n .toList(),\n menu: CircleAvatar(\n backgroundImage: AssetImage('assets/images/icon_head.png'),\n )));\n\n final List children;\n final Widget menu;\n\n BurstFlow({@required this.children, @required this.menu});\n\n @override\n _BurstFlowState createState() => _BurstFlowState();\n}\n\nclass _BurstFlowState extends State\n with SingleTickerProviderStateMixin {\n AnimationController _controller;\n double _rad = 0.0;\n bool _closed = true;\n\n @override\n void initState() {\n _controller = AnimationController(\n duration: Duration(milliseconds: 1000), vsync: this)\n ..addListener(() => setState(\n () => _rad = (_closed ? (_controller.value) : 1 - _controller.value)))\n ..addStatusListener((status) {\n if (status == AnimationStatus.completed) {\n _closed = !_closed;\n }\n });\n super.initState();\n }\n\n @override\n void dispose() {\n _controller.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return Flow(\n delegate: _BurstFlowDelegate(_rad),\n children: [\n ...widget.children,\n InkWell(\n onTap: () {\n _controller.reset();\n _controller.forward();\n },\n child: widget.menu)\n ],\n );\n }\n}\n\nclass _BurstFlowDelegate extends FlowDelegate {\n final double rad;\n\n _BurstFlowDelegate(this.rad);\n\n @override //绘制孩子的方法\n void paintChildren(FlowPaintingContext context) {\n double radius = context.size.shortestSide / 2;\n var count = context.childCount - 1;\n var perRad = 2 * pi / count;\n for (int i = 0; i < count; i++) {\n print(i);\n var cSizeX = context.getChildSize(i).width / 2;\n var cSizeY = context.getChildSize(i).height / 2;\n var offsetX = rad * (radius - cSizeX) * cos(i * perRad) + radius;\n var offsetY = rad * (radius - cSizeY) * sin(i * perRad) + radius;\n context.paintChild(i,\n transform: Matrix4.translationValues(\n offsetX - cSizeX, offsetY - cSizeY, 0.0));\n }\n context.paintChild(context.childCount - 1,\n transform: Matrix4.translationValues(\n radius - context.getChildSize(context.childCount - 1).width / 2,\n radius - context.getChildSize(context.childCount - 1).height / 2,\n 0.0));\n }\n\n @override\n bool shouldRepaint(FlowDelegate oldDelegate) {\n return true;\n }\n}\n"},{"id":null,"widgetId":179,"name":"ListWheelScrollView基本使用","priority":1,"subtitle":" \n【children】 : 子组件列表 【List】\n【perspective】 : 透视度 【double】\n【itemExtent】 : item高 【EdgeInsets】\n【onSelectedItemChanged】 : 选中回调 【ValueChanged 】","code":"import 'package:flutter/material.dart';\nclass CustomListWheelScrollView extends StatefulWidget {\n @override\n _CustomListWheelScrollViewState createState() => _CustomListWheelScrollViewState();\n}\n\nclass _CustomListWheelScrollViewState extends State {\n var data = [\n Colors.orange[50],\n Colors.orange[100],\n Colors.orange[200],\n Colors.orange[300],\n Colors.orange[400],\n Colors.orange[500],\n Colors.orange[600],\n Colors.orange[700],\n Colors.orange[800],\n Colors.orange[900],\n ];\n\n Color _color = Colors.blue;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildCircle(),\n Container(\n height: 150,\n width: 300,\n child: ListWheelScrollView(\n perspective: 0.006,\n itemExtent: 50,\n onSelectedItemChanged: (index){\n print('onSelectedItemChanged:$index');\n setState(() => _color=data[index]);\n },\n children: data.map((color) => _buildItem(color)).toList(),\n ),\n ),\n ],\n );\n }\n\n Widget _buildCircle() => Container(\n margin: EdgeInsets.only(bottom: 5),\n width: 30,\n height: 30,\n decoration: BoxDecoration(\n color: _color,\n shape: BoxShape.circle\n ),\n );\n\n Widget _buildItem(Color color) {\n return Container(\n key: ValueKey(color) ,\n alignment: Alignment.center,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2)\n ]),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":104,"name":"DragTarget基本使用","priority":1,"subtitle":" \n【builder】 : 组件构造器 【DragTargetBuilder】\n【onWillAccept】 : 拖入时 【Function(T)】\n【onAccept】 : 拖拽成功 【Function(T)】\n【onLeave】 : 拖入再脱出 【Function(T)】","code":"import 'package:flutter/material.dart';\nclass CustomDragTarget extends StatefulWidget {\n @override\n _CustomDragTargetState createState() => _CustomDragTargetState();\n}\n\nclass _CustomDragTargetState extends State {\n Color _color = Colors.grey;\n String _info = 'DragTarget';\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n Wrap(\n children: _buildColors(),\n spacing: 10,\n ),\n SizedBox(height: 20,),\n _buildDragTarget()\n ],\n ),\n );\n }\n\n List _buildColors() {\n var colors = [\n Colors.red,\n Colors.yellow,\n Colors.blue,\n Colors.green,\n Colors.orange,\n Colors.purple,\n Colors.cyanAccent\n ];\n return colors\n .map(\n (e) => Draggable(\n child: Container(\n width: 30,\n height: 30,\n alignment: Alignment.center,\n child: Text(\n colors.indexOf(e).toString(),\n style: TextStyle(\n color: Colors.white, fontWeight: FontWeight.bold),\n ),\n decoration: BoxDecoration(color: e, shape: BoxShape.circle),\n ),\n data: e,\n feedback: Container(\n width: 25,\n height: 25,\n decoration: BoxDecoration(color: e, shape: BoxShape.circle),\n )),\n )\n .toList();\n }\n\n Widget _buildDragTarget() {\n return DragTarget(\n onLeave: (data) => setState(() => _info='onLeave'),\n onAccept: (data) => setState(() {\n _info='onAccept';\n _color = data;\n }),\n onWillAccept: (data) {\n setState(() {\n _info='onWillAccept';\n });\n print(\"onWillAccept: data = $data \");\n return data != null;\n },\n builder: (context, candidateData, rejectedData) => Container(\n width: 150.0,\n height: 50.0,\n color: _color,\n child: Center(\n child: Text(\n _info,\n style: TextStyle(color: Colors.white),\n ),\n )));\n }\n}"},{"id":null,"widgetId":173,"name":"StreamBuilder基本使用","priority":1,"subtitle":" \n【stream】 : 子组件 【Stream】\n【initialData】 : 初始数据 【T】\n【builder】 : 点击事件 【AsyncWidgetBuilder】","code":"import 'dart:async';\nimport 'package:flutter/material.dart';\nclass CustomStreamBuilder extends StatefulWidget {\n @override\n _CustomStreamBuilderState createState() => _CustomStreamBuilderState();\n}\n\nclass _CustomStreamBuilderState extends State {\n CountGenerator _generator = CountGenerator()..increment();\n\n @override\n void dispose() {\n _generator.dispose(); //关闭控制器\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Row(\n mainAxisSize: MainAxisSize.min,\n children: [\n FlatButton(\n color: Colors.blue,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n child: Icon(\n Icons.add,\n color: Colors.white,\n ),\n onPressed: () async {\n await _generator.increment();\n },\n ),\n _buildStreamBuilder(),\n FlatButton(\n color: Colors.blue,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n child: Icon(\n Icons.remove,\n color: Colors.white,\n ),\n onPressed: () async {\n await _generator.minus();\n },\n ),\n ],\n ),\n );\n }\n\n Widget _buildStreamBuilder() => StreamBuilder(\n stream: _generator.state,\n builder: (BuildContext context, AsyncSnapshot snap) {\n print(snap);\n if (snap.connectionState == ConnectionState.done) {\n return Text('Done');\n }\n if (snap.connectionState == ConnectionState.active) {\n return Text(\n snap.data.toString(),\n style: Theme.of(context).textTheme.display1,\n );\n }\n if (snap.connectionState == ConnectionState.waiting) {\n return CircularProgressIndicator();\n }\n if (snap.hasError) {\n return Text('Error');\n }\n return Container();\n });\n}\n\nclass CountGenerator {\n int _count = 0; //计数器数据\n final StreamController _controller = StreamController(); //控制器\n\n Stream get state => _controller.stream; //获取状态流\n int get count => _count; //获取计数器数据\n\n void dispose() {//关闭控制器\n _controller.close();\n }\n\n Future increment() async {//增加记数方法\n _controller.add(++_count);\n }\n\n Future minus() async {//增加记数方法\n _controller.add(--_count);\n }\n}\n"},{"id":null,"widgetId":57,"name":"AppBar基本使用","priority":1,"subtitle":" \n【leading】 : 左侧组件 【Widget】\n【title】 : 中间组件 【Widget】\n【actions】 : 右侧组件 【List】\n【elevation】 : 影深 【double】\n【shape】 : 形状 【ShapeBorder】\n【backgroundColor】 : 影深 【背景色】\n【centerTitle】 : 中间是否居中 【bool】","code":"import 'package:flutter/material.dart';\nimport '../PopupMenuButton/node1_base.dart';\nclass CustomAppBar extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return AppBar(\n title: Text('风雅六社'),\n leading: BackButton(),\n backgroundColor: Colors.amber[500],\n elevation: 2,\n centerTitle: true,\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.only(\n topLeft: Radius.circular(20),\n bottomRight: Radius.circular(20),\n topRight: Radius.circular(5),\n bottomLeft: Radius.circular(5),\n )),\n actions: [\n IconButton(\n icon: Icon(Icons.star),\n tooltip: 'like',\n onPressed: () {\n // do nothing\n }),\n CustomPopupMenuButton()\n ],\n );\n }\n}\n"},{"id":null,"widgetId":57,"name":"AppBar与TabBar、TabBarView联用","priority":2,"subtitle":" \n【bottom】 : 底部组件 【PreferredSizeWidget】","code":"import 'package:flutter/material.dart';\nimport '../PopupMenuButton/node1_base.dart';\nclass TabAppBar extends StatefulWidget {\n @override\n _TabAppBarState createState() => _TabAppBarState();\n}\n\nclass _TabAppBarState extends State\n with SingleTickerProviderStateMixin {\n final tabs = ['风画庭', '雨韵舍', '雷鸣殿', '电疾堂', '霜寒阁', '雪月楼'];\n TabController _tabController;\n\n @override\n void initState() {\n super.initState();\n _tabController = TabController(vsync: this, length: tabs.length);\n }\n\n @override\n void dispose() {\n _tabController.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Container(\n height: 180,\n decoration: BoxDecoration(\n image: DecorationImage(\n image: AssetImage(\n \"assets/images/sabar.jpg\",\n ),\n fit: BoxFit.cover)),\n child: _buildAppBar(),\n ),\n Container(\n height: 150, color: Color(0xff916BF0), child: _buildTableBarView())\n ],\n );\n }\n\n Widget _buildAppBar() => AppBar(\n title: Text('风雅六社'),\n elevation: 1,\n leading: BackButton(),\n backgroundColor: Colors.amber[500].withAlpha(33),\n centerTitle: true,\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.only(\n topLeft: Radius.circular(20),\n topRight: Radius.circular(20),\n )),\n actions: [\n IconButton(\n icon: Icon(Icons.star),\n tooltip: 'like',\n onPressed: () {\n // do nothing\n }),\n CustomPopupMenuButton()\n ],\n bottom: TabBar(\n isScrollable: true,\n controller: _tabController,\n indicatorColor: Colors.orangeAccent,\n tabs: tabs.map((e) => Tab(text: e)).toList(),\n ),\n );\n\n Widget _buildTableBarView() => TabBarView(\n controller: _tabController,\n children: tabs\n .map((e) => Center(\n child: Text(\n e,\n style: TextStyle(color: Colors.white, fontSize: 20),\n )))\n .toList());\n}\n"},{"id":null,"widgetId":152,"name":"Ink基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【padding】 : 内边距 【EdgeInsetsGeometry】\n【decoration】 : 装饰 【Decoration】\n【width】 : 宽 【double】\n【height】 : 高 【double】\n【color】 : 颜色 【Color】","code":"import 'package:flutter/material.dart';\nclass CustomInk extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Material(\n color: Colors.orangeAccent,\n child: Padding(\n padding: const EdgeInsets.all(8.0),\n child: Center(\n child: Ink(\n padding: EdgeInsets.all(10),\n decoration: BoxDecoration(\n color: Colors.yellow,\n borderRadius: BorderRadius.all(Radius.circular(20))\n ),\n\n width: 200.0,\n height: 100.0,\n child: InkWell(\n onTap: () {\n },\n child: Center(\n child: Text('Hello'),\n )),\n ),\n ),\n ),\n );\n }\n}\n\n"},{"id":null,"widgetId":152,"name":"Ink.image图片水波纹","priority":2,"subtitle":" \n 其中属性与Image组件一致,详见Image组件","code":"import 'package:flutter/material.dart';\nclass InkImage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Material(\n color: Colors.grey[800],\n child: Center(\n child: Ink.image(\n image: AssetImage('assets/images/sabar.jpg'),\n fit: BoxFit.cover,\n width: 300.0,\n height: 200.0,\n child: InkWell(\n onTap: () {},\n child: Align(\n alignment: Alignment.topLeft,\n child: Padding(\n padding: const EdgeInsets.all(10.0),\n child: Text('Chaos',\n style: TextStyle(\n fontWeight: FontWeight.w900, color: Colors.black)),\n ),\n )),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":198,"name":"Form基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【onChanged】 : 表单变化回调 【VoidCallback】\n【onWillPop】 : 返回回调 【WillPopCallback】","code":"import 'package:flutter/material.dart';\nclass CustomForm extends StatefulWidget {\n @override\n _CustomFormState createState() => _CustomFormState();\n}\n\nclass _CustomFormState extends State {\n GlobalKey _formKey = GlobalKey();\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Form(\n onWillPop: () => _willPop(context),\n key: _formKey,\n onChanged: () {\n print('Form---onChanged');\n },\n child:\n Stack(\n alignment: Alignment.centerRight,\n children: [\n Container(\n width: 350,\n child: UnconstrainedBox(\n child: Container(\n width: 200,\n height: 70,\n child: TextFormField(\n style: TextStyle(textBaseline: TextBaseline.alphabetic),\n decoration: InputDecoration(\n border: OutlineInputBorder(),\n labelText: 'username',\n ),\n validator: _validateUsername,\n ),\n ),\n ),\n ),\n Positioned(\n top: 0, right: 0, child: _buildSubmitButton(context)),\n ],\n ),\n ),\n );\n }\n\n String _validateUsername(value) {\n if (value.isEmpty) {\n return '用户名不能为空';\n }\n return null;\n }\n\n RaisedButton _buildSubmitButton(BuildContext context) {\n return RaisedButton(\n color: Colors.blue,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: _onSubmit,\n child: Icon(\n Icons.check,\n color: Colors.white,\n ),\n );\n }\n\n _onSubmit(){\n if (_formKey.currentState.validate()) {\n FocusScope.of(context).requestFocus(FocusNode());\n Navigator.of(context).pop();\n }\n }\n\n Future _willPop(context) async {\n return await showDialog(\n context: context,\n builder: (context) => AlertDialog(\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n title: Text('提示'),\n content: Text('你确定要离开此页吗?'),\n actions: [\n FlatButton(\n onPressed: () => Navigator.of(context).pop(true),\n child: Text('确定'),\n ),\n FlatButton(\n onPressed: () => Navigator.of(context).pop(false),\n child: Text('取消'),\n ),\n ],\n ),\n ) ??\n false;\n }\n}\n"},{"id":null,"widgetId":50,"name":"Tooltip基本使用","priority":1,"subtitle":" \n【preferBelow】 : 是否首选下方 【bool】\n【padding】 : 内边距 【EdgeInsetsGeometry】\n【margin】 : 外边距 【EdgeInsetsGeometry】\n【message】 : 消息内容 【String】\n【showDuration】 : 展示时间 【Duration】\n【waitDuration】 : 悬浮出现时间 【Duration】\n【child】 : 孩子 【Widget】","code":"import 'package:flutter/material.dart';\nclass CustomTooltip extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Tooltip(\n preferBelow: true,\n padding: EdgeInsets.all(5),\n margin: EdgeInsets.all(5),\n message: \"天王盖地虎\",\n showDuration: Duration(seconds: 3),\n waitDuration: Duration(milliseconds: 200),\n child: Icon(Icons.info_outline),\n );\n }\n}\n"},{"id":null,"widgetId":50,"name":"Tooltip的装饰","priority":2,"subtitle":" \n【decoration】 : 装饰对象 【Decoration】\n【textStyle】 : 文字样式 【double】","code":"import 'package:flutter/material.dart';\nclass DecorationTooltip extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Tooltip(\n preferBelow: false,\n padding: EdgeInsets.all(5),\n margin: EdgeInsets.all(2),\n message: \"宝塔镇河妖\",\n textStyle: TextStyle(\n color: Colors.red,\n shadows: [Shadow(color: Colors.white,\n offset: Offset(1, 1))]),\n decoration: BoxDecoration(boxShadow: [\n BoxShadow(\n color: Colors.orangeAccent,\n offset: Offset(1, 1), blurRadius: 8)\n ]),\n child: Icon(Icons.info_outline));\n }\n}\n"},{"id":null,"widgetId":175,"name":"RawMaterialButton高亮和形状","priority":2,"subtitle":" \n【highlightElevation】 : 高亮影深 【double】\n【shape】 : 形状 【ShapeBorder】","code":"import 'package:flutter/material.dart';\nclass ShapeRawMaterialButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Wrap(\n spacing: 20,\n children: [\n RawMaterialButton(\n elevation: 2,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n fillColor: Colors.green,\n splashColor: Colors.orange,\n textStyle: TextStyle(color: Colors.white),\n onLongPress: ()=>print('onLongPress'),\n child: Icon(Icons.remove),\n onPressed: ()=>print('onPressed'),\n ),\n RawMaterialButton(\n shape:RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(15))),\n elevation: 0,\n highlightElevation: 0,\n fillColor: Colors.blue,\n splashColor: Colors.orange,\n textStyle: TextStyle(color: Colors.white),\n onLongPress: ()=>print('onLongPress'),\n child: Text('Push'),\n onPressed: ()=>print('onPressed'),\n ),\n RawMaterialButton(\n elevation: 2,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n fillColor: Colors.red,\n splashColor: Colors.orange,\n textStyle: TextStyle(color: Colors.white),\n onLongPress: ()=>print('onLongPress'),\n child: Icon(Icons.add),\n onPressed: ()=>print('onPressed'),\n ),\n\n ],\n ),\n );\n }\n}"},{"id":null,"widgetId":49,"name":"RefreshIndicator基本使用","priority":1,"subtitle":" \n【child】 : 孩子(可滑动) 【Widget】\n【displacement】 : 指示器悬浮高度 【double】\n【color】 : 指示器颜色 【Color】,","code":"import 'package:flutter/material.dart';\nclass CustomRefreshIndicator extends StatefulWidget {\n @override\n _CustomRefreshIndicatorState createState() => _CustomRefreshIndicatorState();\n}\n\nclass _CustomRefreshIndicatorState extends State {\n int _count = 0;\n\n @override\n Widget build(BuildContext context) {\n\n return Container(\n height: 200,\n width: 200,\n child: RefreshIndicator(\n onRefresh: _increment,\n displacement: 20,\n color: Colors.orange,\n backgroundColor: Colors.white,\n child: SingleChildScrollView(\n child: Container(\n alignment: Alignment.center,\n width: 200,\n height: 300,\n color: Colors.blue,\n child: Text('$_count',style: TextStyle(color: Colors.white,fontSize: 40)),\n ),\n ),\n ),\n );\n }\n\n Future _increment() async {\n await Future.delayed(Duration(seconds: 2));\n setState(() {\n _count++;\n });\n }\n}\n"},{"id":null,"widgetId":175,"name":"RawMaterialButton基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【elevation】 : 影深 【double】\n【fillColor】 : 填充色 【Color】\n【splashColor】 : 水波纹色 【Color】\n【textStyle】 : 文字样式 【TextStyle】\n【onLongPress】 : 长按事件 【Function()】\n【onPressed】 : 点击事件 【Function()】","code":"import 'package:flutter/material.dart';\nclass CustomRawMaterialButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Wrap(\n spacing: 20,\n children: [\n RawMaterialButton(\n elevation: 2,\n fillColor: Colors.green,\n splashColor: Colors.orange,\n textStyle: TextStyle(color: Colors.white),\n onLongPress: ()=>print('onLongPress'),\n child: Icon(Icons.remove),\n onPressed: ()=>print('onPressed'),\n ),\n RawMaterialButton(\n elevation: 2,\n fillColor: Colors.blue,\n splashColor: Colors.orange,\n textStyle: TextStyle(color: Colors.white),\n onLongPress: ()=>print('onLongPress'),\n child: Text('Push'),\n onPressed: ()=>print('onPressed'),\n ),\n RawMaterialButton(\n elevation: 2,\n fillColor: Colors.red,\n splashColor: Colors.orange,\n textStyle: TextStyle(color: Colors.white),\n onLongPress: ()=>print('onLongPress'),\n child: Icon(Icons.add),\n onPressed: ()=>print('onPressed'),\n ),\n\n ],\n ),\n );\n }\n}"},{"id":null,"widgetId":112,"name":"SlideTransition基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【textDirection】 : x轴方向 【TextDirection】\n【position】 : 动画 【Animation】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomSlideTransition extends StatefulWidget {\n @override\n _CustomSlideTransitionState createState() => _CustomSlideTransitionState();\n}\n\nclass _CustomSlideTransitionState extends State\n with SingleTickerProviderStateMixin {\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 1));\n _ctrl.forward();\n super.initState();\n }\n\n @override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Container(\n width: MediaQuery.of(context).size.width,\n color: Colors.grey.withAlpha(33),\n height: 100,\n child: SlideTransition(\n textDirection: TextDirection.ltr,\n position: Tween(\n begin: Offset.zero,\n end: Offset(0.2, 0.2),\n ).animate(_ctrl),\n child: Container(\n child: Icon(Icons.android, color: Colors.green, size: 60)),\n ),\n ));\n }\n}\n"},{"id":null,"widgetId":45,"name":"Radio基本使用","priority":1,"subtitle":" \n【value】 : 选钮值 【T】\n【groupValue】 : 当前匹配值 【T】\n【activeColor】 : 激活颜色 【Color】\n【onChanged】 : 改变时回调 【Function(T)】","code":"import 'package:flutter/material.dart';\nclass CustomRadio extends StatefulWidget {\n @override\n _CustomRadioState createState() => _CustomRadioState();\n}\n\nclass _CustomRadioState extends State {\n var data = [1, 2, 3, 4, 5];\n double _value = 1;\n\n @override\n Widget build(BuildContext context) {\n return Row(\n mainAxisSize: MainAxisSize.min,\n children: data\n .map((e) => Radio(\n activeColor: Colors.orangeAccent,\n value: e,\n groupValue: _value,\n onChanged: (v) => setState(() => _value = v)))\n .toList(),\n );\n }\n}\n"},{"id":null,"widgetId":165,"name":"PageView基本使用","priority":1,"subtitle":" \n【children】 : 子组件列表 【List】\n【onPageChanged】 : 点击事件 【ValueChanged】","code":"import 'package:flutter/material.dart';\nclass CustomPageView extends StatelessWidget {\n final data = [\n Colors.green[50],\n Colors.green[100],\n Colors.green[200],\n Colors.green[300],\n Colors.green[400],\n Colors.green[500],\n Colors.green[600],\n Colors.green[700],\n Colors.green[800],\n Colors.green[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 150,\n child: PageView(\n onPageChanged: (position){\n print(position);\n },\n children: data\n .map((color) => Container(\n alignment: Alignment.center,\n width: 90,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white,\n fontSize:24,shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n ),\n );\n }\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":165,"name":"PageView控制器简单实用","priority":3,"subtitle":" \n【controller】 : 页面控制器 【PageController】","code":"import 'package:flutter/material.dart';\nclass CtrlPageView extends StatefulWidget {\n @override\n _CtrlPageViewState createState() => _CtrlPageViewState();\n}\n\nclass _CtrlPageViewState extends State {\n final data = [\n Colors.orange[50],\n Colors.orange[100],\n Colors.orange[200],\n Colors.orange[300],\n Colors.orange[400],\n Colors.orange[500],\n Colors.orange[600],\n Colors.orange[700],\n Colors.orange[800],\n Colors.orange[900],\n ];\n\n PageController _controller;\n\n @override\n void dispose() {\n _controller.dispose();\n super.dispose();\n }\n\n @override\n void initState() {\n super.initState();\n _controller=PageController(\n viewportFraction: 0.8,\n initialPage: (data.length/2).round()\n );\n\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 150,\n child: PageView(\n controller: _controller,\n onPageChanged: (position) {\n print(position);\n },\n children: data\n .map((color) =>\n Container(\n alignment: Alignment.center,\n width: 90,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white,\n fontSize: 24, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":165,"name":"PageView滑动方向","priority":2,"subtitle":" \n【scrollDirection】 : 滑动方向 【Axis】\n【reverse】 : 是否反向 【bool】","code":"import 'package:flutter/material.dart';\nclass DirectionPageView extends StatelessWidget {\n final data = [\n Colors.orange[50],\n Colors.orange[100],\n Colors.orange[200],\n Colors.orange[300],\n Colors.orange[400],\n Colors.orange[500],\n Colors.orange[600],\n Colors.orange[700],\n Colors.orange[800],\n Colors.orange[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 150,\n child: PageView(\n scrollDirection: Axis.vertical,\n reverse: true,\n onPageChanged: (position) {\n print(position);\n },\n children: data\n .map((color) =>\n Container(\n alignment: Alignment.center,\n width: 90,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white,\n fontSize: 24, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":60,"name":"BottomNavigationBar基本使用","priority":1,"subtitle":" \n【currentIndex】 : 当前索引 【int】\n【elevation】 : 影深 【double】\n【type】 : 类型*2 【BottomNavigationBarType】\n【fixedColor】 : type为fix的颜色 【Color】\n【backgroundColor】 : 背景色 【Color】\n【iconSize】 : 图标大小 【double】\n【selectedLabelStyle】 : 选中文字样式 【TextStyle】\n【unselectedLabelStyle】 : 未选中文字样式 【TextStyle】\n【showUnselectedLabels】 : 显示未选中标签 【bool】\n【showSelectedLabels】 : 显示选中标签 【bool】\n【items】 : 条目 【List】\n【onTap】 : 点击事件 【Function(int)】","code":"import 'package:flutter/material.dart';\nclass CustomBottomNavigationBar extends StatefulWidget {\n @override\n _CustomBottomNavigationBarState createState() =>\n _CustomBottomNavigationBarState();\n}\n\nclass _CustomBottomNavigationBarState extends State {\n var _position = 0;\n BottomNavigationBarType _type = BottomNavigationBarType.shifting;\n final iconsMap = {\n //底栏图标\n \"图鉴\": Icons.home, \"动态\": Icons.toys,\n \"喜欢\": Icons.favorite, \"手册\": Icons.class_,\n \"我的\": Icons.account_circle,\n };\n final _colors = [\n Colors.red,\n Colors.yellow,\n Colors.blue,\n Colors.green,\n Colors.purple,\n ];\n\n @override\n Widget build(BuildContext context) {\n return Column(\n crossAxisAlignment: CrossAxisAlignment.end,\n children: [\n _buildOp(),\n _buildBottomNavigationBar(),\n ],\n );\n }\n\n bool get isShifting => _type == BottomNavigationBarType.shifting;\n\n BottomNavigationBar _buildBottomNavigationBar() {\n return BottomNavigationBar(\n onTap: (position) => setState(() => _position = position),\n currentIndex: _position,\n elevation: 1,\n type: _type,\n fixedColor: isShifting ? Colors.white : _colors[_position],\n backgroundColor: Colors.white,\n iconSize: 25,\n selectedLabelStyle: TextStyle(fontWeight: FontWeight.bold),\n showUnselectedLabels: false,\n showSelectedLabels: true,\n items: iconsMap.keys\n .map((key) => BottomNavigationBarItem(\n title: Text(\n key,\n ),\n icon: Icon(iconsMap[key]),\n backgroundColor: _colors[_position]))\n .toList(),\n );\n }\n\n Widget _buildOp() {\n return Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [\n Text(\n _type.toString(),\n style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue),\n ),\n Switch(\n value: _type == BottomNavigationBarType.shifting,\n onChanged: (b) {\n setState(() => _type = b\n ? BottomNavigationBarType.shifting\n : BottomNavigationBarType.fixed);\n }),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":60,"name":"可结合PageView进行切页","priority":2,"subtitle":" \n在onTap时进行使用控制器进行切页","code":"import 'package:flutter/material.dart';\nclass BottomNavigationBarWithPageView extends StatefulWidget {\n @override\n _BottomNavigationBarWithPageViewState createState() =>\n _BottomNavigationBarWithPageViewState();\n}\n\nclass _BottomNavigationBarWithPageViewState\n extends State {\n var _position = 0;\n final iconsMap = {\n //底栏图标\n \"图鉴\": Icons.home, \"动态\": Icons.toys,\n \"喜欢\": Icons.favorite, \"手册\": Icons.class_,\n \"我的\": Icons.account_circle,\n };\n final _colors = [\n Colors.red,\n Colors.yellow,\n Colors.blue,\n Colors.green,\n Colors.purple,\n ];\n PageController _controller; //页面控制器,初始0\n\n @override\n void initState() {\n _controller = PageController(\n initialPage: _position,\n );\n super.initState();\n }\n\n @override\n void dispose() {\n _controller.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n Container(\n color: Colors.orange.withAlpha(88),\n width: MediaQuery.of(context).size.width,\n height: 150,\n child: PageView(\n controller: _controller,\n children: iconsMap.keys\n .map((e) => Center(\n child: Text(\n e,\n style: TextStyle(color: Colors.white, fontSize: 20),\n ),\n ))\n .toList(),\n ),\n ),\n _buildBottomNavigationBar()\n ],\n ),\n );\n }\n\n BottomNavigationBar _buildBottomNavigationBar() {\n return BottomNavigationBar(\n onTap: (position) {\n _controller.jumpToPage(position);\n setState(() => _position = position);\n },\n currentIndex: _position,\n elevation: 1,\n type: BottomNavigationBarType.shifting,\n fixedColor: Colors.white,\n iconSize: 25,\n selectedLabelStyle: TextStyle(fontWeight: FontWeight.bold),\n showUnselectedLabels: false,\n showSelectedLabels: true,\n items: iconsMap.keys\n .map((key) => BottomNavigationBarItem(\n title: Text(\n key,\n ),\n icon: Icon(iconsMap[key]),\n backgroundColor: _colors[_position]))\n .toList(),\n );\n }\n}\n"},{"id":null,"widgetId":59,"name":"TabBarView需要与TabBar联用","priority":1,"subtitle":" \n【controller】 : 控制器 【TabController】\n【children】 : 孩子们 【指示器颜色】\n【physics】 : 表现 【ScrollPhysics】","code":"import 'package:flutter/material.dart';\nclass CustomTabBarView extends StatefulWidget {\n @override\n _CustomTabBarViewState createState() => _CustomTabBarViewState();\n}\n\nclass _CustomTabBarViewState extends State with SingleTickerProviderStateMixin {\n final tabs = ['风画庭', '雨韵舍', '雷鸣殿', '电疾堂', '霜寒阁', '雪月楼'];\n TabController _tabController;\n\n @override\n void initState() {\n super.initState();\n _tabController = TabController(vsync: this, length: tabs.length);\n }\n\n @override\n void dispose() {\n _tabController.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n _buildTabBar(),\n Container(\n color: Colors.purple,\n width: MediaQuery.of(context).size.width,\n height: 200,\n child: _buildTableBarView())\n ],\n ),\n );\n }\n\n Widget _buildTabBar() => TabBar(\n onTap: (tab) => print(tab),\n labelStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),\n unselectedLabelStyle: TextStyle(fontSize: 16),\n isScrollable: true,\n controller: _tabController,\n labelColor: Colors.blue,\n indicatorWeight: 3,\n indicatorPadding: EdgeInsets.symmetric(horizontal: 10),\n unselectedLabelColor: Colors.grey,\n indicatorColor: Colors.orangeAccent,\n tabs: tabs.map((e) => Tab(text: e)).toList(),\n );\n\n Widget _buildTableBarView() => TabBarView(\n controller: _tabController,\n children: tabs.map((e) => Center(\n child: Text(e, style: TextStyle(color: Colors.white, fontSize: 20),\n ))).toList());\n}\n"},{"id":null,"widgetId":144,"name":"CupertinoContextMenuAction基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【isDefaultAction】 : 是否默认选中 【bool】\n【trailingIcon】 : 尾部 【bool】\n【onPressed】 : 点击事件 【Function()】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoContextMenuAction extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Container(\n width: 200,\n margin: EdgeInsets.all(5),\n child: CupertinoContextMenuAction(\n trailingIcon: CupertinoIcons.settings,\n isDefaultAction: true,\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'),\n child: Text('张风捷特烈')),\n ),\n Container(\n width: 200,\n margin: EdgeInsets.all(5),\n child: CupertinoContextMenuAction(\n trailingIcon: CupertinoIcons.home,\n isDefaultAction: false,\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'),\n child: Text('百里·巫缨')),\n ),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":91,"name":"ScaleTransition基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【scale】 : 动画 【Animation】","code":"import 'package:flutter/material.dart';\nclass CustomScaleTransition extends StatefulWidget {\n @override\n _CustomScaleTransitionState createState() => _CustomScaleTransitionState();\n}\n\nclass _CustomScaleTransitionState extends State\n with SingleTickerProviderStateMixin {\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 2));\n _ctrl.forward();\n super.initState();\n }\n\n @override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Container(\n color: Colors.grey.withAlpha(22),\n width: 100,\n height: 100,\n child: ScaleTransition(\n scale: CurvedAnimation(parent: _ctrl, curve: Curves.linear),\n child: Icon(Icons.android, color: Colors.green, size: 60),\n ),\n ));\n }\n}\n"},{"id":null,"widgetId":53,"name":"SelectableText对齐属性","priority":2,"subtitle":" \n【textAlign】 : 对齐方式*6 【textAlign】\n【textDirection】 : 文字方向*2 【TextDirection】","code":"import 'package:flutter/material.dart';\nclass AlignSelectableText extends StatefulWidget {\n @override\n _AlignSelectableTextState createState() => _AlignSelectableTextState();\n}\n\nclass _AlignSelectableTextState extends State {\n final text =\n \"The [SelectableText] widget displays a string of text with a single style.\"\n \"The string might break across multiple lines or might all be displayed on\"\n \"the same line depending on the layout constraints.\";\n var _textAlign = TextAlign.left;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildSelector(),\n SelectableText(\n text,\n style: TextStyle(fontSize: 18, color: Colors.red),\n cursorColor: Colors.green,\n cursorRadius: Radius.circular(3),\n cursorWidth: 5,\n showCursor: true,\n textAlign: _textAlign,\n textDirection: TextDirection.ltr,\n\n autofocus: false,\n ),\n ],\n );\n }\n\n Widget _buildSelector() {\n return Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [\n Text(\n \"textAlign属性选择:\",\n style: TextStyle(\n fontSize: 16, color: Colors.blue, fontWeight: FontWeight.bold),\n ),\n DropdownButton(\n underline: Container(),\n value: _textAlign,\n items: TextAlign.values\n .map((e) => DropdownMenuItem(\n value: e,\n child: Text(e.toString()),\n ))\n .toList(),\n onChanged: (e) {\n setState(() {\n _textAlign = e;\n });\n }),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":53,"name":"SelectableText基本使用","priority":1,"subtitle":" \n【入参】 : 显示文字 【String】\n【style】 : 文字样式 【TextStyle】\n【cursorRadius】 : 光标半径 【Radius】\n【cursorColor】 : 光标颜色 【Color】\n【cursorWidth】 : 光标宽度 【double】\n【showCursor】 : 是否显示光标 【bool】\n【autofocus】 : 自动聚焦 【bool】","code":"import 'package:flutter/material.dart';\nclass CustomSelectableText extends StatelessWidget {\n final text = \" 始臣之解牛之时,所见无非牛者。三年之后,未尝见全牛也。方今之时,\"\n \"臣以神遇而不以目视,官知止而神欲行。依乎天理,批大郤,导大窾,因其固然,\"\n \"技经肯綮之未尝,而况大軱乎!良庖岁更刀,割也;族庖月更刀,折也。\"\n \"今臣之刀十九年矣,所解数千牛矣,而刀刃若新发于硎。彼节者有间,而刀刃者无厚;\"\n \"以无厚入有间,恢恢乎其于游刃必有余地矣,是以十九年而刀刃若新发于硎。\"\n \"虽然,每至于族,吾见其难为,怵然为戒,视为止,行为迟。动刀甚微,謋然已解,如土委地。\"\n \"提刀而立,为之四顾,为之踌躇满志,善刀而藏之.\";\n\n @override\n Widget build(BuildContext context) {\n return SelectableText(\n text,\n style: TextStyle(fontSize: 18, color: Colors.orange),\n cursorColor: Colors.green,\n cursorRadius: Radius.circular(3),\n cursorWidth: 5,\n showCursor: true,\n autofocus: false,\n );\n }\n}\n"},{"id":null,"widgetId":137,"name":"CupertinoDatePicker基本使用","priority":1,"subtitle":" \n【initialDateTime】 : 初始日期 【DateTime】\n【minimumYear】 : 最小年份 【int】\n【maximumYear】 : 最大年份 【int】\n【onDateTimeChanged】 : 点击回调 【Function(DateTime)】\n【minuteInterval】 : 分钟间隔 【int】\n【use24hFormat】 : 是否是24小时制 【bool】\n【backgroundColor】 : 背景色 【Color】\n【mode】 : 模式*3 【CupertinoDatePickerMode】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoDatePicker extends StatefulWidget {\n @override\n _CustomCupertinoDatePickerState createState() =>\n _CustomCupertinoDatePickerState();\n}\n\nclass _CustomCupertinoDatePickerState extends State {\n DateTime _date = DateTime.now();\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Text(\n '当前日期:${_date.toIso8601String()}',\n style: TextStyle(color: Colors.grey, fontSize: 16),\n ),\n _buildInfoTitle('CupertinoDatePickerMode.dateAndTime'),\n buildPicker(CupertinoDatePickerMode.dateAndTime),\n _buildInfoTitle('CupertinoDatePickerMode.date'),\n buildPicker(CupertinoDatePickerMode.date),\n _buildInfoTitle('CupertinoDatePickerMode.time'),\n buildPicker(CupertinoDatePickerMode.time),\n ],\n );\n }\n\n Container buildPicker(CupertinoDatePickerMode mode) {\n return Container(\n margin: EdgeInsets.all(10),\n height: 150,\n child: CupertinoDatePicker(\n mode: mode,\n initialDateTime: DateTime.now(),\n// maximumDate: DateTime(2018,8,8),\n// minimumDate: DateTime(2030,8,8),\n minimumYear: 2018,\n maximumYear: 2030,\n use24hFormat: false,\n minuteInterval: 1,\n backgroundColor: CupertinoColors.white,\n onDateTimeChanged: (date) {\n print(date);\n setState(() => _date = date);\n },\n ),\n );\n }\n\n Widget _buildInfoTitle(info){\n return Padding(\n padding: const EdgeInsets.only(left: 20,top: 20,bottom: 5),\n child: Text(\n info,\n style: TextStyle(color: Colors.blue, fontSize: 16,fontWeight: FontWeight.bold),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":150,"name":"InkWell其他属性","priority":2,"subtitle":" \n【child】 : 子组件 【Widget】\n【onHighlightChanged】 : 高亮变化回调 【Function(bool)】\n【highlightColor】 : 高亮色 【Color】\n【splashColor】 : 水波纹色 【Color】\n【radius】 : 水波半径 【double】","code":"import 'package:flutter/material.dart';\nclass ColorInkWell extends StatefulWidget {\n @override\n _ColorInkWellState createState() => _ColorInkWellState();\n}\n\nclass _ColorInkWellState extends State {\n var _info = 'Push';\n\n @override\n Widget build(BuildContext context) {\n return InkWell(\n onTap: () => {},\n splashColor: Colors.blueAccent,\n highlightColor: Colors.orange,\n onHighlightChanged: (v) =>\n setState(() => _info = 'onHighlightChanged:$v'),\n radius: 50,\n child: Container(\n alignment: Alignment.center,\n width: 180,\n height: 50,\n child: Text(_info),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":150,"name":"InkWell基本事件","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【onTap】 : 点击事件 【Function()】\n【onDoubleTap】 : 双击事件 【Function()】\n【onTapCancel】 : 点击取消 【Function()】\n【onLongPress】 : 长按事件 【Function()】","code":"import 'package:flutter/material.dart';\nclass CustomInkWell extends StatefulWidget {\n @override\n _CustomInkWellState createState() => _CustomInkWellState();\n}\n\nclass _CustomInkWellState extends State {\n var _info = 'Push';\n\n @override\n Widget build(BuildContext context) {\n return InkWell(\n onTap: () => setState(() => _info = 'onTap'),\n onDoubleTap: () => setState(() => _info = 'onDoubleTap'),\n onLongPress: () => setState(() => _info = 'onLongPress'),\n onTapCancel: () => setState(() => _info = 'onTapCancel'),\n child: Container(\n alignment: Alignment.center,\n width: 120,\n height: 50,\n child: Text(_info),\n ),\n );\n }\n}"},{"id":null,"widgetId":114,"name":"DefaultTextStyleTransition基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【textAlign】 : 文字对齐方式 【TextAlign】\n【softWrap】 : 是否包裹 【bool】\n【maxLines】 : 最大行数 【int】\n【overflow】 : 溢出模式 【TextOverflow】\n【style】 : 动画 【Animation】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomDefaultTextStyleTransition extends StatefulWidget {\n @override\n _CustomDefaultTextStyleTransitionState createState() =>\n _CustomDefaultTextStyleTransitionState();\n}\n\nclass _CustomDefaultTextStyleTransitionState\n extends State\n with SingleTickerProviderStateMixin {\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 1));\n _ctrl.forward();\n super.initState();\n }\n\n @override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Container(\n alignment: Alignment.center,\n width: 300,\n height: 100,\n child: DefaultTextStyleTransition(\n textAlign: TextAlign.start,\n softWrap: true,\n maxLines: 1,\n overflow: TextOverflow.ellipsis,\n style: TextStyleTween(\n begin: TextStyle(color: Colors.blue, fontSize: 50, shadows: [\n Shadow(\n offset: Offset(1, 1), color: Colors.black, blurRadius: 3)\n ]),\n end: TextStyle(color: Colors.white, fontSize: 20, shadows: [\n Shadow(\n offset: Offset(1, 1), color: Colors.purple, blurRadius: 3)\n ])).animate(_ctrl),\n child: Text('张风捷特烈'),\n ),\n ));\n }\n}\n"},{"id":null,"widgetId":176,"name":"Dismissible基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【background】 : 左底 【Widget】\n【secondaryBackground】 : 右底 【Widget】\n【key】 : 键 【Key】\n【confirmDismiss】 : 确认回调 【DismissDirectionCallback】\n【onDismissed】 : 消失回调 【DismissDirectionCallback】,","code":"import 'package:flutter/material.dart';\nclass CustomDismissible extends StatefulWidget {\n @override\n _CustomDismissibleState createState() => _CustomDismissibleState();\n}\n\nclass _CustomDismissibleState extends State {\n var data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: ListView(\n padding: EdgeInsets.symmetric(horizontal: 5),\n children: data.map((color) => _buildItem(color)).toList(),\n ),\n );\n }\n\n Widget _buildItem(Color color) {\n return Dismissible(\n background: Container(\n color: Colors.green,\n alignment: Alignment(-0.9, 0),\n child: Icon(\n Icons.check,\n color: Colors.white,\n ),\n ),\n secondaryBackground: Container(\n alignment: Alignment(0.9, 0),\n child: Icon(\n Icons.close,\n color: Colors.white,\n ),\n color: Colors.red,\n ),\n key: ValueKey(color),\n onDismissed: (d) {\n data.remove(color);\n },\n confirmDismiss: (e) async {\n if (e == DismissDirection.endToStart) {\n return true;\n } else {\n return false;\n }\n },\n child: Container(\n alignment: Alignment.center,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2)\n ]),\n ),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":115,"name":"RelativePositionedTransition基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【size】 : 左和上的偏移量 【Size】\n【rect】 : 动画 【Animation】\n PositionedTransition组件只能在Stack内起作用","code":"import 'package:flutter/material.dart';\nclass CustomRelativePositionedTransition extends StatefulWidget {\n @override\n _CustomRelativePositionedTransitionState createState() =>\n _CustomRelativePositionedTransitionState();\n}\n\nclass _CustomRelativePositionedTransitionState\n extends State\n with SingleTickerProviderStateMixin {\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 2));\n _ctrl.forward();\n super.initState();\n }\n\n @override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Container(\n color: Colors.grey.withAlpha(33),\n width: 200,\n height: 100,\n child: Stack(\n children: [\n RelativePositionedTransition(\n size: Size(200, 100),\n rect: RectTween(\n begin: Rect.fromLTRB(0, 0, 50, 50),\n end: Rect.fromLTRB(0, 0, 50, 50).translate(100, 50),\n ).animate(_ctrl),\n child: Icon(\n Icons.android,\n color: Colors.green,\n size: 50,\n ),\n )\n ],\n ),\n ));\n }\n}\n"},{"id":null,"widgetId":176,"name":"Dismissible基本使用","priority":2,"subtitle":" \n【direction】 : 方向 【DismissDirection】\n【crossAxisEndOffset】 : 偏移 【double】,","code":"import 'package:flutter/material.dart';\nclass DirectionDismissible extends StatefulWidget {\n @override\n _CustomDirectionDismissibleState createState() => _CustomDirectionDismissibleState();\n}\n\nclass _CustomDirectionDismissibleState extends State {\n var data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: ListView(\n scrollDirection: Axis.horizontal,\n padding: EdgeInsets.symmetric(horizontal: 5),\n children: data.map((color) => _buildItem(color)).toList(),\n ),\n );\n }\n\n Widget _buildItem(Color color) {\n return Dismissible(\n direction: DismissDirection.vertical,\n background: Container(\n color: Colors.green,\n alignment: Alignment( 0,-0.9,),\n child: Icon(\n Icons.check,\n color: Colors.white,\n ),\n ),\n crossAxisEndOffset: 0.5,\n secondaryBackground: Container(\n alignment: Alignment( 0,0.9,),\n child: Icon(\n Icons.close,\n color: Colors.white,\n ),\n color: Colors.red,\n ),\n key: ValueKey(color),\n onDismissed: (d) {\n data.remove(color);\n },\n confirmDismiss: (e) async {\n print(e);\n if (e == DismissDirection.up) {\n return true;\n } else {\n return false;\n }\n },\n child: Container(\n alignment: Alignment.center,\n width: 80,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2)\n ]),\n ),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":251,"name":"NestedScrollView基本用法","priority":1,"subtitle":" \n【controller】 : 滑动控制器 【ScrollController】\n【scrollDirection】 : 滑动方向 【Axis】\n【reverse】 : 是否反向 【bool】\n【physics】 : 滑顶样式 【ScrollPhysics】\n【dragStartBehavior】 : 开始拖动行为 【DragStartBehavior】\n【headerSliverBuilder】 : *头部构造器 【NestedScrollViewHeaderSliversBuilder】\n【body】 : *内容 【Widget】","code":"import 'package:flutter/material.dart';\nclass NestedScrollViewDemo extends StatelessWidget {\n final _tabs = ['风神传', '封妖志', \"幻将录\", \"永恒传说\"];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: MediaQuery.of(context).size.height - 200,\n child: Scaffold(\n body: DefaultTabController(\n length: _tabs.length,\n child: NestedScrollView(\n headerSliverBuilder:\n (BuildContext context, bool innerBoxIsScrolled) {\n return [\n SliverOverlapAbsorber(\n handle: NestedScrollView.sliverOverlapAbsorberHandleFor(\n context),\n sliver: SliverAppBar(\n title: const Text('旷古奇书'),\n pinned: true,\n elevation: 6, //影深\n expandedHeight: 220.0,\n forceElevated: innerBoxIsScrolled, //为true时展开有阴影\n flexibleSpace: FlexibleSpaceBar(\n background: Image.asset(\n \"assets/images/wy_300x200_filter.jpg\",\n fit: BoxFit.cover,\n ),\n ),\n bottom: TabBar(\n tabs: _tabs\n .map((String name) => Tab(text: name,))\n .toList(),\n ),\n ),\n ),\n ];\n },\n body: _buildTabBarView(),\n ),\n ),\n ));\n }\n\n Widget _buildTabBarView() {\n return TabBarView(\n children: _tabs.map((String name) {\n return SafeArea(\n top: false,\n bottom: false,\n child: Builder(\n builder: (BuildContext context) {\n return CustomScrollView(\n key: PageStorageKey(name),\n slivers: [\n SliverOverlapInjector(\n handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),\n ),\n SliverPadding(\n padding: const EdgeInsets.all(8.0),\n sliver: SliverFixedExtentList(\n itemExtent: 48.0,\n delegate: SliverChildBuilderDelegate(\n (BuildContext context, int index) {\n return ListTile(\n title: Text('《$name》 第 $index章'),\n );\n },\n childCount: 50,\n ),\n ),\n ),\n ],\n );\n },\n ),\n );\n }).toList(),\n );\n }\n}\n"},{"id":null,"widgetId":24,"name":"CupertinoButton点击事件","priority":1,"subtitle":"【color】: 颜色 【Color】\n【pressedOpacity】: 按下时透明度 【double】\n【child】: 子组件 【Widget】\n【padding】: 内边距 【EdgeInsetsGeometry】\n【borderRadius】: 圆角半径 【BorderRadius】\n【onPressed】: 点击事件 【Function】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoButton extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var data = {\n CupertinoColors.activeBlue:4.0,\n Colors.blue:6.0,\n CupertinoColors.activeOrange:8.0,\n };\n return Wrap(\n spacing: 20,\n children:data.keys.map((e)=> CupertinoButton(\n padding: EdgeInsets.zero,\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'),\n color: e,\n pressedOpacity: 0.4,\n borderRadius: BorderRadius.all(Radius.circular(data[e])),\n child: Text(\"iOS\"),\n )).toList()\n );\n }\n}\n\n"},{"id":null,"widgetId":61,"name":"BottomAppBar基本用法","priority":1,"subtitle":" \n【elevation】 : 影深 【double】\n【shape】 : 形状 【NotchedShape】\n【notchMargin】 : 间隔距离 【double】\n【color】 : 颜色 【Color】\n【child】 : 孩子 【Widget】","code":"import 'package:flutter/material.dart';\nclass CustomBottomAppBar extends StatefulWidget {\n @override\n _CustomBottomAppBarState createState() => _CustomBottomAppBarState();\n}\n\nclass _CustomBottomAppBarState extends State {\n var _position = 0;\n var _location = FloatingActionButtonLocation.centerDocked;\n final iconsMap = {\n \"图鉴\": Icons.home,\n \"动态\": Icons.toys,\n \"喜欢\": Icons.favorite,\n \"手册\": Icons.class_,\n };\n var activeColor = Colors.blue.withAlpha(240);\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: 180,\n child: Scaffold(\n backgroundColor: Colors.purple.withAlpha(22),\n floatingActionButton: FloatingActionButton(\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'),\n child: Icon(Icons.add),\n ),\n bottomNavigationBar: _buildBottomAppBar(),\n floatingActionButtonLocation: _location,\n body: _buildContent(),\n ),\n );\n }\n\n Widget _buildBottomAppBar() {\n return BottomAppBar(\n elevation: 1,\n shape: CircularNotchedRectangle(),\n notchMargin: 5,\n color: Colors.red,\n child: Row(\n mainAxisAlignment: MainAxisAlignment.spaceAround,\n children: info.asMap().keys.map((i) => _buildChild(i)).toList()\n ..insertAll(isCenter ? 2 : 4, [SizedBox(width: 30)])),\n );\n }\n\n Container _buildContent() {\n return Container(\n alignment: Alignment.center,\n child: Wrap(\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n Text(\n '当前页索引:$_position',\n style: TextStyle(color: Colors.blue, fontSize: 18),\n ),\n Switch(\n value: isCenter,\n onChanged: (v) {\n setState(() {\n _location = v\n ? FloatingActionButtonLocation.centerDocked\n : FloatingActionButtonLocation.endDocked;\n });\n }),\n ],\n ),\n );\n }\n\n List get info => iconsMap.keys.toList();\n\n bool get isCenter => _location == FloatingActionButtonLocation.centerDocked;\n\n Widget _buildChild(int i) {\n var active = i == _position;\n return Padding(\n padding: const EdgeInsets.all(8.0),\n child: GestureDetector(\n onTap: () => setState(() => _position = i),\n child: Wrap(\n direction: Axis.vertical,\n alignment: WrapAlignment.center,\n children: [\n Icon(\n iconsMap[info[i]],\n color: active ? activeColor : Colors.white,\n size: 30,\n ),\n Text(info[i],\n style: TextStyle(\n color: active ? activeColor : Colors.white, fontSize: 14)),\n ],\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":153,"name":"RawChip点击效果","priority":1,"subtitle":" \n【label】: 中间组件 【Widget】\n【padding】 : 内边距 【EdgeInsetsGeometry】\n【labelPadding】 : label边距 【EdgeInsetsGeometry】\n【shadowColor】: 阴影色 【Color】\n【avatar】: 左侧组件 【Widget】\n【elevation】: 影深 【double】\n【pressElevation】: 点击时影深 【double】\n【onPressed】 : 点击事件 【Function()】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass PressRawChip extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: RawChip(\n padding: EdgeInsets.all(5),\n labelPadding: EdgeInsets.all(3),\n label: Text('张风捷特烈'),\n avatar: Image.asset(\"assets/images/icon_head.png\"),\n elevation: 3,\n pressElevation: 5,\n shadowColor: Colors.orangeAccent,\n onPressed: () => Navigator.of(context).pushNamed('AboutMePage'),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":153,"name":"RawChip选中和删除效果","priority":2,"subtitle":" \n【selected】: 是否选中 【bool】\n【deleteIconColor】: 尾部图标色 【Color】\n【selectedColor】: 选中色 【Color】\n【deleteIcon】: 尾部组件 【Widget】\n【onSelected】: 选中事件 【Function(bool)】\n【onDeleted】 : 尾部事件 【Function()】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass SelectRawChip extends StatefulWidget {\n @override\n _SelectRawChipState createState() => _SelectRawChipState();\n}\n\nclass _SelectRawChipState extends State {\n bool _selected = false;\n @override\n Widget build(BuildContext context) {\n return Container(\n child: RawChip(\n selected: _selected,\n padding: EdgeInsets.all(5),\n labelPadding: EdgeInsets.all(3),\n deleteIconColor: Colors.red,\n selectedColor: Colors.orangeAccent.withAlpha(44),\n label: Text('张风捷特烈'),\n avatar: Image.asset(\"assets/images/icon_head.png\"),\n elevation: 3,\n pressElevation: 5,\n shadowColor: Colors.orangeAccent,\n onSelected: (v)=> setState(() => _selected=v),\n onDeleted: () => Navigator.of(context).pushNamed('AboutMePage'),\n ),\n );\n }\n}"},{"id":null,"widgetId":46,"name":"CircularProgressIndicator基本使用","priority":1,"subtitle":" \n【value】 : 进度 【double】\n【backgroundColor】 : 背景色 【Color】\n【valueColor】 : 进度颜色 【Animation】\n【strokeWidth】 : 线宽 【double】","code":"import 'package:flutter/material.dart';\nclass CustomCircularProgressIndicator extends StatefulWidget {\n @override\n _CustomCircularProgressIndicatorState createState() =>\n _CustomCircularProgressIndicatorState();\n}\n\nclass _CustomCircularProgressIndicatorState\n extends State {\n\n var data = [0.2,0.4,0.6,0.8,null];\n\n @override\n Widget build(BuildContext context) {\n\n return Wrap(\n spacing: 10,\n children:data.map((e)=>Container(\n width: 50,\n height: 50,\n child: CircularProgressIndicator(\n value: e,\n backgroundColor: Colors.grey.withAlpha(33),\n valueColor: AlwaysStoppedAnimation(Colors.orange),\n strokeWidth: 5,\n ),\n )).toList(),\n );\n }\n}\n"},{"id":null,"widgetId":100,"name":"AnimatedCrossFade动画曲线","priority":2,"subtitle":" \n【firstCurve】 : 第一曲线 【Curve】\n【secondCurve】 : 第二曲线 【Curve】\n【sizeCurve】 : 尺寸变化曲线 【CrossFadeState】","code":"import 'package:flutter/material.dart';\nclass CurveAnimatedCrossFade extends StatefulWidget {\n @override\n _CurveAnimatedCrossFadeState createState() => _CurveAnimatedCrossFadeState();\n}\n\nclass _CurveAnimatedCrossFadeState extends State {\n var _crossFadeState = CrossFadeState.showFirst;\n\n bool get isFirst=> _crossFadeState == CrossFadeState.showFirst;\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: [\n Container(\n child: AnimatedCrossFade(\n firstCurve: Curves.easeInCirc,\n secondCurve: Curves.easeInToLinear,\n sizeCurve: Curves.bounceOut,\n firstChild: Container(\n alignment: Alignment.center,\n width: 200,\n height: 80,\n color: Colors.orange ,\n child: FlutterLogo(colors: Colors.blue,size: 50,),\n ),\n secondChild: Container(\n width: 200,\n height: 150,\n alignment: Alignment.center,\n color: Colors.blue,\n child: FlutterLogo(\n textColor: Colors.white,\n colors: Colors.orange,size: 100,style: FlutterLogoStyle.stacked,),\n ),\n duration: Duration(milliseconds: 1000),\n crossFadeState: _crossFadeState,\n ),\n ),\n _buildSwitch(),\n ],\n );\n }\n\n Widget _buildSwitch() => Switch(value: isFirst, onChanged: (v){\n setState(() {\n _crossFadeState= v?CrossFadeState.showFirst:CrossFadeState.showSecond;\n });\n });\n}\n"},{"id":null,"widgetId":47,"name":"LinearProgressIndicator基本使用","priority":1,"subtitle":" \n【value】 : 进度 【double】\n【backgroundColor】 : 背景色 【Color】\n【valueColor】 : 进度颜色 【Animation】\n value为null时会不停循环","code":"import 'package:flutter/material.dart';\nclass CustomLinearProgressIndicator extends StatefulWidget {\n @override\n _CustomLinearProgressIndicatorState createState() =>\n _CustomLinearProgressIndicatorState();\n}\n\nclass _CustomLinearProgressIndicatorState\n extends State {\n var data = [0.2, 0.4, 0.6, 0.8, null];\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n children: data\n .map((e) => Container(\n width: 50,\n height: 3,\n child:LinearProgressIndicator(\n value: e,\n backgroundColor: Colors.grey.withAlpha(33),\n valueColor: AlwaysStoppedAnimation(Colors.orange),\n ),\n ))\n .toList(),\n );\n }\n}\n"},{"id":null,"widgetId":100,"name":"AnimatedCrossFade基本使用","priority":1,"subtitle":" \n【firstChild】 : 第一孩子 【Widget】\n【secondChild】 : 第二孩子 【Widget】\n【crossFadeState】 : 显示第几个 【CrossFadeState】\n【duration】 : 时长 【Duration】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedCrossFade extends StatefulWidget {\n @override\n _CustomAnimatedCrossFadeState createState() =>\n _CustomAnimatedCrossFadeState();\n}\n\nclass _CustomAnimatedCrossFadeState extends State {\n var _crossFadeState = CrossFadeState.showFirst;\n\n bool get isFirst => _crossFadeState == CrossFadeState.showFirst;\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: [\n Container(\n child: AnimatedCrossFade(\n firstChild: Container(\n alignment: Alignment.center,\n width: 200,\n height: 150,\n color: Colors.orange,\n child: FlutterLogo(colors: Colors.blue, size: 100,),\n ),\n secondChild: Container(\n width: 200,\n height: 150,\n alignment: Alignment.center,\n color: Colors.blue,\n child: FlutterLogo(\n textColor: Colors.white,\n colors: Colors.orange,\n size: 100,\n style: FlutterLogoStyle.stacked,),\n ),\n duration: Duration(milliseconds: 600),\n\n crossFadeState: _crossFadeState,\n ),\n ),\n _buildSwitch(),\n ],\n\n );\n }\n\n Widget _buildSwitch() =>\n Switch(value: isFirst, onChanged: (v) {\n setState(() {\n _crossFadeState =\n v ? CrossFadeState.showFirst : CrossFadeState.showSecond;\n });\n });\n}"},{"id":null,"widgetId":65,"name":"MaterialApp基本用法","priority":1,"subtitle":" \n【theme】 : 主题 【ThemeData】\n【title】 : 任务栏标题 【String】\n【onGenerateRoute】 : 路由生成器 【RouteFactory】\n【home】 : 主页 【Widget】","code":"import 'package:flutter/material.dart';\nimport '../../../../app/router.dart';\nimport '../../StatefulWidget/Scaffold/node1_base.dart';\nclass CustomMaterialApp extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: MediaQuery.of(context).size.height - 200,\n child: MaterialApp(\n title: 'Flutter Demo',\n onGenerateRoute: Router.generateRoute,\n theme: ThemeData(\n primarySwatch: Colors.blue,\n ),\n home: CustomScaffold(),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":245,"name":"CupertinoTextField常用样式属性","priority":2,"subtitle":"【style】 : 输入文字样式 【TextStyle】\n【prefix】: 前缀组件 【Widget】\n【prefixMode】: 前缀模式 【OverlayVisibilityMode】\n【suffix】: 后缀组件 【Widget】\n【suffixMode】: 后缀模式 【OverlayVisibilityMode】\n【cursorColor】: 游标颜色 【Color】\n【cursorWidth】: 游标宽度 【double】\n【cursorRadius】: 游标圆角 【Radius】\n【readOnly】: 是否只读 【bool】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CupertinoTextFieldStyle extends StatelessWidget {\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child:\n CupertinoTextField(\n style: TextStyle(color: Colors.blue),\n prefix: Icon(CupertinoIcons.add),\n prefixMode: OverlayVisibilityMode.notEditing,\n suffix: Icon(CupertinoIcons.clear),\n suffixMode: OverlayVisibilityMode.editing,\n cursorColor: Colors.purple,\n cursorWidth: 4,\n cursorRadius: Radius.circular(2),\n readOnly: false,\n placeholder: '输入用户名',\n )\n );\n }\n}\n"},{"id":null,"widgetId":245,"name":"CupertinoTextField基础使用","priority":1,"subtitle":"【placeholder】 : 提示文字 【String】\n【showCursor】 : 是否显示游标 【bool】\n【minLines】 : 最小行数 【int】\n【maxLines】 : 最大行数 【int】\n【padding】 : 内边距 【EdgeInsetsGeometry】\n【onChanged】 : 变化监听 【ValueChanged】\n【onTap】: 点击监听 【GestureTapCallback】\n【onSubmitted】: 提交监听 【ValueChanged】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CupertinoTextFieldDemo extends StatefulWidget {\n @override\n _CupertinoTextFieldDemoState createState() => _CupertinoTextFieldDemoState();\n}\n\nclass _CupertinoTextFieldDemoState extends State {\n var _value = '';\n var _color =Colors.black;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n Text('输入了:$_value',style: TextStyle(color: _color),),\n CupertinoTextField(\n placeholder: 'Input Name',\n showCursor: true,\n minLines: 1,\n maxLines: 4,\n padding: EdgeInsets.all(8),\n onChanged: _onChanged,\n onTap: _onTap,\n onSubmitted: _onSubmitted,\n ),\n ],\n ),\n );\n }\n\n void _onChanged(String value) {\n setState(() {\n _value = value;\n });\n }\n\n void _onTap() {\n print('----_onTap----');\n setState(() {\n _color=Colors.blue;\n });\n }\n\n void _onSubmitted(String value) {\n print('----_onSubmitted:$value}----');\n setState(() {\n _color=Colors.black;\n });\n }\n\n}\n"},{"id":null,"widgetId":124,"name":"AnimatedDefaultTextStyle基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【duration】 : 动画时长 【Duration】\n【onEnd】 : 动画结束回调 【Function()】\n【curve】 : 动画曲线 【Duration】\n【textAlign】 : 文字对齐方式 【TextAlign】\n【softWrap】 : 是否包裹 【bool】\n【maxLines】 : 最大行数 【int】\n【overflow】 : 溢出模式 【TextOverflow】\n【style】 : 文字样式 【TextStyle】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedDefaultTextStyle extends StatefulWidget {\n @override\n _CustomAnimatedDefaultTextStyleState createState() =>\n _CustomAnimatedDefaultTextStyleState();\n}\n\nclass _CustomAnimatedDefaultTextStyleState\n extends State {\n final TextStyle start = TextStyle(color: Colors.blue, fontSize: 50, shadows: [\n Shadow(offset: Offset(1, 1), color: Colors.black, blurRadius: 3)\n ]);\n final TextStyle end = TextStyle(color: Colors.white, fontSize: 20, shadows: [\n Shadow(offset: Offset(1, 1), color: Colors.purple, blurRadius: 3)\n ]);\n\n TextStyle _style;\n\n @override\n void initState() {\n _style = start;\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildSwitch(),\n Container(\n alignment: Alignment.center,\n color: Colors.grey.withAlpha(22),\n width: 300,\n height: 100,\n child: AnimatedDefaultTextStyle(\n textAlign: TextAlign.start,\n softWrap: true,\n maxLines: 1,\n overflow: TextOverflow.ellipsis,\n duration: Duration(seconds: 1),\n curve: Curves.fastOutSlowIn,\n style: _style,\n onEnd: () => print('End'),\n child: Text(\n '张风捷特烈',\n style: TextStyle(color: Colors.white),\n ),\n ),\n ),\n ],\n );\n }\n\n Widget _buildSwitch() => Switch(\n value: _style == end,\n onChanged: (v) {\n setState(() {\n _style = v ? end : start;\n });\n });\n}\n"},{"id":null,"widgetId":231,"name":"InputDecorator基本使用","priority":1,"subtitle":"【decoration】 : 装饰对象 【InputDecoration】\n【textAlign】 : 文字对齐方式 【TextAlign】\n【child】 : 子组件 【Widget】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass InputDecoratorDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Padding(\n padding: const EdgeInsets.all(8.0),\n child: InputDecorator(\n decoration: InputDecoration(),\n child: EditableText(\n controller: TextEditingController(text:'hello'),\n focusNode: FocusNode(),\n style: TextStyle(fontSize: 12,color: Colors.black),\n cursorColor: Colors.blue,\n backgroundCursorColor: Colors.orange,\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":39,"name":"Checkbox的三态","priority":2,"subtitle":" \n【tristate】 : 是否是三态 【double】\n onChanged时,","code":"import 'package:flutter/material.dart';\nclass TristateCheckBok extends StatefulWidget {\n @override\n _TristateCheckBokState createState() => _TristateCheckBokState();\n}\n\nclass _TristateCheckBokState extends State {\n bool _checked = false;\n final colors = [Colors.red, Colors.yellow, Colors.blue, Colors.green];\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n children: colors\n .map((e) =>\n Checkbox(\n value: _checked,\n tristate: true,\n checkColor: Colors.white,\n activeColor: e,\n onChanged: (v) {\n print(v);\n setState(() => _checked = v);\n }))\n .toList(),\n );\n }\n}\n\n"},{"id":null,"widgetId":39,"name":"Checkbox基础用法","priority":1,"subtitle":" \n【value】 : 是否选中 【double】\n【checkColor】: 选中时✔️gou颜色 【Color】\n【activeColor】: 选中时框内颜色 【Color】\n【onChanged】: 状态改变事件 【Function(bool)】,","code":"import 'package:flutter/material.dart';\nclass CustomCheckbox extends StatefulWidget {\n @override\n _CustomCheckboxState createState() => _CustomCheckboxState();\n}\n\nclass _CustomCheckboxState extends State {\n bool _checked = false;\n final colors = [Colors.red, Colors.yellow, Colors.blue, Colors.green];\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n children: colors\n .map((e) =>\n Checkbox(\n value: _checked,\n checkColor: Colors.white,\n activeColor: e,\n onChanged: (v) =>\n setState(() => _checked = v)))\n .toList(),\n );\n }\n}\n\n\n"},{"id":null,"widgetId":62,"name":"CupertinoNavigationBar基本用法","priority":1,"subtitle":" \n【leading】 : 左侧组件 【Widget】\n【middle】 : 中间组件 【Widget】\n【trailing】 : 尾部组件 【Widget】\n【backgroundColor】 : 背景色 【Color】\n【padding】 : 内边距 【EdgeInsetsDirectional】\n【border】 : 边线 【Border】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoNavigationBar extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return CupertinoNavigationBar(\n leading: Icon(\n CupertinoIcons.back,\n size: 25,\n color: Colors.blue,\n ),\n middle: Text(\"风雪雅舍\"),\n trailing: Image.asset(\n \"assets/images/icon_head.png\",\n width: 25.0,\n height: 25.0,\n ),\n backgroundColor: Color(0xfff1f1f1),\n padding: EdgeInsetsDirectional.only(start: 10,end: 20),\n border: Border.all(color: Colors.transparent),\n );\n }\n}\n"},{"id":null,"widgetId":41,"name":"CupertinoSwitch基本使用","priority":1,"subtitle":" \n【value】 : 是否选中 【double】\n【activeColor】 : 激活态颜色 【Color】\n【onChanged】 : 切换回调 【Function(double)】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoSwitch extends StatefulWidget {\n @override\n _CustomCupertinoSwitchState createState() => _CustomCupertinoSwitchState();\n}\n\nclass _CustomCupertinoSwitchState extends State {\n final colors = [Colors.red, Colors.yellow, Colors.blue, Colors.green];\n\n bool _checked = false;\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n children: colors\n .map((e) => CupertinoSwitch(\n value: _checked,\n activeColor: e,\n onChanged: (v) {\n setState(() => _checked = v);\n }))\n .toList(),\n );\n }\n}\n"},{"id":null,"widgetId":54,"name":"TextField基本用法","priority":1,"subtitle":" \n【controller】 : 控制器 【TextEditingController】\n【style】 : 文字样式 【TextStyle】\n【decoration】 : 装饰线 【InputDecoration】\n【onEditingComplete】 : 输入完成事件 【Function()】\n【onSubmitted】 : 提交事件 【Function(String)】\n【onChanged】 : 输入事件 【Function(String)】","code":"import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nclass CustomTextField extends StatefulWidget {\n @override\n _CustomTextFieldState createState() => _CustomTextFieldState();\n}\n\nclass _CustomTextFieldState extends State {\n final FocusNode _focusNode = FocusNode();\n TextEditingController _controller;\n\n void initState() {\n super.initState();\n _controller = TextEditingController();\n }\n\n @override\n void dispose() {\n _controller.dispose();\n _focusNode.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 300,\n child: TextField(\n controller: _controller,\n style: TextStyle(color: Colors.blue),\n decoration: InputDecoration(\n border: OutlineInputBorder(),\n labelText: 'username',\n ),\n onEditingComplete: () {\n print('onEditingComplete');\n },\n onChanged: (v) {\n print('onChanged:' + v);\n },\n onSubmitted: (v) {\n FocusScope.of(context).requestFocus(_focusNode);\n print('onSubmitted:' + v);\n _controller.clear();\n },\n ));\n }\n}\n\n"},{"id":null,"widgetId":54,"name":"TextField行数和cursor","priority":2,"subtitle":" \n【minLines】 : 最小行数 【int】\n【maxLines】 : 最大行数 【int】\n【cursorRadius】 : 光标半径 【Radius】\n【cursorColor】 : 光标颜色 【Color】\n【cursorWidth】 : 光标宽度 【double】\n【showCursor】 : 是否显示光标 【bool】\n【autofocus】 : 自动聚焦 【bool】","code":"import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nclass CursorTextField extends StatefulWidget {\n @override\n _CursorTextFieldState createState() => _CursorTextFieldState();\n}\n\nclass _CursorTextFieldState extends State {\n final FocusNode _focusNode = FocusNode();\n\n @override\n void dispose() {\n _focusNode.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n crossAxisAlignment: CrossAxisAlignment.end,\n children: [\n _buildSubmitBtn(),\n _buildTextField(context),\n ],\n );\n }\n\n Container _buildTextField(BuildContext context) {\n return Container(\n width: 300,\n child: TextField(\n style: TextStyle(color: Colors.blue),\n minLines: 3,\n maxLines: 5,\n cursorColor: Colors.green,\n cursorRadius: Radius.circular(3),\n cursorWidth: 5,\n showCursor: true,\n decoration: InputDecoration(\n contentPadding: EdgeInsets.all(10),\n hintText: \"请输入...\",\n border: OutlineInputBorder(),\n ),\n onChanged: (v) {},\n ),\n );\n }\n\n _buildSubmitBtn() => FlatButton(\n color: Colors.blue,\n child: Text(\n \"提交\",\n style: TextStyle(color: Colors.white, fontSize: 16),\n ),\n onPressed: () => FocusScope.of(context).requestFocus(_focusNode));\n}\n"},{"id":null,"widgetId":54,"name":"decoration的复杂装饰","priority":3,"subtitle":" \nInputDecoration有非常多的装饰点,对应点缀见代码:\nborder: 边线相关\nhelper: 左下角相关提示\ncounter: 右下角相关提示\nprefix: 输入框内部最左侧\nsuffix: 输入框内部最右侧\nlabel: 无焦点时文字\nlabel: 无焦点时文字\nhint: 提示文字相关\nborder: 边线相关","code":"import 'package:flutter/material.dart';\nclass ComplexTextField extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return TextField(\n decoration: InputDecoration(\n border: OutlineInputBorder(),\n focusedBorder: OutlineInputBorder(\n borderSide: BorderSide(color: Colors.blue),\n borderRadius: BorderRadius.only(\n topLeft: Radius.circular(10), bottomLeft: Radius.circular(10))),\n enabledBorder: OutlineInputBorder(\n borderSide: BorderSide(color: Colors.deepPurpleAccent),\n borderRadius: BorderRadius.only(\n topLeft: Radius.circular(10), bottomLeft: Radius.circular(10))),\n labelText: 'username',\n labelStyle: TextStyle(color: Colors.purple),\n helperText: \"help me\",\n helperStyle: TextStyle(color: Colors.blue),\n\n suffixText: \"suffix\",\n suffixIcon: Icon(Icons.done),\n suffixStyle: TextStyle(color: Colors.green),\n\n counterText: \"counter\",\n counterStyle: TextStyle(color: Colors.orange),\n\n prefixText: \"ID \",\n prefixStyle: TextStyle(color: Colors.blue),\n prefixIcon: Icon(Icons.language),\n\n fillColor: Color(0x110099ee),\n filled: true,\n\n // errorText: \"error\",\n // errorMaxLines: 1,\n // errorStyle: TextStyle(color: Colors.red),\n // errorBorder: UnderlineInputBorder(),\n\n hintText: \"请输入用户名\",\n hintMaxLines: 1,\n hintStyle: TextStyle(color: Colors.black38),\n icon: Icon(Icons.assignment_ind),\n ));\n }\n}\n"},{"id":null,"widgetId":262,"name":"CupertinoSegmentedControl的颜色","priority":2,"subtitle":" \n【unselectedColor】 : 未选中色 【Color】\n【selectedColor】 : 选中色 【Color】\n【pressedColor】 : 按下色 【Color】\n【borderColor】 : 边线色 【Color】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CupertinoSegmentedControlColor extends StatefulWidget {\n @override\n _CupertinoSegmentedControlColorState createState() =>\n _CupertinoSegmentedControlColorState();\n}\n\nclass _CupertinoSegmentedControlColorState\n extends State {\n var _value = 1;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: CupertinoSegmentedControl(\n unselectedColor: Colors.yellow,\n selectedColor: Colors.green,\n pressedColor: Colors.blue,\n borderColor: Colors.red,\n groupValue: _value,\n onValueChanged: _onValueChanged,\n padding: EdgeInsets.only(top: 20),\n children: {\n 1: Padding(\n padding: EdgeInsets.only(left: 20, right: 20),\n child: Text(\"混沌战士\"),\n ),\n 2: Text(\"青眼白龙\"),\n 3: Text(\"黑魔术士\"),\n },\n ),\n );\n }\n\n void _onValueChanged(int value) {\n setState(() {\n _value=value;\n });\n }\n}\n"},{"id":null,"widgetId":262,"name":"基本使用","priority":1,"subtitle":" \n【children】 : 组件Map 【Map】\n【onValueChanged】 : 最小值 【ValueChanged】\n【groupValue】 : 选中值 【T】\n【padding】 : 内边距 【EdgeInsetsGeometry】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CupertinoSegmentedControlDemo extends StatefulWidget {\n @override\n _CupertinoSegmentedControlDemoState createState() =>\n _CupertinoSegmentedControlDemoState();\n}\n\nclass _CupertinoSegmentedControlDemoState\n extends State {\n var _value = 1;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: CupertinoSegmentedControl(\n groupValue: _value,\n onValueChanged: _onValueChanged,\n padding: EdgeInsets.only(top: 20),\n children: {\n 1: Padding(\n padding: EdgeInsets.only(left: 20, right: 20),\n child: Text(\"混沌战士\"),\n ),\n 2: Text(\"青眼白龙\"),\n 3: Text(\"黑魔术士\"),\n },\n ),\n );\n }\n\n void _onValueChanged(int value) {\n setState(() {\n _value=value;\n });\n }\n}\n"},{"id":null,"widgetId":42,"name":"Slider基本使用","priority":1,"subtitle":" \n【value】 : 数值 【double】\n【min】 : 最小值 【double】\n【max】 : 最大值 【double】\n【activeColor】 : 激活颜色 【Color】\n【inactiveColor】 : 非激活颜色 【Color】\n【onChanged】 : 改变时回调 【Function(double)】","code":"import 'package:flutter/material.dart';\nclass CustomSlider extends StatefulWidget {\n @override\n _CustomSliderState createState() => _CustomSliderState();\n}\n\nclass _CustomSliderState extends State {\n double _value = 0.0;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Text('当前值:${_value.toStringAsFixed(1)}'),\n Slider(\n value: _value,\n min: 0.0,\n max: 360.0,\n activeColor: Colors.orangeAccent,\n inactiveColor: Colors.green.withAlpha(99),\n onChanged: (value) {\n setState(() {\n _value = value;\n });\n }),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":42,"name":"Slider的分段与标签","priority":2,"subtitle":" \n【divisions】 : 分段数 【int】\n【label】 : 提示气泡文字 【String】\n【onChangeStart】 : 开始滑动时监听 【Function(double)】\n【onChangeEnd】 : 滑动结束时监听 【Function(double)】","code":"import 'package:flutter/material.dart';\nclass DivisionsSlider extends StatefulWidget {\n @override\n _DivisionsSliderState createState() => _DivisionsSliderState();\n}\n\nclass _DivisionsSliderState extends State {\n double _value = 0.0;\n\n @override\n Widget build(BuildContext context) {\n return Slider(\n value: _value,\n min: 0.0,\n max: 360.0,\n divisions: 10,\n label: '${_value.toStringAsFixed(1)}',\n activeColor: Colors.orangeAccent,\n inactiveColor: Colors.green.withAlpha(99),\n onChangeStart: (value) {\n print('开始滑动:$value');\n },\n onChangeEnd: (value) {\n print('滑动结束:$value');\n },\n onChanged: (value) {\n setState(() {\n _value = value;\n });\n });\n }\n}\n"},{"id":null,"widgetId":157,"name":"CupertinoPageScaffold基本用法","priority":1,"subtitle":" \n【child】 : 内容 【Widget】\n【backgroundColor】 : 背景色 【Color】\n【navigationBar】 : 头部 【ObstructingPreferredSizeWidget】","code":"import 'package:flutter/cupertino.dart';\nclass CustomCupertinoPageScaffold extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: MediaQuery.of(context).size.height - 300,\n child: CupertinoPageScaffold(\n navigationBar: CupertinoNavigationBar(\n leading: Icon(CupertinoIcons.reply),\n trailing: Icon(CupertinoIcons.share),\n middle: Text('Flutter Unit'),\n ),\n backgroundColor: CupertinoColors.systemBackground,\n child: Center(\n child: Text('Hello, World!'),\n ),\n ),\n );\n }\n}"},{"id":null,"widgetId":200,"name":"Stepper基本使用","priority":1,"subtitle":" \n【steps】 : 步骤列表 【List】\n【currentStep】 : 当前步骤 【double】\n【onStepTapped】 : 点击回调 【ValueChanged】\n【onStepCancel】 : 上一步回调 【VoidCallback】\n【controlsBuilder】 : 控制器构造 【ControlsWidgetBuilder】","code":"import 'package:flutter/material.dart';\nclass StepperDemo extends StatefulWidget {\n @override\n _StepperDemoState createState() => _StepperDemoState();\n}\n\nclass _StepperDemoState extends State {\n int _position = 0;\n\n final stepsData = {\n \"填写表单\":'请按表单填写个人信息。',\n \"邮箱校验\":'已将邮件发送至您的邮箱,请按照相关指示对您的账号进行邮箱校验。',\n \"注册完成\":'恭喜您,注册完成!',\n };\n\n final steps = [\n Step(\n title: Text(\"填写表单\"),\n content: Container(height: 60, child: Text(\"请按表单填写个人信息\")),\n ),\n Step(title: Text(\"邮箱校验\"), content: Text(\"请对您的账号进行邮箱校验\")),\n Step(title: Text(\"注册完成\"), content: Text(\"恭喜您,注册完成\")),\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: Stepper(\n type:StepperType.horizontal,\n currentStep: _position,\n onStepTapped: (index) {\n setState(() {\n _position = index;\n });\n },\n onStepContinue: () {\n setState(() {\n if (_position < 2) {\n _position++;\n }\n });\n },\n onStepCancel: () {\n if (_position > 0) {\n setState(() {\n _position--;\n });\n }\n },\n controlsBuilder: (_,\n {VoidCallback onStepContinue, VoidCallback onStepCancel}) {\n return Row(\n children: [\n RaisedButton(\n color: Colors.blue,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: onStepContinue,\n child: Icon(\n Icons.check,\n color: Colors.white,\n ),\n ),\n RaisedButton(\n color: Colors.red,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: onStepCancel,\n child: Icon(\n Icons.keyboard_backspace,\n color: Colors.white,\n ),\n ),\n ],\n );\n },\n steps: stepsData.keys.map((e){\n bool isActive = stepsData.keys.toList().indexOf(e) ==_position;\n return Step(\n title: Text(e,style: TextStyle(color: isActive?Colors.blue:Colors.black),),\n isActive: isActive,\n state: _getState(stepsData.keys.toList().indexOf(e)),\n content: Container(height: 60, child: Text(stepsData[e])),\n );\n }).toList()),\n );\n }\n _getState(index){\n if(_position==index) return StepState.editing;\n if(_position>index) return StepState.complete;\n return StepState.indexed;\n }\n}\n"},{"id":null,"widgetId":200,"name":"Stepper的方向","priority":2,"subtitle":" \n【type】 : 方向 【StepperType】","code":"import 'package:flutter/material.dart';\nclass VerticalStepper extends StatefulWidget {\n @override\n _VerticalStepperState createState() => _VerticalStepperState();\n}\n\nclass _VerticalStepperState extends State {\n int _position = 0;\n\n final stepsData = {\n \"填写表单\": '请按表单填写个人信息。',\n \"邮箱校验\": '已将邮件发送至您的邮箱,请按照相关指示对您的账号进行邮箱校验。',\n \"注册完成\": '恭喜您,注册完成!',\n };\n\n final steps = [\n Step(\n title: Text(\"填写表单\"),\n content: Container(height: 60, child: Text(\"请按表单填写个人信息\")),\n ),\n Step(title: Text(\"邮箱校验\"), content: Text(\"请对您的账号进行邮箱校验\")),\n Step(title: Text(\"注册完成\"), content: Text(\"恭喜您,注册完成\")),\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Stepper(\n type: StepperType.vertical,\n currentStep: _position,\n onStepTapped: (index) {\n setState(() {\n _position = index;\n });\n },\n onStepContinue: () {\n setState(() {\n if (_position < 2) {\n _position++;\n }\n });\n },\n onStepCancel: () {\n if (_position > 0) {\n setState(() {\n _position--;\n });\n }\n },\n controlsBuilder: (_,\n {VoidCallback onStepContinue, VoidCallback onStepCancel}) {\n return Row(\n children: [\n RaisedButton(\n color: Colors.blue,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: onStepContinue,\n child: Icon(\n Icons.check,\n color: Colors.white,\n ),\n ),\n RaisedButton(\n color: Colors.red,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: onStepCancel,\n child: Icon(\n Icons.keyboard_backspace,\n color: Colors.white,\n ),\n ),\n ],\n );\n },\n steps: stepsData.keys.map((e) {\n bool isActive = stepsData.keys.toList().indexOf(e) == _position;\n return Step(\n title: Text(\n e,\n style: TextStyle(color: isActive ? Colors.blue : Colors.black),\n ),\n isActive: isActive,\n state: _getState(stepsData.keys.toList().indexOf(e)),\n content: Container(height: 60, child: Text(stepsData[e])),\n );\n }).toList()),\n );\n }\n\n _getState(index) {\n if (_position == index) return StepState.editing;\n if (_position > index) return StepState.complete;\n return StepState.indexed;\n }\n}\n"},{"id":null,"widgetId":120,"name":"AnimatedAlign基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【duration】 : 动画时长 【Duration】\n【onEnd】 : 动画结束回调 【Function()】\n【alignment】 : 对齐方式 【AlignmentGeometry】\n【curve】 : 动画曲线 【Duration】\n【padding】 : 内边距 【EdgeInsetsGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedAlign extends StatefulWidget {\n @override\n _CustomAnimatedAlignState createState() => _CustomAnimatedAlignState();\n}\n\nclass _CustomAnimatedAlignState extends State {\n final Alignment start = Alignment(0, 0);\n final Alignment end = Alignment.bottomRight;\n\n Alignment _alignment;\n\n @override\n void initState() {\n _alignment = start;\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildSwitch(),\n Container(\n color: Colors.grey.withAlpha(22),\n width: 200,\n height: 100,\n child: AnimatedAlign(\n duration: Duration(seconds: 1),\n curve: Curves.fastOutSlowIn,\n alignment: _alignment,\n onEnd: () => print('End'),\n child: Container(\n height: 40,\n width: 80,\n alignment: Alignment.center,\n color: Colors.blue,\n child: Text(\n '张风捷特烈',\n style: TextStyle(color: Colors.white),\n ),\n ),\n ),\n ),\n ],\n );\n }\n\n Widget _buildSwitch() => Switch(\n value: _alignment == end,\n onChanged: (v) {\n setState(() {\n _alignment = v ? end : start;\n });\n });\n}\n"},{"id":null,"widgetId":48,"name":"CupertinoActivityIndicator基本使用","priority":1,"subtitle":" \n【animating】 : 是否loading动画 【bool】\n【radius】 : 半径 【double】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoActivityIndicator extends StatelessWidget {\n\n @override\n Widget build(BuildContext context) {\n\n return Wrap(\n spacing: 20,\n children: [\n CupertinoActivityIndicator(\n animating: true,\n radius: 25,\n ),\n CupertinoActivityIndicator(\n animating: false,\n radius: 25,\n )\n ],\n );\n }\n}\n"},{"id":null,"widgetId":123,"name":"AnimatedContainer基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【duration】 : 动画时长 【Duration】\n【onEnd】 : 动画结束回调 【Function()】\n【curve】 : 动画曲线 【Duration】\n【color】 : 颜色 【Color】\n【width】 : 宽 【double】\n【height】 : 高 【double】\n【alignment】 : 对齐 【AlignmentGeometry】\n【decoration】 : 装饰 【Decoration】\n【constraints】 : 约束 【BoxConstraints】\n【transform】 : 变化 【Matrix4】\n【margin】 : 外边距 【EdgeInsetsGeometry】\n【padding】 : 内边距 【EdgeInsetsGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedContainer extends StatefulWidget {\n @override\n _CustomAnimatedContainerState createState() =>\n _CustomAnimatedContainerState();\n}\n\nclass _CustomAnimatedContainerState extends State {\n final Decoration startDecoration = BoxDecoration(\n color: Colors.blue,\n image: DecorationImage(\n image: AssetImage('assets/images/wy_200x300.jpg'), fit: BoxFit.cover),\n borderRadius: BorderRadius.all(Radius.circular(20)));\n final Decoration endDecoration = BoxDecoration(\n image: DecorationImage(\n image: AssetImage('assets/images/wy_200x300.jpg'), fit: BoxFit.cover),\n color: Colors.orange,\n borderRadius: BorderRadius.all(Radius.circular(50)));\n\n final Alignment startAlignment = Alignment(0, 0);\n final Alignment endAlignment = Alignment.topLeft + Alignment(0.2, 0.2);\n\n final startHeight = 100.0;\n final endHeight = 50.0;\n\n Decoration _decoration;\n double _height;\n Alignment _alignment;\n\n @override\n void initState() {\n _decoration = startDecoration;\n _height = startHeight;\n _alignment=startAlignment;\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildSwitch(),\n AnimatedContainer(\n duration: Duration(seconds: 1),\n curve: Curves.fastOutSlowIn,\n alignment: _alignment,\n color: Colors.grey.withAlpha(22),\n width: 200,\n height: 120,\n child: UnconstrainedBox(\n child: AnimatedContainer(\n duration: Duration(seconds: 1),\n curve: Curves.fastOutSlowIn,\n decoration: _decoration,\n onEnd: () => print('End'),\n height: _height,\n width: _height,\n ),\n ),\n ),\n ],\n );\n }\n\n Widget _buildSwitch() => Switch(\n value: _height == endHeight,\n onChanged: (v) {\n setState(() {\n _height = v ? endHeight : startHeight;\n _decoration = v ? endDecoration : startDecoration;\n _alignment = v ? endAlignment : startAlignment;\n });\n });\n}\n"},{"id":null,"widgetId":178,"name":"ExpansionPanelList基本使用","priority":1,"subtitle":" \n【children】 : 子组件列表 【List】\n【animationDuration】 : 动画时长 【Duration】\n【expansionCallback】 : 展开回调 【List】\n【onPressed】 : 点击事件 【Function()】","code":"import 'package:flutter/material.dart';\nclass CustomExpansionPanelList extends StatefulWidget {\n @override\n _CustomExpansionPanelListState createState() =>\n _CustomExpansionPanelListState();\n}\n\nclass _CustomExpansionPanelListState extends State {\n var data = [\n Colors.red[50],\n Colors.red[100],\n Colors.red[200],\n Colors.red[300],\n Colors.red[400],\n Colors.red[500],\n Colors.red[600],\n Colors.red[700],\n Colors.red[800],\n Colors.red[900],\n ];\n int _position = 0;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 300,\n child: ExpansionPanelList(\n children: data.map((color) => _buildItem(color)).toList(),\n animationDuration: Duration(milliseconds: 200),\n expansionCallback: (index, open) {\n setState(() => _position=open?-1:index);\n },\n ),\n );\n }\n\n ExpansionPanel _buildItem(Color color) {\n return ExpansionPanel(\n isExpanded: data.indexOf(color) == _position,\n canTapOnHeader: true,\n headerBuilder: (ctx, index) => Center(\n child: Wrap(\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n Container(\n height: 30,\n width: 30,\n decoration:\n BoxDecoration(color: color, shape: BoxShape.circle),\n ),\n Container(\n width: 120,\n alignment: Alignment.center,\n height: 50,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.black),\n ),\n ),\n ],\n ),\n ),\n body: Container(\n alignment: Alignment.center,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2)\n ]),\n ),\n ));\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":44,"name":"RangeSlider基本使用","priority":1,"subtitle":" \n【values】 : 数值 【RangeValues】\n【min】 : 最小值 【double】\n【max】 : 最大值 【double】\n【divisions】 : 分段数 【int】\n【label】 : 提示气泡文字 【String】\n【activeColor】 : 激活颜色 【Color】\n【inactiveColor】 : 非激活颜色 【Color】\n【onChangeStart】 : 开始滑动时监听 【Function(RangeValues)】\n【onChangeEnd】 : 滑动结束时监听 【Function(RangeValues)】\n【onChanged】 : 改变时回调 【Function(RangeValues)】","code":"import 'package:flutter/material.dart';\nclass CustomRangeSlider extends StatefulWidget {\n @override\n _CustomRangeSliderState createState() => _CustomRangeSliderState();\n}\n\nclass _CustomRangeSliderState extends State {\n RangeValues _rangeValues = RangeValues(90, 270);\n\n @override\n Widget build(BuildContext context) {\n return RangeSlider(\n values: _rangeValues,\n divisions: 180,\n min: 0.0,\n max: 360.0,\n labels: RangeLabels(\"${_rangeValues.start.toStringAsFixed(1)}\",\n \"${_rangeValues.end.toStringAsFixed(1)}\"),\n activeColor: Colors.orangeAccent,\n inactiveColor: Colors.green.withAlpha(99),\n onChangeStart: (value) {\n print('开始滑动:$value');\n },\n onChangeEnd: (value) {\n print('滑动结束:$value');\n },\n onChanged: (value) {\n setState(() {\n _rangeValues = value;\n });\n });\n }\n}\n"},{"id":null,"widgetId":136,"name":"YearPicker基本使用","priority":1,"subtitle":" \n【selectedDate】 : 选中日期 【DateTime】\n【firstDate】 : 最前日期限制 【DateTime】\n【lastDate】 : 最后日期限制 【DateTime】\n【onChanged】 : 点击回调 【Function(DateTime)】","code":"import 'package:flutter/material.dart';\nclass CustomYearPicker extends StatefulWidget {\n @override\n _CustomYearPickerState createState() => _CustomYearPickerState();\n}\n\nclass _CustomYearPickerState extends State {\n DateTime _date = DateTime.now();\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height:150,\n child: YearPicker(\n selectedDate: _date,\n onChanged: (date) => setState(() => _date = date),\n firstDate: DateTime(2018),\n lastDate: DateTime(2030),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":92,"name":"SizeTransition基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【axis】 : 轴向*2 【Axis】\n【sizeFactor】 : 动画 【Animation】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomSizeTransition extends StatefulWidget {\n @override\n _CustomSizeTransitionState createState() => _CustomSizeTransitionState();\n}\n\nclass _CustomSizeTransitionState extends State\n with SingleTickerProviderStateMixin {\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 1));\n _ctrl.forward();\n super.initState();\n }\n\n @override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Wrap(\n runSpacing: 20,\n children: [\n SizeTransition(\n axis: Axis.horizontal,\n sizeFactor: CurvedAnimation(parent: _ctrl, curve: Curves.linear),\n child: Container(\n width: MediaQuery.of(context).size.width,\n color: Colors.orange,\n child: Icon(Icons.android, color: Colors.green, size: 80)),\n ),\n SizeTransition(\n axis: Axis.vertical,\n sizeFactor: CurvedAnimation(parent: _ctrl, curve: Curves.linear),\n child: Container(\n width: MediaQuery.of(context).size.width,\n color: Colors.orange,\n child: Icon(Icons.android, color: Colors.green, size: 80)),\n ),\n ],\n ),\n );\n }\n}\n"},{"id":null,"widgetId":90,"name":"RotationTransition基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【turns】 : 是否消失 【Animation】","code":"import 'package:flutter/material.dart';\nclass CustomRotationTransition extends StatefulWidget {\n @override\n _CustomRotationTransitionState createState() => _CustomRotationTransitionState();\n}\n\nclass _CustomRotationTransitionState extends State with SingleTickerProviderStateMixin{\n\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl= AnimationController(vsync: this,duration: Duration(seconds: 2));\n _ctrl.forward();\n super.initState();\n }\n@override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Container(\n color: Colors.grey.withAlpha(22),\n width: 100,\n height: 100,\n child: RotationTransition(\n turns: CurvedAnimation(parent: _ctrl, curve: Curves.linear),\n child: Icon(Icons.android,color: Colors.green,size: 60),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":116,"name":"AnimatedSwitcher基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【duration】 : 动画时长 【Duration】\n【switchOutCurve】 : 切出曲线 【Curves】\n【switchInCurve】 : 切入曲线 【Curves】\n【switchInCurve】 : 切入曲线 【Curves】\n【transitionBuilder】 : 动画构造器 【Widget Function(Widget, Animation)】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedSwitcher extends StatefulWidget {\n @override\n _CustomAnimatedSwitcherState createState() => _CustomAnimatedSwitcherState();\n}\n\nclass _CustomAnimatedSwitcherState extends State {\n int _count = 0;\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Wrap(\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n _buildMinusBtn(),\n SizedBox(width:80,child: _buildAnimatedSwitcher(context)),\n _buildAddBtn()\n ],\n ),\n );\n }\n\n Widget _buildAnimatedSwitcher(BuildContext context) =>\n AnimatedSwitcher(\n duration: const Duration(milliseconds: 400),\n transitionBuilder: (Widget child, Animation animation) =>\n ScaleTransition(\n child: RotationTransition(turns: animation, child: child),\n scale: animation),\n child: Text(\n '$_count',\n key: ValueKey(_count),\n style: Theme.of(context).textTheme.display3,\n ),\n );\n\n Widget _buildMinusBtn() {\n return MaterialButton(\n padding: EdgeInsets.all(0),\n textColor: Color(0xffFfffff),\n elevation: 3,\n color: Colors.red,\n highlightColor: Color(0xffF88B0A),\n splashColor: Colors.red,\n child: Icon(\n Icons.remove,\n color: Colors.white,\n ),\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: () => setState(() => _count -= 1));\n }\n\n Widget _buildAddBtn() => MaterialButton(\n padding: EdgeInsets.all(0),\n textColor: Color(0xffFfffff),\n elevation: 3,\n color: Colors.blue,\n highlightColor: Color(0xffF88B0A),\n splashColor: Colors.red,\n child: Icon(\n Icons.add,\n color: Colors.white,\n ),\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: () => setState(() => _count += 1));\n}\n"},{"id":null,"widgetId":38,"name":"可从资源文件和网络加载图片","priority":1,"subtitle":" \nImage.asset加载资源图片,","code":"import 'package:flutter/material.dart';\nclass LoadImage extends StatelessWidget {\n final assetsImagePath = \"assets/images/icon_head.png\";\n final assetsGif = \"assets/images/pica.gif\";\n final netImageUrl = \"https://user-gold-cdn.xitu.io\"\n \"/2019/7/24/16c225e78234ec26?\"\n \"imageView2/1/w/1304/h/734/q/85/format/webp/interlace/1\";\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n children: [\n _loadFromAssets(),\n _loadFromNet(),\n ],\n );\n }\n\n Widget _loadFromAssets() => Wrap(\n spacing: 10,\n children: [\n Image.asset(assetsImagePath, height: 80, width: 80),\n Image.asset(assetsGif, height: 80, width: 80),\n ],\n );\n\n Widget _loadFromNet() => Image.network(netImageUrl, height: 80);\n}\n"},{"id":null,"widgetId":38,"name":"图片颜色及混合模式","priority":4,"subtitle":" \n【color】 : 颜色 【Color】\n【colorBlendMode】: 混合模式*29 【BlendMode】","code":"import 'package:flutter/material.dart';\nclass BlendModeImage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: BlendMode.values\n .toList()\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 60,\n height: 60,\n color: Colors.red,\n child: Image(\n image: AssetImage(\"assets/images/icon_head.png\"),\n color: Colors.blue.withAlpha(88),\n colorBlendMode: mode)),\n Text(mode.toString().split(\".\")[1])\n ]))\n .toList(),\n );\n }\n}"},{"id":null,"widgetId":38,"name":"图片对齐模式","priority":3,"subtitle":" \n【alignment】 : 颜色 【AlignmentGeometry】\n 常用Alignment类的九个静态常量,但也可定制位置","code":"import 'package:flutter/material.dart';\nclass AlignmentImage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var alignment = [\n Alignment.center,\n Alignment.centerLeft,\n Alignment.centerRight,\n Alignment.topCenter,\n Alignment.topLeft,\n Alignment.topRight,\n Alignment.bottomCenter,\n Alignment.bottomLeft,\n Alignment.bottomRight\n ]; //测试数组\n var imgLi = alignment\n .map((alignment) => //生成子Widget列表\n Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 90,\n height: 60,\n color: Colors.grey.withAlpha(88),\n child: Image(\n image: AssetImage(\"assets/images/wy_30x20.jpg\"),\n alignment: alignment,\n )),\n Text(alignment.toString())\n ]))\n .toList();\n var imageAlignment = Wrap(children: imgLi);\n return imageAlignment;\n }\n}"},{"id":null,"widgetId":38,"name":"图片实现局部放大","priority":6,"subtitle":" \n【centerSlice】 : 保留的区域 【Rect】","code":"import 'package:flutter/material.dart';\nclass CenterSliceImage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: 300,\n height: 80,\n child: Image.asset(\n \"assets/images/right_chat.png\",\n centerSlice: Rect.fromLTRB(9, 27, 60, 27 + 1.0),\n fit: BoxFit.fill,\n ),\n );\n }\n}\n"},{"id":null,"widgetId":38,"name":"图片重复模式","priority":5,"subtitle":" \n【repeat】 : 重复模式*4 【ImageRepeat】","code":"import 'package:flutter/material.dart';\nclass RepeatImage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: ImageRepeat.values\n .toList()\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 150,\n height: 60,\n color: Colors.red,\n child: Image(\n image: AssetImage(\"assets/images/wy_30x20.jpg\"),\n repeat: mode)),\n Text(mode.toString().split(\".\")[1])\n ]))\n .toList(),\n );\n }\n}"},{"id":null,"widgetId":38,"name":"图片的适应模式","priority":2,"subtitle":" \n【fit】 : 适应模式*7 【BoxFit】,","code":"import 'package:flutter/material.dart';\nclass FitImage extends StatefulWidget {\n @override\n _FitImageState createState() => _FitImageState();\n}\n\nclass _FitImageState extends State {\n bool _smallImage = false;\n\n @override\n Widget build(BuildContext context) {\n var imageLi = BoxFit.values\n .toList()\n .map((mode) => Column(children: [\n Container(\n margin: EdgeInsets.all(5),\n width: 100,\n height: 80,\n color: Colors.grey.withAlpha(88),\n child: Image(\n image: AssetImage(!_smallImage\n ? \"assets/images/wy_300x200.jpg\"\n : \"assets/images/wy_30x20.jpg\"),\n fit: mode)),\n Text(mode.toString().split(\".\")[1])\n ]))\n .toList();\n\n return Wrap(\n children: [...imageLi, _buildSwitch()],\n );\n }\n\n Widget _buildSwitch() {\n return Container(\n alignment: Alignment.center,\n width: 200,\n height: 100,\n child: Wrap(\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n Text(\"使用小图\"),\n Switch(\n value: _smallImage,\n onChanged: (b) => setState(() => _smallImage = b)),\n ],\n ),\n );\n }\n}\n"},{"id":null,"widgetId":351,"name":"constrained属性测试","priority":2,"subtitle":"【constrained】 : 受约束的 【bool】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass InteractiveViewerDemo2 extends StatelessWidget {\n\n Widget build(BuildContext context) {\n const int _rowCount = 20;\n const int _columnCount = 4;\n\n return Container(\n width: 300,\n height: 200,\n child: InteractiveViewer(\n constrained: false,\n scaleEnabled: false,\n child: Table(\n columnWidths: {\n for (int column = 0; column < _columnCount; column += 1)\n column: const FixedColumnWidth(150.0),\n },\n children: buildRows(_rowCount, _columnCount),\n ),\n ),\n );\n }\n\n List buildRows(int rowCount, int columnCount) {\n return [\n for (int row = 0; row < rowCount; row += 1)\n TableRow(\n children: [\n for (int column = 0; column < columnCount; column += 1)\n Container(\n margin: EdgeInsets.all(2),\n height: 50,\n alignment: Alignment.center,\n color: _colorful(row,column),\n child: Text('($row,$column)',style: TextStyle(fontSize: 20,color: Colors.white),),\n ),\n ],\n ),\n ];\n }\n\n final colors = [Colors.red,Colors.yellow,Colors.blue,Colors.green];\n final colors2 = [Colors.yellow,Colors.blue,Colors.green,Colors.red];\n\n _colorful(int row, int column ) => row % 2==0?colors[column]:colors2[column];\n}\n"},{"id":null,"widgetId":351,"name":"InteractiveViewer基本使用","priority":1,"subtitle":"【alignPanAxis】 : 沿轴拖动 【bool】\n【boundaryMargin】 : 边界边距 【EdgeInsets】\n【panEnabled】 : 是否可平移 【bool】\n【scaleEnabled】 : 是否可缩放 【bool】\n【maxScale】 : 最大放大倍数 【double】\n【minScale】 : 最小缩小倍数 【double】\n【onInteractionEnd】 : 交互结束回调 【GestureScaleEndCallback】\n【onInteractionStart】 : 交互开始回调 【GestureScaleStartCallback】\n【onInteractionUpdate】 : 交互更新回调 【GestureScaleUpdateCallback】\n【child】 : 游标颜色 【Widget】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass InteractiveViewerDemo extends StatelessWidget {\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 150,\n color: Colors.grey.withAlpha(33),\n child: InteractiveViewer(\n boundaryMargin: EdgeInsets.all(40.0),\n maxScale: 2.5,\n minScale: 0.3,\n panEnabled: true,\n scaleEnabled: true,\n child: Container(\n child: Image.asset('assets/images/caver.jpeg'),\n ),\n onInteractionStart: _onInteractionStart,\n onInteractionUpdate: _onInteractionUpdate,\n onInteractionEnd: _onInteractionEnd,\n ),\n );\n }\n\n void _onInteractionStart(ScaleStartDetails details) {\n print('onInteractionStart----' + details.toString());\n }\n\n void _onInteractionUpdate(ScaleUpdateDetails details) {\n print('onInteractionUpdate----' + details.toString());\n }\n\n void _onInteractionEnd(ScaleEndDetails details) {\n print('onInteractionEnd----' + details.toString());\n }\n}\n"},{"id":null,"widgetId":351,"name":"变换控制器的使用","priority":3,"subtitle":"【transformationController】 : 变换控制器 【TransformationController】","code":"import 'dart:math';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass InteractiveViewerDemo3 extends StatefulWidget {\n @override\n _InteractiveViewerDemo3State createState() => _InteractiveViewerDemo3State();\n}\n\nclass _InteractiveViewerDemo3State extends State\n with SingleTickerProviderStateMixin {\n final TransformationController _transformationController =\n TransformationController();\n Animation _animationReset;\n AnimationController _controllerReset;\n\n void _onAnimateReset() {\n _transformationController.value = _animationReset.value;\n if (!_controllerReset.isAnimating) {\n _animationReset?.removeListener(_onAnimateReset);\n _animationReset = null;\n _controllerReset.reset();\n }\n }\n\n void _animateResetInitialize() {\n _controllerReset.reset();\n _animationReset = Matrix4Tween(\n begin: _transformationController.value,\n end: Matrix4.identity(),\n ).animate(_controllerReset);\n _animationReset.addListener(_onAnimateReset);\n _controllerReset.forward();\n }\n\n void _animateResetStop() {\n _controllerReset.stop();\n _animationReset?.removeListener(_onAnimateReset);\n _animationReset = null;\n _controllerReset.reset();\n }\n\n void _onInteractionStart(ScaleStartDetails details) {\n if (_controllerReset.status == AnimationStatus.forward) {\n _animateResetStop();\n }\n }\n\n @override\n void initState() {\n super.initState();\n _controllerReset = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 400),\n );\n }\n\n @override\n void dispose() {\n _controllerReset.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n direction: Axis.vertical,\n spacing: 10,\n crossAxisAlignment: WrapCrossAlignment.center,\n alignment: WrapAlignment.center,\n children: [\n Container(\n height: 150,\n color: Colors.grey.withAlpha(33),\n child: InteractiveViewer(\n boundaryMargin: EdgeInsets.all(40),\n transformationController: _transformationController,\n minScale: 0.1,\n maxScale: 1.8,\n onInteractionStart: _onInteractionStart,\n child: Container(\n child: Image.asset('assets/images/caver.jpeg'),\n ),\n ),\n ),\n Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [\n _buildButton(),\n _buildButton2(),\n _buildButton3(),\n ],\n )\n ],\n );\n }\n\n Widget _buildButton() {\n return MaterialButton(\n child: Icon(\n Icons.refresh,\n color: Colors.white,\n ),\n color: Colors.green,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: _animateResetInitialize);\n }\n\n var _x = 0.0;\n\n Widget _buildButton2() {\n return MaterialButton(\n child: Icon(\n Icons.navigate_before,\n color: Colors.white,\n ),\n color: Colors.green,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: () {\n var temp = _transformationController.value.clone();\n temp.translate(_x - 4);\n _transformationController.value = temp;\n });\n }\n\n Widget _buildButton3() {\n return MaterialButton(\n child: Icon(\n Icons.navigate_next,\n color: Colors.white,\n ),\n color: Colors.green,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: () {\n var temp = _transformationController.value.clone();\n temp.translate(_x + 4);\n _transformationController.value = temp;\n });\n }\n}\n"},{"id":null,"widgetId":255,"name":"ValueListenableBuilder基本使用","priority":1,"subtitle":"【builder】: 组件构造器 【ValueWidgetBuilder】\n【valueListenable】: 监听值 【ValueListenable】\n【child】: 子组件 【Widget】","code":"import 'package:flutter/material.dart';\nclass ValueListenableBuilderDemo extends StatelessWidget {\n ValueListenableBuilderDemo({Key key}) : super(key: key);\n\n final ValueNotifier _counter = ValueNotifier(0);\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: Scaffold(\n appBar: AppBar(title: Text(\"ValueListenableBuilder\")),\n body: Center(\n child: Column(\n mainAxisAlignment: MainAxisAlignment.center,\n children: [\n Text('You have pushed the button this many times:'),\n ValueListenableBuilder(\n builder: _buildWithValue,\n valueListenable: _counter,\n child: const Text('I am Child!'),\n )\n ],\n ),\n ),\n floatingActionButton: FloatingActionButton(\n child: Icon(Icons.plus_one),\n onPressed: () => _counter.value += 1,\n ),\n ),\n );\n }\n\n Widget _buildWithValue(BuildContext context, int value, Widget child) {\n return Row(\n mainAxisAlignment: MainAxisAlignment.spaceAround,\n children: [\n Text('$value'),\n child,\n ],\n );\n }\n}\n"},{"id":null,"widgetId":177,"name":"ReorderableListView基本使用","priority":1,"subtitle":" \n【children】 : 子组件列表 【List】\n【header】 : 头部组件 【Widget】\n【padding】 : 内边距 【EdgeInsets】\n【onReorder】 : 调换时回调 【ReorderCallback】","code":"import 'package:flutter/material.dart';\nclass CustomReorderableListView extends StatefulWidget {\n @override\n _CustomReorderableListViewState createState() => _CustomReorderableListViewState();\n}\n\nclass _CustomReorderableListViewState extends State {\n var data = [\n Colors.yellow[50],\n Colors.yellow[100],\n Colors.yellow[200],\n Colors.yellow[300],\n Colors.yellow[400],\n Colors.yellow[500],\n Colors.yellow[600],\n Colors.yellow[700],\n Colors.yellow[800],\n Colors.yellow[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 250,\n child: ReorderableListView(\n padding: EdgeInsets.all(10),\n header: Container(\n color: Colors.blue,\n alignment: Alignment.center,\n height: 50,\n child: Text('长按拖拽进行换位',style: TextStyle(color: Colors.white),)),\n onReorder: _handleReorder,\n children: data.map((color) => _buildItem(color)).toList(),\n ),\n );\n }\n\n void _handleReorder(int oldIndex, int newIndex) {\n if (oldIndex < newIndex) {\n newIndex -= 1;\n }\n\n setState(() {\n final element = data.removeAt(oldIndex);\n data.insert(newIndex, element);\n });\n\n }\n\n Widget _buildItem(Color color) {\n return Container(\n key: ValueKey(color) ,\n alignment: Alignment.center,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2)\n ]),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":177,"name":"ReorderableListView滑动方向","priority":2,"subtitle":" \n【scrollDirection】 : 滑动方向 【Axis】\n【reverse】 : 是否反向 【bool】","code":"import 'package:flutter/material.dart';\nclass DirectionReorderableListView extends StatefulWidget {\n @override\n _DirectionReorderableListViewState createState() => _DirectionReorderableListViewState();\n}\n\nclass _DirectionReorderableListViewState extends State {\n var data = [\n Colors.yellow[50],\n Colors.yellow[100],\n Colors.yellow[200],\n Colors.yellow[300],\n Colors.yellow[400],\n Colors.yellow[500],\n Colors.yellow[600],\n Colors.yellow[700],\n Colors.yellow[800],\n Colors.yellow[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: ReorderableListView(\n scrollDirection: Axis.horizontal,\n reverse: false,\n onReorder: _handleReorder,\n children: data.map((color) => _buildItem(color)).toList(),\n ),\n );\n }\n\n void _handleReorder(int oldIndex, int newIndex) {\n if (oldIndex < newIndex) {\n newIndex -= 1;\n }\n\n setState(() {\n final element = data.removeAt(oldIndex);\n data.insert(newIndex, element);\n });\n\n }\n\n Widget _buildItem(Color color) {\n return Container(\n key: ValueKey(color) ,\n alignment: Alignment.center,\n width: 80,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2)\n ]),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":113,"name":"DecoratedBoxTransition基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【position】 : 前/背景色 【DecorationPosition】\n【decoration】 : 动画 【Animation】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomDecoratedBoxTransition extends StatefulWidget {\n @override\n _CustomDecoratedBoxTransitionState createState() =>\n _CustomDecoratedBoxTransitionState();\n}\n\nclass _CustomDecoratedBoxTransitionState\n extends State\n with SingleTickerProviderStateMixin {\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 1));\n _ctrl.forward();\n super.initState();\n }\n\n @override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Container(\n width: 200,\n height: 100,\n child: DecoratedBoxTransition(\n position: DecorationPosition.background,\n decoration: DecorationTween(\n begin: BoxDecoration(\n color: Colors.greenAccent,\n borderRadius: BorderRadius.all(Radius.circular(50)),\n boxShadow: [\n BoxShadow(\n offset: Offset(1, 1),\n color: Colors.purple,\n blurRadius: 3,\n spreadRadius: 1)\n ]),\n end: BoxDecoration(\n color: Colors.orange,\n borderRadius: BorderRadius.all(Radius.circular(10)),\n boxShadow: [\n BoxShadow(\n offset: Offset(1, 1),\n color: Colors.blue,\n blurRadius: 1,\n spreadRadius: 0)\n ])).animate(_ctrl),\n child: Container(\n child: Icon(Icons.android, color: Colors.white, size: 60)),\n ),\n ));\n }\n}\n"},{"id":null,"widgetId":52,"name":"ExpansionTile基本使用","priority":1,"subtitle":" \n【children】 : 展开内容 【List】\n【leading】 : 头左组件 【Widget】\n【title】 : 头中组件 【Widget】\n【trailing】 : 头尾组件 【Widget】\n【backgroundColor】 : 背景色 【Color】\n【onExpansionChanged】 : 折叠事件 【Function(bool)】\n【initiallyExpanded】 : 是否初始时展开 【bool】","code":"import 'package:flutter/material.dart';\nimport '../../StatelessWidget/RadioListTile/node1_base.dart';\nclass CustomExpansionTile extends StatefulWidget {\n @override\n _CustomExpansionTileState createState() => _CustomExpansionTileState();\n}\n\nclass _CustomExpansionTileState extends State {\n @override\n Widget build(BuildContext context) {\n return ExpansionTile(\n leading: Icon(Icons.star),\n title: Text(\"选择语言\"),\n backgroundColor: Colors.grey.withAlpha(6),\n onExpansionChanged: (value) {\n print('$value');\n },\n initiallyExpanded: false,\n children: [CustomRadioListTile()],\n );\n }\n}\n"},{"id":null,"widgetId":158,"name":"CupertinoTabScaffold基本用法","priority":1,"subtitle":" \n【tabBar】 : 页签条 【CupertinoTabBar】\n【backgroundColor】 : 背景色 【Color】\n【controller】 : 控制器 【CupertinoTabController】\n【tabBuilder】 : 页面构造器 【IndexedWidgetBuilder】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoTabScaffold extends StatefulWidget {\n @override\n _CustomCupertinoTabScaffoldState createState() =>\n _CustomCupertinoTabScaffoldState();\n}\n\nclass _CustomCupertinoTabScaffoldState\n extends State {\n var _position = 0;\n final iconsMap = {\n //底栏图标\n \"图鉴\": Icons.home, \"动态\": Icons.toys,\n \"喜欢\": Icons.favorite, \"手册\": Icons.class_,\n \"我的\": Icons.account_circle,\n };\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: MediaQuery.of(context).size.height - 300,\n child: CupertinoTabScaffold(\n backgroundColor: Colors.grey.withAlpha(11),\n tabBar: _buildTabBar(),\n tabBuilder: (_, index) => _buildContent(index)),\n );\n }\n\n CupertinoTabBar _buildTabBar() => CupertinoTabBar(\n currentIndex: _position,\n onTap: (value) => setState(() => _position = value),\n items: iconsMap.keys\n .map((e) => BottomNavigationBarItem(\n icon: Icon(\n iconsMap[e],\n ),\n title: Text(e),\n ))\n .toList(),\n activeColor: Colors.blue,\n inactiveColor: Color(0xff333333),\n backgroundColor: Color(0xfff1f1f1),\n iconSize: 25.0,\n );\n\n _buildContent(int index) => Container(\n alignment: Alignment.center,\n child: Text(iconsMap.keys.toList()[index]),\n );\n}\n"},{"id":null,"widgetId":105,"name":"LongPressDraggable与DragTarget联用","priority":1,"subtitle":" \n【child】 : 孩子 【Widget】\n【feedback】 : 拖拽时的孩子 【Widget】\n【axis】 : 拖动的轴 【Axis】\n【data】 : 数据 【T】\n【onDragStarted】 : 开始拖拽 【Function()】\n【onDragEnd】 : 结束拖拽 【Function(DraggableDetails)】\n【onDragCompleted】 : 拖拽完成 【Function()】\n【onDraggableCanceled】 : 拖拽取消 【Function(Velocity,Offset)】","code":"import 'package:flutter/material.dart';\nclass CustomLongPressDraggable extends StatefulWidget {\n @override\n _CustomLongPressDraggableState createState() => _CustomLongPressDraggableState();\n}\n\nclass _CustomLongPressDraggableState extends State {\n Color _color = Colors.grey;\n String _info = 'DragTarget';\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n Wrap(\n children: _buildColors(),\n spacing: 10,\n ),\n SizedBox(height: 20,),\n _buildDragTarget()\n ],\n ),\n );\n }\n\n List _buildColors() {\n var colors = [\n Colors.red,\n Colors.yellow,\n Colors.blue,\n Colors.green,\n Colors.orange,\n Colors.purple,\n Colors.cyanAccent\n ];\n return colors\n .map(\n (e) => LongPressDraggable(\n onDragStarted: () => setState(() => _info = '开始拖拽'),\n onDragEnd: (d) => setState(() => _info = '结束拖拽'),\n onDragCompleted: () => _info = '拖拽完成',\n onDraggableCanceled: (v, o) => _info = '拖拽取消',\n child: Container(\n width: 30,\n height: 30,\n alignment: Alignment.center,\n child: Text(\n colors.indexOf(e).toString(),\n style: TextStyle(\n color: Colors.white, fontWeight: FontWeight.bold),\n ),\n decoration: BoxDecoration(color: e, shape: BoxShape.circle),\n ),\n data: e,\n feedback: Container(\n width: 25,\n height: 25,\n decoration: BoxDecoration(color: e, shape: BoxShape.circle),\n )),\n )\n .toList();\n }\n\n Widget _buildDragTarget() {\n return DragTarget(\n onAccept: (data) => setState(() {\n _info='onAccept';\n _color = data;\n }),\n builder: (context, candidateData, rejectedData) => Container(\n width: 150.0,\n height: 50.0,\n color: _color,\n child: Center(\n child: Text(\n _info,\n style: TextStyle(color: Colors.white),\n ),\n )));\n }\n}\n"},{"id":null,"widgetId":138,"name":"CupertinoTimerPicker基本使用","priority":1,"subtitle":" \n【initialTimerDuration】 : 初始时间 【Duration】\n【minuteInterval】 : 分钟间隔数 【double】\n【secondInterval】 : 秒间隔数 【double】\n【alignment】 : 对齐方式 【AlignmentGeometry】\n【backgroundColor】 : 背景色 【Color】\n【mode】 : 模式*3 【CupertinoTimerPickerMode】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoTimerPicker extends StatefulWidget {\n @override\n _CustomCupertinoTimerPickerState createState() =>\n _CustomCupertinoTimerPickerState();\n}\n\nclass _CustomCupertinoTimerPickerState\n extends State {\n Duration _date = Duration(seconds: 30);\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Text(\n '当前时间:${_date.toString()}',\n style: TextStyle(color: Colors.grey, fontSize: 16),\n ),\n _buildInfoTitle('CupertinoTimerPickerMode.hms'),\n buildPicker(CupertinoTimerPickerMode.hms),\n _buildInfoTitle('CupertinoTimerPickerMode.hm'),\n buildPicker(CupertinoTimerPickerMode.hm),\n _buildInfoTitle('CupertinoTimerPickerMode.ms'),\n buildPicker(CupertinoTimerPickerMode.ms),\n ],\n );\n }\n\n Widget _buildInfoTitle(info) {\n return Padding(\n padding: const EdgeInsets.only(left: 20, top: 20, bottom: 5),\n child: Text(\n info,\n style: TextStyle(\n color: Colors.blue, fontSize: 16, fontWeight: FontWeight.bold),\n ),\n );\n }\n\n Widget buildPicker(CupertinoTimerPickerMode mode) {\n return Container(\n margin: EdgeInsets.all(10),\n height: 150,\n child: CupertinoTimerPicker(\n mode: mode,\n initialTimerDuration: Duration(seconds: 30),\n onTimerDurationChanged: (date) {\n print(date);\n setState(() => _date = date);\n },\n ),\n );\n }\n}\n"},{"id":null,"widgetId":93,"name":"PositionedTransition基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【rect】 : 动画 【Animation】\n PositionedTransition组件只能在Stack内起作用","code":"import 'package:flutter/material.dart';\nclass CustomPositionedTransition extends StatefulWidget {\n @override\n _CustomPositionedTransitionState createState() =>\n _CustomPositionedTransitionState();\n}\n\nclass _CustomPositionedTransitionState extends State\n with SingleTickerProviderStateMixin {\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 2));\n _ctrl.forward();\n super.initState();\n }\n\n @override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Container(\n color: Colors.grey.withAlpha(33),\n width: 200,\n height: 100,\n child: Stack(\n children: [\n PositionedTransition(\n rect: RelativeRectTween(\n begin: RelativeRect.fromLTRB(0, 50, 150, 100),\n end: RelativeRect.fromLTRB(60, 0, 150, -50),\n ).animate(_ctrl),\n child: Icon(\n Icons.android,\n color: Colors.green,\n size: 50,\n ),\n )\n ],\n ),\n ));\n }\n}\n"},{"id":null,"widgetId":103,"name":"Draggable基本使用","priority":1,"subtitle":" \n【child】 : 孩子 【Widget】\n【feedback】 : 拖拽时的孩子 【Widget】\n【axis】 : 拖动的轴 【Axis】","code":"import 'package:flutter/material.dart';\nclass CustomDraggable extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var axis = [null, Axis.vertical, Axis.horizontal];\n return Wrap(\n spacing: 30,\n children: axis\n .map((e) => Draggable(\n axis: e,\n child: Container(\n width: 30,\n height: 30,\n alignment: Alignment.center,\n decoration: BoxDecoration(\n color: Colors.blue, shape: BoxShape.circle),\n ),\n feedback: Container(\n width: 30,\n height: 30,\n decoration: BoxDecoration(\n color: Colors.red, shape: BoxShape.circle),\n ),\n ))\n .toList());\n }\n}"},{"id":null,"widgetId":103,"name":"Draggable其他使用","priority":3,"subtitle":" \n可以根据拖拽来处理一些事件。如删除、查询、弹框等","code":"import 'package:flutter/material.dart';\nclass DeleteDraggable extends StatefulWidget {\n @override\n _DeleteDraggableState createState() => _DeleteDraggableState();\n}\n\nclass _DeleteDraggableState extends State {\n List colors = [\n Colors.red,\n Colors.yellow,\n Colors.blue,\n Colors.green,\n Colors.orange,\n Colors.purple,\n Colors.cyanAccent\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n Wrap(\n children: _buildColors(),\n spacing: 10,\n ),\n SizedBox(\n height: 20,\n ),\n _buildDragTarget()\n ],\n ),\n );\n }\n\n Widget _buildDragTarget() {\n return DragTarget(\n onAccept: (data) {\n setState(() {\n colors.removeAt(data);\n });\n },\n onWillAccept: (data) => data != null,\n builder: (context, candidateData, rejectedData) => Container(\n width: 50.0,\n height: 50.0,\n decoration:\n BoxDecoration(color: Colors.red, shape: BoxShape.circle),\n child: Center(\n child: Icon(Icons.delete_sweep, color: Colors.white),\n )));\n }\n\n List _buildColors() => colors\n .map(\n (e) => Draggable(\n child: Container(\n width: 30,\n height: 30,\n alignment: Alignment.center,\n child: Text(\n colors.indexOf(e).toString(),\n style:\n TextStyle(color: Colors.white, fontWeight: FontWeight.bold),\n ),\n decoration: BoxDecoration(color: e, shape: BoxShape.circle),\n ),\n data: colors.indexOf(e),\n feedback: Container(\n width: 25,\n height: 25,\n decoration: BoxDecoration(\n color: e.withAlpha(100), shape: BoxShape.circle),\n )),\n )\n .toList();\n}\n"},{"id":null,"widgetId":103,"name":"Draggable与DragTarget联用","priority":2,"subtitle":" \n【data】 : 数据 【T】\n【onDragStarted】 : 开始拖拽 【Function()】\n【onDragEnd】 : 结束拖拽 【Function(DraggableDetails)】\n【onDragCompleted】 : 拖拽完成 【Function()】\n【onDraggableCanceled】 : 拖拽取消 【Function(Velocity,Offset)】\n【onChanged】 : 改变时回调 【Function(T)】","code":"import 'package:flutter/material.dart';\nclass DraggablePage extends StatefulWidget {\n @override\n _DraggablePageState createState() => _DraggablePageState();\n}\n\nclass _DraggablePageState extends State {\n Color _color = Colors.grey;\n String _info = 'DragTarget';\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n Wrap(\n children: _buildColors(),\n spacing: 10,\n ),\n SizedBox(\n height: 20,\n ),\n _buildDragTarget()\n ],\n ),\n );\n }\n\n List _buildColors() {\n var colors = [\n Colors.red,\n Colors.yellow,\n Colors.blue,\n Colors.green,\n Colors.orange,\n Colors.purple,\n Colors.cyanAccent\n ];\n return colors\n .map(\n (e) => Draggable(\n onDragStarted: () => setState(() => _info = '开始拖拽'),\n onDragEnd: (d) => setState(() => _info = '结束拖拽'),\n onDragCompleted: () => _info = '拖拽完成',\n onDraggableCanceled: (v, o) => _info = '拖拽取消',\n child: Container(\n width: 30,\n height: 30,\n alignment: Alignment.center,\n child: Text(\n colors.indexOf(e).toString(),\n style: TextStyle(\n color: Colors.white, fontWeight: FontWeight.bold),\n ),\n decoration: BoxDecoration(color: e, shape: BoxShape.circle),\n ),\n data: e,\n feedback: Container(\n width: 25,\n height: 25,\n decoration: BoxDecoration(color: e, shape: BoxShape.circle),\n )),\n )\n .toList();\n }\n\n Widget _buildDragTarget() {\n return DragTarget(\n onLeave: (data) => print(\"onLeave: data = $data \"),\n onAccept: (data) {\n print(\"onAccept: data = $data \");\n setState(() {\n _color = data;\n });\n },\n onWillAccept: (data) {\n print(\"onWillAccept: data = $data \");\n return data != null;\n },\n builder: (context, candidateData, rejectedData) => Container(\n width: 150.0,\n height: 50.0,\n color: _color,\n child: Center(\n child: Text(\n _info,\n style: TextStyle(color: Colors.white),\n ),\n )));\n }\n}\n"},{"id":null,"widgetId":121,"name":"AnimatedPositioned基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【duration】 : 动画时长 【Duration】\n【onEnd】 : 动画结束回调 【Function()】\n【curve】 : 动画曲线 【Duration】\n【top】 : 到父顶距离 【double】\n【right】 : 到父右距离 【double】\n【left】 : 到父左距离 【double】\n【bottom】 : 到父底距离 【double】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedPositioned extends StatefulWidget {\n @override\n _CustomAnimatedPositionedState createState() =>\n _CustomAnimatedPositionedState();\n}\n\nclass _CustomAnimatedPositionedState extends State {\n final startTop = 0.0;\n final endTop = 30.0;\n\n var _top = 0.0;\n\n @override\n void initState() {\n _top = startTop;\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildSwitch(),\n Container(\n color: Colors.grey.withAlpha(33),\n width: 200,\n height: 100,\n child: Stack(\n children: _buildChildren(),\n ),\n ),\n ],\n );\n }\n\n List _buildChildren() => [\n AnimatedPositioned(\n duration: Duration(seconds: 1),\n top: _top,\n left: _top * 4,\n child: Icon(\n Icons.android,\n color: Colors.green,\n size: 50,\n ),\n ),\n AnimatedPositioned(\n duration: Duration(seconds: 1),\n top: 50 - _top,\n left: 150 - _top * 4,\n child: Icon(\n Icons.android,\n color: Colors.red,\n size: 50,\n ),\n )\n ];\n\n Widget _buildSwitch() => Switch(\n value: _top == endTop,\n onChanged: (v) {\n setState(() {\n _top = v ? endTop : startTop;\n });\n });\n}\n"},{"id":null,"widgetId":51,"name":"ExpandIcon基本使用","priority":1,"subtitle":" \n【isExpanded】 : 是否展开 【bool】\n【padding】 : 内边距 【EdgeInsetsGeometry】,\n【size】 : 图标大小 【double】\n【color】 : 不展开时颜色 【Color】\n【expandedColor】 : 展开时颜色 【Color】\n【onPressed】 : 点击事件 【Function(bool)】","code":"import 'package:flutter/material.dart';\nclass CustomExpandIcon extends StatefulWidget {\n @override\n _CustomExpandIconState createState() => _CustomExpandIconState();\n}\n\nclass _CustomExpandIconState extends State {\n var _closed = true;\n\n @override\n Widget build(BuildContext context) {\n return ExpandIcon(\n isExpanded: _closed,\n padding: EdgeInsets.all(5),\n size: 30,\n color: Colors.blue,\n expandedColor: Colors.orangeAccent,\n onPressed: (value) => setState(() => _closed = !_closed),\n );\n }\n}\n"},{"id":null,"widgetId":199,"name":"TextFormField基本使用","priority":1,"subtitle":" \n 基本属性和TextField一致,详见之\n【validator】 : 验证函数 【FormFieldValidator 】\n【onFieldSubmitted】 : 提交回调 【ValueChanged】\n【onSaved】 : 表单save时回调 【FormFieldSetter】","code":"import 'package:flutter/material.dart';\nclass CustomTextFormField extends StatefulWidget {\n @override\n _CustomTextFormFieldState createState() => _CustomTextFormFieldState();\n}\n\nclass _CustomTextFormFieldState extends State {\n GlobalKey _formKey = GlobalKey();\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Form(\n key: _formKey,\n child:\n Stack(\n alignment: Alignment.centerRight,\n children: [\n Container(\n width: 350,\n child: UnconstrainedBox(\n child: Container(\n width: 200,\n height: 70,\n child: TextFormField(\n style: TextStyle(textBaseline: TextBaseline.alphabetic),\n decoration: InputDecoration(\n border: OutlineInputBorder(),\n labelText: 'username',\n ),\n validator: _validateUsername,\n onFieldSubmitted: _onFieldSubmitted,\n onSaved: _onSaved,\n ),\n ),\n ),\n ),\n Positioned(\n top: 0, right: 0, child: _buildSubmitButton(context)),\n ],\n ),\n ),\n );\n }\n\n String _validateUsername(value) {\n if (value.isEmpty) {\n return '用户名不能为空';\n }\n return null;\n }\n _onSaved(value){\n print('onSaved:'+value);\n }\n\n void _onFieldSubmitted(value) {\n print('onFieldSubmitted:'+value);\n }\n\n RaisedButton _buildSubmitButton(BuildContext context) {\n return RaisedButton(\n color: Colors.blue,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n onPressed: _onSubmit,\n child: Icon(\n Icons.check,\n color: Colors.white,\n ),\n );\n }\n\n _onSubmit(){\n _formKey.currentState.save();\n if (_formKey.currentState.validate()) {\n FocusScope.of(context).requestFocus(FocusNode());\n }\n }\n}"},{"id":null,"widgetId":253,"name":"Scrollable的基本使用","priority":1,"subtitle":"【viewportBuilder】 : 视口构造器 【ViewportBuilder】\n【axisDirection】: 滑动方向 【AxisDirection】\n【controller】: 滑动控制器 【ScrollController】\n【dragStartBehavior】: t拖动行为 【DragStartBehavior】\n【physics】: 滚动现象 【ScrollPhysics】\n【color】: 子组件 【Color】","code":"import 'package:flutter/gestures.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/rendering.dart';\nclass ScrollableDemo extends StatelessWidget {\n final data = List.generate(32, (i) => Color(0xFF6600FF - 2 * i));\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 250,\n child: Scrollable(\n axisDirection: AxisDirection.down,\n physics: BouncingScrollPhysics(),\n dragStartBehavior: DragStartBehavior.start,\n viewportBuilder: (ctx, position) => Viewport(\n cacheExtent: 200,\n cacheExtentStyle: CacheExtentStyle.pixel,\n offset: position,\n slivers: [_buildSliverList()],\n ),\n ),\n );\n }\n\n Widget _buildSliverList() => SliverList(\n delegate: SliverChildBuilderDelegate(\n (_, int index) => Container(\n margin: EdgeInsets.only(top: 1),\n alignment: Alignment.center,\n width: 100,\n height: 60,\n color: data[index],\n child: Text(\n colorString(data[index]),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ),\n childCount: data.length),\n );\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}\n"},{"id":null,"widgetId":40,"name":"Switch基础用法","priority":1,"subtitle":" \n【inactiveThumbColor】 : 未选中小圈颜色 【Color】\n【inactiveTrackColor】 : 未选中滑槽颜色 【Color】\n【activeColor】 : 选中时小圈颜色 【Color】\n【activeTrackColor】 : 选中时滑槽颜色 【Color】\n【onChanged】 : 切换回调 【Function(double)】\"\n onChanged时,回调true、null、false三种状态","code":"import 'package:flutter/material.dart';\nclass CustomSwitch extends StatefulWidget {\n @override\n _CustomSwitchState createState() => _CustomSwitchState();\n}\n\nclass _CustomSwitchState extends State {\n final colors = [Colors.red, Colors.yellow, Colors.blue, Colors.green];\n bool _checked = false;\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n children: colors\n .map((e) =>\n Switch(\n value: _checked,\n inactiveThumbColor: e,\n inactiveTrackColor: Colors.grey.withAlpha(88),\n activeColor: Colors.green,\n activeTrackColor: Colors.orange,\n onChanged: (v) {\n setState(() => _checked = v);\n }))\n .toList(),\n );\n }\n}\n\n"},{"id":null,"widgetId":40,"name":"Switch图片","priority":2,"subtitle":" \n【inactiveThumbImage】 : 未选中小圈图片 【ImageProvider】\n【activeThumbImage】 : 选中时滑槽颜色 【ImageProvider】","code":"import 'package:flutter/material.dart';\nclass ImageSwitch extends StatefulWidget {\n @override\n _ImageSwitchState createState() => _ImageSwitchState();\n}\n\nclass _ImageSwitchState extends State {\n final imgs = [\n \"assets/images/head_icon/icon_5.jpg\",\n \"assets/images/head_icon/icon_6.jpg\",\n \"assets/images/head_icon/icon_7.jpg\",\n \"assets/images/head_icon/icon_8.jpg\"];\n bool _checked = false;\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n children: imgs\n .map((e) =>\n Switch(\n value: _checked,\n inactiveThumbImage: AssetImage(e),\n activeThumbImage: AssetImage('assets/images/pica.gif'),\n onChanged: (v) {\n setState(() => _checked = v);\n }))\n .toList(),\n );\n }\n}\n\n"},{"id":null,"widgetId":232,"name":"Navigator基本用法","priority":1,"subtitle":" \n【initialRoute】 : 最初显示路由 【String】\n【onGenerateRoute】 : 路由生成器 【RouteFactory】\n【observers】 : 路由监听器 【List】\n【onPopPage】 : 出栈回调 【PopPageCallback】","code":"import 'package:flutter/material.dart';\nclass NavigatorDemo extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n width: 300,\n child: Navigator(\n onPopPage: _onPopPage,\n initialRoute: '/home-content',\n onGenerateRoute: _onGenerateRoute,\n observers: [TolyNavigatorObservers()],\n ),\n );\n }\n\n Route _onGenerateRoute(RouteSettings settings) {\n switch (settings.name) {\n case '/home-content':\n return MaterialPageRoute(\n builder: (_) => HomeContent(), settings: settings);\n case \"/red\":\n return MaterialPageRoute(builder: (_) => RedPage(), settings: settings);\n case \"/yellow\":\n return MaterialPageRoute(\n builder: (_) => YellowPage(), settings: settings);\n case \"/green\":\n return MaterialPageRoute(\n builder: (_) => GreenPage(), settings: settings);\n default:\n return MaterialPageRoute(\n builder: (_) => HomeContent(), settings: settings);\n }\n }\n\n bool _onPopPage(Route route, result) {\n print('----_onPopPage-----');\n return true;\n }\n}\n\n//路由监听器\nclass TolyNavigatorObservers extends NavigatorObserver {\n @override\n void didPush(Route route, Route previousRoute) {\n print(\n '--didPush:--route:--${route.settings}--previousRoute:--${previousRoute?.settings}');\n }\n\n @override\n void didStopUserGesture() {\n print('--didStopUserGesture:--');\n }\n\n @override\n void didStartUserGesture(Route route, Route previousRoute) {\n print(\n '--didStartUserGesture:--route:--${route.settings}--previousRoute:--${previousRoute.settings}');\n }\n\n @override\n void didReplace({Route newRoute, Route oldRoute}) {\n print(\n '--didReplace:--newRoute:--${newRoute.settings}--oldRoute:--${oldRoute.settings}');\n }\n\n @override\n void didRemove(Route route, Route previousRoute) {\n print(\n '--didRemove:--route:--${route.settings}--previousRoute:--${previousRoute.settings}');\n }\n\n @override\n void didPop(Route route, Route previousRoute) {\n print(\n '--didPop:--route:--${route.settings}--previousRoute:--${previousRoute.settings}');\n }\n}\n\nclass HomeContent extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n Row(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n children: [\n RaisedButton(\n color: Colors.red,\n onPressed: () {\n Navigator.pushNamed(context, '/red');\n },\n ),\n RaisedButton(\n color: Colors.yellow,\n onPressed: () {\n Navigator.pushNamed(context, '/yellow');\n },\n ),\n RaisedButton(\n color: Colors.green,\n onPressed: () {\n Navigator.pushNamed(context, '/green');\n },\n )\n ],\n ),\n ],\n ),\n );\n }\n}\n\nclass RedPage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Scaffold(\n appBar: AppBar(\n title: Text(\"RedPage\"),\n ),\n body: Container(\n color: Colors.red,\n ),\n );\n }\n}\n\nclass YellowPage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Scaffold(\n appBar: AppBar(\n title: Text(\"YellowPage\"),\n ),\n body: Container(\n color: Colors.yellow,\n ),\n );\n }\n}\n\nclass GreenPage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Scaffold(\n appBar: AppBar(\n title: Text(\"GreenPage\"),\n ),\n body: Container(\n color: Colors.green,\n ),\n );\n }\n}\n"},{"id":null,"widgetId":43,"name":"CupertinoSlider基本使用","priority":1,"subtitle":" \n【value】 : 数值 【double】\n【min】 : 最小值 【double】\n【max】 : 最大值 【double】\n【activeColor】 : 激活颜色 【Color】\n【thumbColor】 : 圆形颜色 【Color】\n【divisions】 : 分段数 【int】\n【onChangeStart】 : 开始滑动回调 【Function(double)】\n【onChangeEnd】 : 滑动结束回调 【Function(double)】\n【onChanged】 : 改变时回调 【Function(double)】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoSlider extends StatefulWidget {\n @override\n _CustomCupertinoSliderState createState() => _CustomCupertinoSliderState();\n}\n\nclass _CustomCupertinoSliderState extends State {\n double _value = 0.0;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n mainAxisSize: MainAxisSize.min,\n\n children: [\n Text('当前值:${_value.toStringAsFixed(1)}'),\n CupertinoSlider(\n value: _value,\n divisions: 180,\n min: 0.0,\n max: 360.0,\n activeColor: Colors.green,\n thumbColor: Colors.white,\n onChangeStart: (value) {\n print('开始滑动:$value');\n },\n onChangeEnd: (value) {\n print('滑动结束:$value');\n },\n onChanged: (value) {\n setState(() {\n _value = value;\n });\n }),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":63,"name":"CupertinoTabBar基本用法","priority":1,"subtitle":" \n【currentIndex】 : 当前激活索引 【Widget】\n【items】 : 条目组件 【Widget】\n【backgroundColor】 : 背景色 【Color】\n【inactiveColor】 : 非激活色 【Color】\n【activeColor】 : 激活色 【Color】\n【iconSize】 : 图标大小 【double】\n【border】 : 边线 【Border】\n【onTap】 : 点击事件 【Function(int)】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoTabBar extends StatefulWidget {\n @override\n _CustomCupertinoTabBarState createState() => _CustomCupertinoTabBarState();\n}\n\nclass _CustomCupertinoTabBarState extends State {\n var _position = 0;\n final iconsMap = {\n //底栏图标\n \"图鉴\": Icons.home, \"动态\": Icons.toys,\n \"喜欢\": Icons.favorite, \"手册\": Icons.class_,\n \"我的\": Icons.account_circle,\n };\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildContent(context),\n _buildTabBar(),\n ],\n );\n }\n\n Widget _buildTabBar() {\n return CupertinoTabBar(\n currentIndex: _position,\n onTap: (value) => setState(() => _position = value),\n items: iconsMap.keys\n .map((e) => BottomNavigationBarItem(\n icon: Icon(\n iconsMap[e],\n ),\n title: Text(e),\n ))\n .toList(),\n activeColor: Colors.blue,\n inactiveColor: Color(0xff333333),\n backgroundColor: Color(0xfff1f1f1),\n iconSize: 25.0,\n );\n }\n\n Widget _buildContent(BuildContext context) {\n return Container(\n alignment: Alignment.center,\n width: MediaQuery.of(context).size.width,\n height: 150,\n color: Color(0xffE7F3FC),\n child: Text(\n iconsMap.keys.toList()[_position],\n style: TextStyle(color: Colors.blue, fontSize: 24),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":122,"name":"AnimatedPositionedDirectional基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【duration】 : 动画时长 【Duration】\n【onEnd】 : 动画结束回调 【Function()】\n【curve】 : 动画曲线 【Duration】\n【top】 : 到父顶距离 【double】\n【end】 : 到父右距离 【double】\n【start】 : 到父左距离 【double】\n【bottom】 : 到父底距离 【double】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedPositionedDirectional extends StatefulWidget {\n @override\n _CustomAnimatedPositionedDirectionalState createState() =>\n _CustomAnimatedPositionedDirectionalState();\n}\n\nclass _CustomAnimatedPositionedDirectionalState\n extends State {\n final startTop = 0.0;\n final endTop = 30.0;\n\n var _top = 0.0;\n\n @override\n void initState() {\n _top = startTop;\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n _buildSwitch(),\n Container(\n color: Colors.grey.withAlpha(33),\n width: 200,\n height: 100,\n child: Stack(\n children: _buildChildren(),\n ),\n ),\n ],\n );\n }\n\n List _buildChildren() => [\n AnimatedPositionedDirectional(\n duration: Duration(seconds: 1),\n top: _top,\n start: _top * 4,\n child: Icon(\n Icons.android,\n color: Colors.green,\n size: 50,\n ),\n ),\n AnimatedPositionedDirectional(\n duration: Duration(seconds: 1),\n top: 50 - _top,\n start: 150 - _top * 4,\n child: Icon(\n Icons.android,\n color: Colors.red,\n size: 50,\n ),\n )\n ];\n\n Widget _buildSwitch() => Switch(\n value: _top == endTop,\n onChanged: (v) {\n setState(() {\n _top = v ? endTop : startTop;\n });\n });\n}\n"},{"id":null,"widgetId":293,"name":"MouseRegion基本使用","priority":1,"subtitle":"【onEnter】 : 移入事件 【PointerEnterEventListener】\n【onHover】: 移动事件 【PointerHoverEventListener】\n【onExit】: 移出事件 【PointerExitEventListener】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass MouseRegionDemo extends StatefulWidget {\n @override\n _MouseRegionDemoState createState() => _MouseRegionDemoState();\n}\n\nclass _MouseRegionDemoState extends State {\n int _enterCounter = 0;\n int _exitCounter = 0;\n double x = 0.0;\n double y = 0.0;\n void _incrementEnter(PointerEvent details) {\n setState(() {\n _enterCounter++;\n });\n }\n void _incrementExit(PointerEvent details) {\n setState(() {\n _exitCounter++;\n });\n }\n void _updateLocation(PointerEvent details) {\n setState(() {\n x = details.position.dx;\n y = details.position.dy;\n });\n }\n @override\n Widget build(BuildContext context) {\n return ConstrainedBox(\n constraints: BoxConstraints.tight(Size(300.0, 200.0)),\n child: MouseRegion(\n onEnter: _incrementEnter,\n onHover: _updateLocation,\n onExit: _incrementExit,\n child: Container(\n color: Colors.lightBlueAccent,\n child: Column(\n mainAxisAlignment: MainAxisAlignment.center,\n children: [\n Text('你的鼠标移入移除信息:'),\n Text(\n '$_enterCounter Entries\\n$_exitCounter Exits',\n style: Theme.of(context).textTheme.headline4,\n ),\n Text(\n 'The cursor is here: (${x.toStringAsFixed(2)}, ${y.toStringAsFixed(2)})',\n ),\n ],\n ),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":244,"name":"EditableText基本使用","priority":1,"subtitle":"【controller】 : 控制器 【TextEditingController】\n【focusNode】 : 焦点 【FocusNode】\n【style】 : 文字样式 【TextStyle】\n【backgroundCursorColor】 : 背景游标颜色 【Color】\n【cursorColor】 : 游标颜色 【Color】\n上面五个是EditableText必须的属性,其他同TextField,此处不再列举。","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass EditableTextDemo extends StatefulWidget {\n @override\n _EditableTextDemoState createState() => _EditableTextDemoState();\n}\n\nclass _EditableTextDemoState extends State {\n final _ctrl = TextEditingController(text:'Hello Flutter Unit!');\n final _node = FocusNode();\n\n @override\n Widget build(BuildContext context) {\n return Padding(\n padding: const EdgeInsets.all(8.0),\n child: EditableText(\n controller: _ctrl,\n focusNode: _node,\n style: TextStyle(fontSize: 16,color: Colors.blue),\n cursorColor: Colors.blue,\n backgroundCursorColor: Colors.orange,\n ),\n );\n }\n}\n"},{"id":null,"widgetId":117,"name":"AnimatedList基本使用","priority":1,"subtitle":" \n【itemBuilder】 : 组件构造器 【AnimatedListItemBuilder】\n【initialItemCount】 : 子组件数量 【int】\n【scrollDirection】 : 滑动方向 【Axis】\n【controller】 : 滑动控制器 【ScrollController】\n【reverse】 : 数据是否反向 【bool】\n【padding】 : 内边距 【EdgeInsetsGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedList extends StatefulWidget {\n @override\n _CustomAnimatedListState createState() => _CustomAnimatedListState();\n}\n\nclass _CustomAnimatedListState extends State {\n final GlobalKey _listKey = GlobalKey();\n ListModel _list;\n int _selectedItem;\n int _nextItem;\n\n @override\n void initState() {\n super.initState();\n _list = ListModel(\n listKey: _listKey,\n initialItems: [0, 1, 2, 3],\n removedItemBuilder: _buildRemovedItem,\n );\n _nextItem = 4;\n }\n\n Widget _buildItem(\n BuildContext context, int index, Animation animation) {\n return CardItem(\n animation: animation,\n item: _list[index],\n selected: _selectedItem == _list[index],\n onTap: () {\n setState(() {\n _selectedItem = _selectedItem == _list[index] ? null : _list[index];\n });\n },\n );\n }\n\n Widget _buildRemovedItem(\n int item, BuildContext context, Animation animation) {\n return CardItem(\n animation: animation,\n item: item,\n selected: false,\n );\n }\n\n void _insert() {\n final int index =\n _selectedItem == null ? _list.length : _list.indexOf(_selectedItem);\n _list.insert(index, _nextItem++);\n }\n\n void _remove() {\n if (_selectedItem != null) {\n _list.removeAt(_list.indexOf(_selectedItem));\n setState(() {\n _selectedItem = null;\n });\n }\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n color: Colors.grey.withAlpha(33),\n width: MediaQuery.of(context).size.width/2,\n child: Column(\n children: [\n _buildBtn(),\n Container(\n width: MediaQuery.of(context).size.width/2,\n height: 300,\n child: AnimatedList(\n padding: EdgeInsets.all(10.0),\n key: _listKey,\n initialItemCount: _list.length,\n itemBuilder: _buildItem,\n ),\n )\n ],\n ));\n }\n\n Widget _buildBtn() => Row(\n children: [\n IconButton(\n icon: const Icon(\n Icons.add_circle,\n color: Colors.blue,\n ),\n onPressed: _insert,\n ),\n IconButton(\n icon: const Icon(Icons.remove_circle, color: Colors.blue),\n onPressed: _remove,\n ),\n ],\n );\n}\n\nclass ListModel {\n ListModel({\n @required this.listKey,\n @required this.removedItemBuilder,\n Iterable initialItems,\n }) : assert(listKey != null),\n assert(removedItemBuilder != null),\n _items = List.from(initialItems ?? []);\n final GlobalKey listKey;\n final dynamic removedItemBuilder;\n final List _items;\n\n AnimatedListState get _animatedList => listKey.currentState;\n\n void insert(int index, E item) {\n _items.insert(index, item);\n _animatedList.insertItem(index);\n }\n\n E removeAt(int index) {\n final E removedItem = _items.removeAt(index);\n if (removedItem != null) {\n _animatedList.removeItem(index,\n (BuildContext context, Animation animation) =>\n removedItemBuilder(removedItem, context, animation),\n );\n }\n return removedItem;\n }\n\n int get length => _items.length;\n\n E operator [](int index) => _items[index];\n\n int indexOf(E item) => _items.indexOf(item);\n}\n\nclass CardItem extends StatelessWidget {\n const CardItem(\n {Key key,\n @required this.animation,\n this.onTap,\n @required this.item,\n this.selected: false})\n : assert(animation != null),\n assert(item != null && item >= 0),\n assert(selected != null),\n super(key: key);\n final Animation animation;\n final VoidCallback onTap;\n final int item;\n final bool selected;\n\n @override\n Widget build(BuildContext context) {\n return SizeTransition(\n axis: Axis.vertical,\n sizeFactor: animation,\n child: Card(\n child: Container(\n color: Colors.primaries[item % Colors.primaries.length],\n child: CheckboxListTile(\n dense: true,\n title: Text(\n 'Item $item',\n style: TextStyle(color: Colors.white, fontSize: 18),\n ),\n value: selected,\n onChanged: (v) => onTap()),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":149,"name":"InkResponse其他属性","priority":2,"subtitle":" \n【child】 : 子组件 【Widget】\n【onHighlightChanged】 : 高亮变化回调 【Function(bool)】\n【highlightColor】 : 高亮色 【Color】\n【splashColor】 : 水波纹色 【Color】\n【radius】 : 水波半径 【double】","code":"import 'package:flutter/material.dart';\nclass ColorInkResponse extends StatefulWidget {\n @override\n _ColorInkResponseState createState() => _ColorInkResponseState();\n}\n\nclass _ColorInkResponseState extends State {\n var _info = 'Push';\n\n @override\n Widget build(BuildContext context) {\n return InkResponse(\n onTap: () => {},\n splashColor: Colors.blueAccent,\n highlightColor: Colors.orange,\n onHighlightChanged: (v) =>\n setState(() => _info = 'onHighlightChanged:$v'),\n radius: 50,\n child: Container(\n alignment: Alignment.center,\n width: 200,\n height: 100,\n child: Text(_info),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":149,"name":"InkResponse基本事件","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【onTap】 : 点击事件 【Function()】\n【onDoubleTap】 : 双击事件 【Function()】\n【onTapCancel】 : 点击取消 【Function()】\n【onLongPress】 : 长按事件 【Function()】","code":"import 'package:flutter/material.dart';\nclass CustomInkResponse extends StatefulWidget {\n @override\n _CustomInkResponseState createState() => _CustomInkResponseState();\n}\n\nclass _CustomInkResponseState extends State {\n var _info = 'Push';\n\n @override\n Widget build(BuildContext context) {\n return InkResponse(\n onTap: () => setState(() => _info = 'onTap'),\n onDoubleTap: () => setState(() => _info = 'onDoubleTap'),\n onLongPress: () => setState(() => _info = 'onLongPress'),\n onTapCancel: () => setState(() => _info = 'onTapCancel'),\n child: Container(\n alignment: Alignment.center,\n width: 200,\n height: 100,\n child: Text(_info),\n ),\n );\n }\n}"},{"id":null,"widgetId":119,"name":"AnimatedPadding基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【duration】 : 动画时长 【Duration】\n【onEnd】 : 动画结束回调 【Function()】\n【curve】 : 动画曲线 【Duration】\n【padding】 : 内边距 【EdgeInsetsGeometry】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedPadding extends StatefulWidget {\n @override\n _CustomAnimatedPaddingState createState() => _CustomAnimatedPaddingState();\n}\n\nclass _CustomAnimatedPaddingState extends State {\n final EdgeInsets startPadding = EdgeInsets.all(10);\n final EdgeInsets endPadding = EdgeInsets.all(30);\n\n EdgeInsets _padding;\n\n @override\n void initState() {\n _padding = startPadding;\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Switch(\n value: _padding == endPadding,\n onChanged: (v) {\n setState(() {\n _padding = v ? endPadding : startPadding;\n });\n }),\n Container(\n color: Colors.grey.withAlpha(22),\n width: 200,\n height: 100,\n child: AnimatedPadding(\n duration: Duration(seconds: 1),\n curve: Curves.fastOutSlowIn,\n padding: _padding,\n onEnd: () => print('End'),\n child: Container(\n alignment: Alignment.center,\n color: Colors.blue,\n child: Text(\n '张风捷特烈',\n style: TextStyle(color: Colors.white),\n ),\n ),\n ),\n ),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":111,"name":"AlignTransition基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【alignment】 : 对齐动画 【Animation】","code":"import 'package:flutter/material.dart';\nclass CustomAlignTransition extends StatefulWidget {\n @override\n _CustomAlignTransitionState createState() => _CustomAlignTransitionState();\n}\n\nclass _CustomAlignTransitionState extends State\n with SingleTickerProviderStateMixin {\n AnimationController _ctrl;\n\n @override\n void initState() {\n _ctrl = AnimationController(vsync: this, duration: Duration(seconds: 1));\n _ctrl.forward();\n super.initState();\n }\n\n @override\n void dispose() {\n _ctrl.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return GestureDetector(\n onTap: () {\n setState(() {\n _ctrl.reset();\n _ctrl.forward();\n });\n },\n child: Container(\n width: MediaQuery.of(context).size.width,\n color: Colors.grey.withAlpha(33),\n height: 100,\n child: AlignTransition(\n alignment: AlignmentTween(\n begin: Alignment.topLeft, end: Alignment.bottomRight)\n .animate(_ctrl),\n child: Container(\n child: Icon(Icons.android, color: Colors.green, size: 60)),\n ),\n ));\n }\n}\n"},{"id":null,"widgetId":145,"name":"LicensePage基本使用","priority":1,"subtitle":" \n【applicationIcon】 : 左上图标 【Widget】\n【applicationVersion】 : 版本号 【String】\n【applicationName】 : 应用名 【String】\n【applicationLegalese】 : 应用律术 【String】","code":"import 'package:flutter/material.dart';\nclass CustomLicensePage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: 400,\n child: LicensePage(\n applicationIcon: FlutterLogo(),\n applicationVersion: 'v0.0.1',\n applicationName: 'Flutter Unit',\n applicationLegalese: 'Copyright© 2018-2020 张风捷特烈',\n ),\n );\n }\n}\n"},{"id":null,"widgetId":156,"name":"CupertinoApp基本用法","priority":1,"subtitle":" \n【theme】 : 主题 【ThemeData】\n【title】 : 任务栏标题 【String】\n【onGenerateRoute】 : 路由生成器 【RouteFactory】\n【home】 : 主页 【Widget】","code":"import 'package:flutter/cupertino.dart';\nclass CustomCupertinoApp extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: MediaQuery.of(context).size.height - 200,\n child: CupertinoApp(\n title: 'Flutter Demo',\n theme: CupertinoThemeData(\n primaryColor: CupertinoColors.white,\n ),\n home: CupertinoPageScaffold(\n navigationBar: CupertinoNavigationBar(\n leading: Icon(\n CupertinoIcons.reply,\n color: CupertinoColors.black,\n ),\n trailing: Icon(\n CupertinoIcons.share,\n color: CupertinoColors.black,\n ),\n middle: Text('Flutter Unit'),\n ),\n backgroundColor: CupertinoColors.systemBackground,\n child: Center(\n child: Text('Hello, World!'),\n ),\n ),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":64,"name":"Scaffold基本用法","priority":1,"subtitle":" \n【appBar】 : 头部组件 【PreferredSizeWidget】\n【bottomNavigationBar】 : 底部组件 【Widget】\n【drawer】 : 左侧滑组件 【Widget】\n【endDrawer】 : 右侧滑组件 【Widget】\n【body】 : 内容组件 【Widget】\n【backgroundColor】 : 背景色 【Color】\n【floatingActionButton】 : 浮动按钮 【Widget】\n【floatingActionButtonLocation】 : 浮动按钮位置 【FloatingActionButtonLocation】","code":"import 'package:flutter/material.dart';\nimport '../PopupMenuButton/node1_base.dart';\nclass CustomScaffold extends StatefulWidget {\n CustomScaffold({Key key}) : super(key: key);\n\n @override\n State createState() => _CustomScaffoldState();\n}\n\n// AppBar 默认的实例,有状态\nclass _CustomScaffoldState extends State with SingleTickerProviderStateMixin {\n final tabs = ['风画庭', '雨韵舍', '雷鸣殿', '电疾堂', '霜寒阁', '雪月楼'];\n var _position = 0;\n final iconsMap = {\n \"图鉴\": Icons.home,\n \"动态\": Icons.toys,\n \"喜欢\": Icons.favorite,\n \"手册\": Icons.class_,\n \"我的\": Icons.account_circle,\n };\n final _colors = [\n Colors.blue,\n Colors.red,\n Colors.yellow,\n Colors.green,\n Colors.purple,\n ];\n\n TabController _tabController;\n\n @override\n void initState() {\n super.initState();\n _tabController = TabController(vsync: this, length: tabs.length);\n }\n\n @override\n void dispose() {\n _tabController.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n width: MediaQuery.of(context).size.width,\n height: MediaQuery.of(context).size.height - 300,\n child: Scaffold(\n floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,\n floatingActionButton: FloatingActionButton(\n child: Icon(Icons.add),\n onPressed: () {},\n ),\n drawer: _buildLeftDrawer(),\n endDrawer: _buildLeftDrawer(),\n appBar: AppBar(\n title: Text('风雅六社'),\n backgroundColor: Colors.blue,\n centerTitle: true,\n actions: [Icon(Icons.star), CustomPopupMenuButton()],\n bottom: _buildTabBar(),\n ),\n body: _buildTableBarView(),\n bottomNavigationBar: _buildBottomNavigationBar(),\n ),\n );\n }\n\n Drawer _buildLeftDrawer() => Drawer(\n elevation: 1,\n child: Image.asset(\n 'assets/images/sabar.jpg',\n fit: BoxFit.cover,\n ),\n );\n\n Widget _buildTabBar() => TabBar(\n isScrollable: true,\n controller: _tabController,\n indicatorColor: Colors.orangeAccent,\n tabs: tabs.map((e) => Tab(text: e)).toList(),\n );\n\n Widget _buildBottomNavigationBar() => BottomNavigationBar(\n onTap: (position) => setState(() => _position = position),\n currentIndex: _position,\n elevation: 1,\n backgroundColor: Colors.white,\n iconSize: 25,\n selectedLabelStyle: TextStyle(fontWeight: FontWeight.bold),\n showUnselectedLabels: false,\n showSelectedLabels: true,\n items: iconsMap.keys\n .map((key) => BottomNavigationBarItem(\n title: Text(\n key,\n ),\n icon: Icon(iconsMap[key]),\n backgroundColor: _colors[_position]))\n .toList(),\n );\n\n Widget _buildTableBarView() => TabBarView(\n controller: _tabController,\n children: tabs\n .map((e) => Center(\n child: Text(\n e,\n style: TextStyle(color: Colors.blue, fontSize: 20),\n )))\n .toList());\n}\n"},{"id":null,"widgetId":171,"name":"Hero基本使用","priority":1,"subtitle":" \n【tag】 : 标签 【String】,","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomHero extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var hero = Hero(\n //----定义一个Hero,并添加tag标签,此中组件共享\n tag: 'user-head',\n child: ClipRRect(\n borderRadius: BorderRadius.all(Radius.circular(30)),\n child: Image.asset(\n \"assets/images/icon_head.png\",\n width: 60,\n height: 60,\n fit: BoxFit.cover,\n ),\n ),\n );\n\n var container = Container(\n alignment: Alignment(-0.8, -0.8),\n child: hero,\n width: 250,\n height: 250 * 0.618,\n decoration: BoxDecoration(\n gradient: LinearGradient(colors: [\n Colors.red.withAlpha(99),\n Colors.yellow.withAlpha(189),\n Colors.green.withAlpha(88),\n Colors.blue.withAlpha(230)\n ])),\n );\n\n return GestureDetector(\n child: Card(elevation: 5, child: container),\n onTap: () => Navigator.push(\n context,\n Bottom2TopRouter(child: TargetPage(), duration: 1000),\n ),\n );\n }\n\n}\n\nclass TargetPage extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var hero = Hero(\n //----定义一个Hero,为其添加标签,两个标签相同,则可以共享\n tag: 'user-head',\n child: Padding(\n padding: EdgeInsets.all(6.0),\n child: CircleAvatar(\n backgroundColor: Colors.transparent,\n backgroundImage: AssetImage(\n \"assets/images/icon_head.png\",\n ),\n ),\n ),\n );\n\n var touch = InkWell(\n onTap: () {\n Navigator.of(context).pop();\n },\n child: hero,\n );\n\n return Scaffold(\n appBar: AppBar(\n actions: [touch],\n ),\n body: Container(\n decoration: BoxDecoration(\n gradient: LinearGradient(colors: [\n Colors.red.withAlpha(99),\n Colors.yellow.withAlpha(189),\n Colors.green.withAlpha(88),\n Colors.blue.withAlpha(230)\n ])),\n ),\n );\n }\n}\n\n//下--->上\nclass Bottom2TopRouter extends PageRouteBuilder {\n final Widget child;\n final int duration;\n final Curve curve;\n\n Bottom2TopRouter(\n {this.child, this.duration = 500, this.curve = Curves.fastOutSlowIn})\n : super(\n transitionDuration: Duration(milliseconds: duration),\n pageBuilder: (ctx, a1, a2) {\n return child;\n },\n transitionsBuilder: (\n ctx,\n a1,\n a2,\n Widget child,\n ) => SlideTransition(\n position: Tween(\n begin: Offset(0.0, 1.0),\n end: Offset(0.0, 0.0),\n ).animate(CurvedAnimation(parent: a1, curve: curve)),\n child: child));\n}\n"},{"id":null,"widgetId":174,"name":"PopupMenuDivider基本使用","priority":1,"subtitle":" \n【height】 : 高度 【double】","code":"import 'package:flutter/material.dart';\nimport '../../../dialogs/dialog_about.dart';\nclass CustomPopupMenuDivider extends StatelessWidget {\n final map = {\n \"关于\": Icons.info_outline,\n \"帮助\": Icons.help_outline,\n \"问题反馈\": Icons.add_comment,\n };\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: Column(\n children: [\n _buildPopupMenuButton(context),\n PopupMenuDivider(),\n ],\n ),\n );\n }\n\n PopupMenuButton _buildPopupMenuButton(BuildContext context) {\n return PopupMenuButton(\n itemBuilder: (context) => [\n ...buildItems().sublist(0, 2),\n PopupMenuDivider(),\n ...buildItems().sublist(2, 3)\n ],\n offset: Offset(0, 50),\n color: Color(0xffF4FFFA),\n elevation: 1,\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.only(\n topLeft: Radius.circular(20),\n bottomRight: Radius.circular(20),\n topRight: Radius.circular(5),\n bottomLeft: Radius.circular(5),\n )),\n onSelected: (e) {\n print(e);\n if (e == '关于') {\n DialogAbout.show(context);\n }\n },\n onCanceled: () => print('onCanceled'),\n );\n }\n\n List> buildItems() {\n return map.keys\n .toList()\n .map((e) => PopupMenuItem(\n value: e,\n child: Wrap(\n spacing: 10,\n children: [\n Icon(\n map[e],\n color: Colors.blue,\n ),\n Text(e),\n ],\n )))\n .toList();\n }\n}\n"},{"id":null,"widgetId":195,"name":"CupertinoScrollbar基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【controller】 : 控制器 【ScrollController】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoScrollbar extends StatelessWidget {\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: CupertinoScrollbar(\n child: ListView(\n padding: EdgeInsets.symmetric(horizontal: 5),\n children: data\n .map((color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n ),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":172,"name":"FutureBuilder基本使用","priority":1,"subtitle":" \n【builder】 : 子组件 【AsyncWidgetBuilder】\n【initialData】 : 初始数据 【T】\n【future】 : 异步任务 【Future】","code":"import 'package:flutter/material.dart';\nclass CustomFutureBuilder extends StatefulWidget {\n @override\n _CustomFutureBuilderState createState() => _CustomFutureBuilderState();\n}\n\nclass _CustomFutureBuilderState extends State {\n Future _future;\n\n @override\n void initState() {\n _future = loadData();\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Container(\n child: FutureBuilder(\n initialData: 'Load',\n future: _future,\n builder: (ctx, snap) {\n if (snap.connectionState == ConnectionState.done) {\n return Text(snap.data);\n }\n if (snap.connectionState == ConnectionState.waiting) {\n return CircularProgressIndicator();\n }\n if (snap.hasError) {\n return Text('Error');\n }\n return Container();\n }),\n );\n }\n\n Future loadData() async {\n await Future.delayed(Duration(seconds: 2));\n return 'LoadeSuccess';\n }\n}\n"},{"id":null,"widgetId":182,"name":"Overlay基本使用","priority":1,"subtitle":" \n Overlay.of(context).insert插入全局组件","code":"import 'package:flutter/material.dart';\nclass CustomOverlay extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: [\n Container(\n height: 50,\n child: RawMaterialButton(\n elevation: 2,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n fillColor: Colors.blue,\n splashColor: Colors.orange,\n textStyle: TextStyle(color: Colors.white),\n child: Icon(Icons.add),\n onPressed: ()=>showFloating(context),\n ),\n ),\n Container(\n height: 50,\n child: RawMaterialButton(\n elevation: 2,\n shape: CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n fillColor: Colors.red,\n splashColor: Colors.orange,\n textStyle: TextStyle(color: Colors.white),\n child: Icon(Icons.remove),\n onPressed: hideFloating,\n ),\n ),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":139,"name":"CupertinoPicker基本使用","priority":1,"subtitle":" \n【children】 : 子组件列表 【List】\n【offAxisFraction】 : 轴偏移率 【double】\n【squeeze】 : 挤压率 【double】\n【diameterRatio】 : 高与圆柱直径比率 【double】\n【itemExtent】 : 间距 【double】\n【backgroundColor】 : 背景色 【Color】\n【onSelectedItemChanged】 : 选中事件 【Function(int)】","code":"import 'package:flutter/cupertino.dart';\nclass CustomCupertinoPicker extends StatelessWidget {\n final names = [\n 'Java',\n 'Kotlin',\n 'Dart',\n 'Swift',\n 'C++',\n 'Python',\n \"JavaScript\",\n \"PHP\",\n \"Go\",\n \"Object-c\"\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 150,\n child: CupertinoPicker(\n backgroundColor: CupertinoColors.systemGrey.withAlpha(33),\n diameterRatio: 1,\n offAxisFraction: 0.4,\n squeeze: 1.5,\n itemExtent: 40,\n onSelectedItemChanged: (position) {\n print('当前条目 ${names[position]}');\n },\n children: names.map((e) => Center(child: Text(e))).toList()),\n );\n }\n}\n"},{"id":null,"widgetId":118,"name":"AnimatedOpacity基本使用","priority":1,"subtitle":" \n【child】 : 孩子组件 【Widget】\n【duration】 : 动画时长 【Duration】\n【onEnd】 : 动画结束回调 【Function()】\n【curve】 : 动画曲线 【Duration】\n【opacity】 : 透明度 【double】","code":"import 'package:flutter/material.dart';\nclass CustomAnimatedOpacity extends StatefulWidget {\n @override\n _CustomAnimatedOpacityState createState() => _CustomAnimatedOpacityState();\n}\n\nclass _CustomAnimatedOpacityState extends State {\n double _opacity = 1.0;\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Switch(\n value: _opacity == 0,\n onChanged: (v) {\n setState(() {\n _opacity = v ? 0 : 1.0;\n });\n }),\n Container(\n color: Colors.grey.withAlpha(22),\n width: 200,\n height: 100,\n child: AnimatedOpacity(\n duration: Duration(seconds: 1),\n curve: Curves.fastOutSlowIn,\n opacity: _opacity,\n onEnd: () => print('End'),\n child: Icon(Icons.android, color: Colors.green, size: 60),\n ),\n ),\n ],\n );\n }\n}\n"},{"id":null,"widgetId":55,"name":"DropdownButton的样式指定","priority":2,"subtitle":" \n【isDense】 : 是否紧排 【bool】\n【iconSize】 : 图标大小 【double】\n【hint】 : 提示组件 【Widget】\n【iconEnabledColor】 : 图标颜色 【Color】","code":"import 'package:flutter/material.dart';\nclass StyleDropDownButton extends StatefulWidget {\n @override\n _StyleDropDownButtonState createState() => _StyleDropDownButtonState();\n}\n\nclass _StyleDropDownButtonState extends State {\n Color _color = Colors.red ;\n final _colors = [Colors.red, Colors.yellow, Colors.blue, Colors.green];\n final _info = [\"红色\", \"黄色\", \"蓝色\", \"绿色\"];\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n crossAxisAlignment: WrapCrossAlignment.center,\n children: [\n Container(\n margin: EdgeInsets.symmetric(horizontal: 20),\n width: 50,\n height: 50,\n color: _color??Colors.transparent,\n ),\n DropdownButton(\n hint: Text('请选择'),\n isDense: true,\n iconSize:20,\n iconEnabledColor:_color??Colors.orange,\n value: _color,\n items: _buildItems(),\n onChanged: (v) => setState(() => _color = v)),\n ],\n );\n }\n\n List> _buildItems() => _colors\n .map((e) => DropdownMenuItem(\n value: e,\n child: Text(\n _info[_colors.indexOf(e)],\n style: TextStyle(color: e),\n )))\n .toList();\n}\n"},{"id":null,"widgetId":55,"name":"DropdownButton基本用法","priority":1,"subtitle":" \n【value】 : 当前值 【T】\n【items】 : 下拉选框 【List>】\n【icon】 : 图标 【Widget】\n【elevation】 : 影深 【double】\n【onChanged】 : 选择条目事件 【Function(T)】\n【backgroundColor】 : 背景色 【Color】","code":"import 'package:flutter/material.dart';\nclass CustomDropDownButton extends StatefulWidget {\n @override\n _CustomDropDownButtonState createState() => _CustomDropDownButtonState();\n}\n\nclass _CustomDropDownButtonState extends State {\n Color _color = Colors.red;\n final _colors = [Colors.red, Colors.yellow, Colors.blue, Colors.green];\n final _info = [\"红色\", \"黄色\", \"蓝色\", \"绿色\"];\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n children: [\n Container(\n margin: EdgeInsets.symmetric(horizontal: 20),\n width: 50,\n height: 50,\n color: _color,\n ),\n DropdownButton(\n value: _color,\n elevation: 1,\n icon: Icon(\n Icons.expand_more,\n size: 20,\n color: _color,\n ),\n items: _buildItems(),\n onChanged: (v) => setState(() => _color = v)),\n ],\n );\n }\n\n List> _buildItems() => _colors\n .map((e) => DropdownMenuItem(\n value: e,\n child: Text(\n _info[_colors.indexOf(e)],\n style: TextStyle(color: e),\n )))\n .toList();\n}"},{"id":null,"widgetId":56,"name":"PopupMenuButton基本使用","priority":1,"subtitle":" \n【itemBuilder】 : 构造器 【PopupMenuItemBuilder】\n【offset】 : 偏移 【Offset】\n【color】 : 背景颜色 【Color】\n【shape】 : 形状 【ShapeBorder】\n【elevation】 : 影深 【double】\n【onCanceled】 : 取消事件 【Function()】\n【onSelected】 : 选择事件 【Function(T)】","code":"import 'package:flutter/material.dart';\nimport '../../../dialogs/dialog_about.dart';\nclass CustomPopupMenuButton extends StatefulWidget {\n @override\n _CustomPopupMenuButtonState createState() => _CustomPopupMenuButtonState();\n}\n\nclass _CustomPopupMenuButtonState extends State {\n final map = {\n \"关于\": Icons.info_outline,\n \"帮助\": Icons.help_outline,\n \"问题反馈\": Icons.add_comment,\n };\n\n @override\n Widget build(BuildContext context) {\n return PopupMenuButton(\n itemBuilder: (context) => buildItems(),\n offset: Offset(0, 50),\n color: Color(0xffF4FFFA),\n elevation: 1,\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.only(\n topLeft: Radius.circular(20),\n bottomRight: Radius.circular(20),\n topRight: Radius.circular(5),\n bottomLeft: Radius.circular(5),\n )),\n onSelected: (e) {\n print(e);\n if (e == '关于') {\n DialogAbout.show(context);\n }\n },\n onCanceled: () => print('onCanceled'),\n );\n }\n\n List> buildItems() {\n return map.keys\n .toList()\n .map((e) => PopupMenuItem(\n value: e,\n child: Wrap(\n spacing: 10,\n children: [\n Icon(\n map[e],\n color: Colors.blue,\n ),\n Text(e),\n ],\n )))\n .toList();\n }\n}\n"},{"id":null,"widgetId":160,"name":"Material的shape属性","priority":2,"subtitle":" \n【shape】 : 形状 【ShapeBorder】,","code":"import 'package:flutter/material.dart';\nclass ShapeMaterial extends StatelessWidget {\n\n final shapeMap = {\n 'BorderDirectional': BorderDirectional(\n top: BorderSide(\n color: Colors.white,\n ),\n start: BorderSide(color: Colors.black, width: 15),\n bottom: BorderSide(\n color: Colors.white,\n )),\n 'Border': Border(\n top: BorderSide(width: 5.0, color: Color(0xFFFFDFDFDF)),\n left: BorderSide(width: 5.0, color: Color(0xFFFFDFDFDF)),\n right: BorderSide(width: 5.0, color: Color(0xFFFF7F7F7F)),\n bottom: BorderSide(width: 5.0, color: Color(0xFFFF7F7F7F)),\n ),\n 'Circle': CircleBorder(\n side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),\n ),\n 'RoundedRectangleBorder': RoundedRectangleBorder(\n side: BorderSide(width: 1.0, color: Colors.black),\n borderRadius: BorderRadius.all(Radius.circular(15))),\n 'ContinuousRectangleBorder': ContinuousRectangleBorder(\n side: BorderSide.none,\n borderRadius: BorderRadius.circular(40.0),\n )\n };\n\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n runSpacing: 10,\n children: shapeMap.keys.map((e) => _buildMaterial(e)).toList());\n }\n\n Material _buildMaterial(String type) => Material(\n shadowColor: Colors.blue,\n shape: shapeMap[type],\n color: Colors.orange,\n elevation: 3,\n textStyle: TextStyle(color: Colors.white),\n child: Container(\n alignment: Alignment.center,\n width: 300,\n height: 60,\n child: Text(\n type,\n ),\n ),\n );\n}\n"},{"id":null,"widgetId":160,"name":"Material基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【type】 : 类型 【MaterialType】\n【elevation】 : 影深 【double】\n【shadowColor】 : 阴影颜色 【Color】\n【color】 : 颜色 【Color】","code":"import 'package:flutter/material.dart';\nclass CustomMaterial extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Wrap(\n spacing: 10,\n runSpacing: 10,\n children: MaterialType.values.map((e) => _buildMaterial(e)).toList());\n }\n\n Material _buildMaterial(MaterialType type) => Material(\n shadowColor: Colors.blue,\n type: type,\n color: Colors.orange,\n elevation: 3,\n child: Container(\n alignment: Alignment.center,\n width: 100,\n height: 60,\n child: Text(\n type.toString().split('.')[1],\n style: TextStyle(color: Colors.black),\n ),\n ),\n );\n}\n"},{"id":null,"widgetId":194,"name":"Scrollbar基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【controller】 : 控制器 【ScrollController】","code":"import 'package:flutter/material.dart';\nclass CustomScrollbar extends StatelessWidget {\n final data = [\n Colors.purple[50],\n Colors.purple[100],\n Colors.purple[200],\n Colors.purple[300],\n Colors.purple[400],\n Colors.purple[500],\n Colors.purple[600],\n Colors.purple[700],\n Colors.purple[800],\n Colors.purple[900],\n ];\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 200,\n child: Scrollbar(\n child: ListView(\n padding: EdgeInsets.symmetric(horizontal: 5),\n children: data\n .map((color) => Container(\n alignment: Alignment.center,\n width: 100,\n height: 50,\n color: color,\n child: Text(\n colorString(color),\n style: TextStyle(color: Colors.white, shadows: [\n Shadow(\n color: Colors.black,\n offset: Offset(.5, .5),\n blurRadius: 2)\n ]),\n ),\n ))\n .toList(),\n ),\n ),\n );\n }\n\n String colorString(Color color) =>\n \"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}\";\n}"},{"id":null,"widgetId":170,"name":"WillPopScope使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【onWillPop】 : 返回回调 【WillPopCallback】","code":"import 'package:flutter/material.dart';\nclass CustomWillPopScope extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: WillPopScope(child: (BackButton()),\n onWillPop: ()=>_willPop(context)),\n );\n }\n\n Future _willPop(context) async{\n return await showDialog(\n context: context,\n builder: (context) => AlertDialog(\n shape: RoundedRectangleBorder(\n borderRadius: BorderRadius.all(Radius.circular(10))),\n title: Text('提示'),\n content: Text('你确定要离开此页吗?'),\n actions: [\n FlatButton(\n onPressed: () => Navigator.of(context).pop(true),\n child: Text('确定'),\n ),\n FlatButton(\n onPressed: () => Navigator.of(context).pop(false),\n child: Text('取消'),\n ),\n\n ],\n ),\n ) ?? false;\n\n }\n}\n"},{"id":null,"widgetId":151,"name":"TableRowInkWell基本事件","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【onTap】 : 点击事件 【Function()】\n【onDoubleTap】 : 双击事件 【Function()】\n【onLongPress】 : 长按事件 【Function()】\n【onHighlightChanged】 : 高亮变化回调 【Function(bool)】","code":"import 'package:flutter/material.dart';\nclass CustomTableRowInkWell extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n var title = _ItemBean(\"单位称\", \"量纲\", \"单位\", \"单位名称\", \"单位符号\");\n var m = _ItemBean(\"长度\", \"L\", \"1m\", \"米\", \"m\");\n var kg = _ItemBean(\"质量\", \"M\", \"1Kg\", \"千克\", \"Kg\");\n var s = _ItemBean(\"时间\", \"T\", \"1s\", \"秒\", \"s\");\n var a = _ItemBean(\"安培\", \"Ι\", \"1A\", \"安培\", \"A\");\n var k = _ItemBean(\"热力学温度\", \"θ\", \"1K\", \"开尔文\", \"K\");\n var mol = _ItemBean(\"物质的量\", \"N\", \"1mol\", \"摩尔\", \"mol\");\n var cd = _ItemBean(\"发光强度\", \"J\", \"1cd\", \"坎德拉\", \"cd\");\n\n var data = <_ItemBean>[title, m, kg, s, a, k, mol, cd];\n\n return SingleChildScrollView(\n scrollDirection: Axis.horizontal,\n child: Table(\n columnWidths: const {\n 0: FixedColumnWidth(80.0),\n 1: FixedColumnWidth(80.0),\n 2: FixedColumnWidth(80.0),\n 3: FixedColumnWidth(80.0),\n 4: FixedColumnWidth(80.0),\n },\n defaultVerticalAlignment: TableCellVerticalAlignment.middle,\n border: TableBorder.all(\n color: Colors.orangeAccent, width: 1.0, style: BorderStyle.solid),\n children: data\n .map((item) => TableRow(children: [\n TableRowInkWell(\n onTap: () => print('onTap'),\n onDoubleTap: () => print('onDoubleTap'),\n onLongPress: () => print('onLongPress'),\n onHighlightChanged: (v) => print('onHighlightChanged:$v'),\n child: Center(\n child: Text(\n item.name,\n style: TextStyle(color: Colors.blue),\n )),\n ),\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Center(child: Text(item.symbol)),\n ),\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Center(child: Text(item.unitSymbol)),\n ),\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Center(child: Text(item.unitName)),\n ),\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: Center(child: Text(item.unit)),\n ),\n ]))\n .toList(),\n ),\n );\n }\n}\n\nclass _ItemBean {\n String name;\n String symbol;\n String unit;\n String unitName;\n String unitSymbol;\n\n _ItemBean(this.name, this.symbol, this.unit, this.unitName, this.unitSymbol);\n}\n\n"},{"id":null,"widgetId":135,"name":"MonthPicker基本使用","priority":1,"subtitle":" \n【selectedDate】 : 选中日期 【DateTime】\n【firstDate】 : 最前日期限制 【DateTime】\n【lastDate】 : 最后日期限制 【DateTime】\n【onChanged】 : 点击回调 【Function(DateTime)】","code":"import 'package:flutter/material.dart';\nclass CustomMonthPicker extends StatefulWidget {\n @override\n _CustomMonthPickerState createState() => _CustomMonthPickerState();\n}\n\nclass _CustomMonthPickerState extends State {\n DateTime _date = DateTime.now();\n\n @override\n Widget build(BuildContext context) {\n return Container(\n height: 350,\n child: MonthPicker(\n selectedDate: _date,\n onChanged: (date) => setState(() => _date = date),\n firstDate: DateTime(2018),\n lastDate: DateTime(2030),\n ),\n );\n }\n}\n"},{"id":null,"widgetId":143,"name":"CupertinoContextMenu基本使用","priority":1,"subtitle":" \n【child】 : 子组件 【Widget】\n【actions】 : 行为组件集 【List】\n【previewBuilder】 : 动画构造器 【ContextMenuPreviewBuilder】","code":"import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nclass CustomCupertinoContextMenu extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return Container(\n child: _buildCupertinoContextMenu(context),\n );\n }\n\n final info= ['保存图片','立刻呼叫','添加到收藏夹'];\n\n Widget _buildCupertinoContextMenu(context) => Container(\n decoration: BoxDecoration(\n image: DecorationImage(\n image: AssetImage('assets/images/sabar_bar.jpg'),\n fit: BoxFit.cover),\n borderRadius: BorderRadius.all(Radius.circular(50))),\n width: 100,\n height: 100,\n child: CupertinoContextMenu(\n child: Container(\n decoration: BoxDecoration(\n image: DecorationImage(\n image: AssetImage('assets/images/sabar_bar.jpg'),\n fit: BoxFit.cover),\n borderRadius: BorderRadius.all(Radius.circular(50))),\n ),\n actions: info.map((e)=>CupertinoContextMenuAction(\n child: Center(child: Text(e)),\n onPressed: () => Navigator.pop(context),\n )).toList())\n );\n}\n"}] ================================================ FILE: assets/data/web/widget.json ================================================ [{"id":1,"family":0,"name":"Container","nameCN":"容器组件","lever":5,"linkWidget":"74,85,80,78,70,123","info":"用于容纳单个子组件的容器组件。集成了若干个单子组件的功能,如内外边距、形变、装饰、约束等...","image":"assets/images/widgets/Container.png"},{"id":2,"family":0,"name":"Text","nameCN":"文字组件","lever":5,"linkWidget":"101,324","info":"用于显示文字的组件。拥有的属性非常多,足够满足你的使用需求,核心样式由style属性控制。","image":"assets/images/widgets/Text.png"},{"id":3,"family":0,"name":"Card","nameCN":"卡片组件","lever":3,"linkWidget":"160","info":" 基于Material组件实现,用于将单个组件卡片化。并使其具有投影效果,可加外边距,也可以自定义卡片形状。","image":"assets/images/widgets/Card.png"},{"id":4,"family":0,"name":"FlutterLogo","lever":1,"linkWidget":"","nameCN":"Flutter图标","info":"用于展示Flutter图标组件。可定义颜色、尺寸、展示模式等信息,是一个非常简单的组件。","image":"assets/images/widgets/FlutterLogo.png"},{"id":5,"family":0,"name":"Banner","lever":1,"linkWidget":"","nameCN":"角标组件","info":"用于角标显示的组件。可容纳一个子组件,可选择方位添加角标及信息文字,可设置颜色。","image":"assets/images/widgets/Banner.png"},{"id":6,"family":0,"name":"Icon","lever":2,"linkWidget":"7,30,125","nameCN":"图标组件","info":"用于图标显示的组件。可指定图标资源、大小、颜色。非常简单,但是非常用","image":"assets/images/widgets/Icon.png"},{"id":7,"family":0,"name":"ImageIcon","linkWidget":"6,30,125","nameCN":"容器","lever":1,"info":"用于将一个图片变为纯色的组件。可指定大小、颜色。","image":"assets/images/widgets/ImageIcon.png"},{"id":8,"family":0,"name":"FadeInImage","nameCN":"淡入图片","linkWidget":"38","lever":2,"info":"透明渐变地加载一张图片。可指定占位图片、进退的动画曲线、时间、宽高、fit类型、对齐方式、重复方式等。","image":""},{"id":9,"family":0,"name":"CircleAvatar","nameCN":"圆形组件","linkWidget":"","lever":4,"info":"可将一张图片变成圆形,并且中间可以放置一个组件。可指定半径、前景色、背景色等。","image":""},{"id":10,"family":0,"name":"Visibility","nameCN":"显隐组件","linkWidget":"71","lever":3,"info":"控制一个组件显示或隐藏,可设置隐藏后的占位组件。与其功能相似的由OffStage组件。","image":"assets/images/widgets/Visibility.png"},{"id":11,"family":0,"name":"Chip","nameCN":"小条组件","linkWidget":"12,13,14,15,153","lever":4,"info":"一个横向的圆边小条,可以包含左中右三个组件。可以指定颜色、阴影色和点击事件。","image":"assets/images/widgets/Chip.png"},{"id":12,"family":0,"name":"ChoiceChip","nameCN":"选择小条","lever":3,"linkWidget":"11,13,14,15,153","info":"和Chip组件类似的样式,有一些选择的属性。可以指定选中时的颜色、阴影色和选择事件。","image":"assets/images/widgets/ChoiceChip.png"},{"id":13,"family":0,"name":"ActionChip","nameCN":"事件小条","lever":3,"linkWidget":"11,12,14,15,153","info":"和Chip组件类似的样式,有一些点击的属性。可以指定点击时的阴影深、点击事件。","image":"assets/images/widgets/ActionChip.png"},{"id":14,"family":0,"name":"InputChip","nameCN":"综合小条","linkWidget":"11,12,13,15,153","lever":4,"info":"和Chip组件类似的样式,集成了点击、删除、选择事件为一体。注意:点击事件和选择事件不能同时存在。","image":"assets/images/widgets/InputChip.png"},{"id":15,"family":0,"name":"FilterChip","linkWidget":"11,12,13,14,153","nameCN":"过滤小条","lever":4,"info":"和Chip组件类似的样式,具有选中与否的属性和选中事件。当选中时左侧组件上层会被✔️遮罩。","image":"assets/images/widgets/FilterChip.png"},{"id":16,"family":0,"name":"ListTile","nameCN":"列表瓦片","linkWidget":"162,334","lever":3,"info":"Flutter提供的一个通用列表条目结构,为左中右结构。相应位置可插入组件,可以很方便地应对特定的条目。","image":"assets/images/widgets/ListTile.png"},{"id":17,"family":0,"name":"CheckboxListTile","nameCN":"复选瓦片","linkWidget":"39","lever":3,"info":"Flutter提供的一个通用列表条目结构,为左中结构,尾部是一个CheckBox。相应位置可插入组件,可以很方便地应对特定的条目。","image":"assets/images/widgets/CheckBoxListTile.png"},{"id":18,"family":0,"name":"SwitchListTile","nameCN":"切钮瓦片","linkWidget":"40","lever":3,"info":"Flutter提供的一个通用列表条目结构,为左中结构,尾部是一个Switch。相应位置可插入组件,可以很方便地应对特定的条目。","image":"assets/images/widgets/SwitchListTile.png"},{"id":19,"family":0,"name":"RadioListTile","nameCN":"选钮瓦片","linkWidget":"45","lever":3,"info":"Flutter提供的一个通用列表条目结构,为中右结构,尾部是一个Radio。相应位置可插入组件,可以很方便地应对特定的条目。","image":"assets/images/widgets/RadioListTile.png"},{"id":20,"family":0,"name":"GridTileBar","nameCN":"网格瓦片头","linkWidget":"21","lever":2,"info":"Flutter提供的一个通用头结构,为左中右结构。相应位置可插入组件,可以很方便地应对特定的条目,相比ListTile而言,属性较少。","image":"assets/images/widgets/GridTileBar.png"},{"id":21,"family":0,"name":"GridTile","nameCN":"网格瓦片","linkWidget":"20","lever":3,"info":"Flutter提供的一个通用列表条目结构,可指定头、尾、子组件,常用于网格列表。","image":"assets/images/widgets/GridTile.png"},{"id":22,"family":0,"name":"UserAccountsDrawerHeader","nameCN":"展示头","linkWidget":"154","lever":3.8,"info":"Flutter提供的一个通用展示结构,相应位置可插入组件,可以很方便地应对特定的条目,常用于Drawer中。","image":"assets/images/widgets/UserAccountsDrawerHeader.png"},{"id":23,"family":0,"name":"MaterialButton","nameCN":"材料按钮","linkWidget":"25,26,27,326,175","lever":4,"info":"基于RawMaterialButton实现的通用Material按钮。可盛放一个子组件,能定义颜色、形状等表现,可接收点击和长按事件。","image":""},{"id":24,"family":1,"name":"CupertinoButton","nameCN":"iOS按钮","linkWidget":"23","lever":3,"info":"iOS风格的按钮。可指定颜色、点击时透明度、内边距、圆角等。可接收点击事件。","image":""},{"id":25,"family":0,"name":"FlatButton","nameCN":"平按钮","linkWidget":"24,26,27,175","lever":3,"info":"无阴影的平按钮,基于MaterialButton实现,所有属性和MaterialButton类似。","image":""},{"id":26,"family":0,"name":"RaisedButton","nameCN":"浮起按钮","linkWidget":"24,25,27,175","lever":3,"info":"有阴影的浮起按钮,基于MaterialButton实现,所有属性和MaterialButton类似。","image":""},{"id":27,"family":0,"name":"OutlineButton","nameCN":"线框按钮","linkWidget":"23,24,25,175","lever":3,"info":"边框样式按钮,基于MaterialButton实现,所有属性和MaterialButton类似。","image":""},{"id":28,"family":0,"name":"FloatingActionButton","nameCN":"浮动按钮","linkWidget":"64","lever":4,"info":"浮动按钮,一般用于Scaffold中,可摆放在特定位置。可盛放一个子组件,接收点击、可定义颜色、形状等。","image":""},{"id":29,"family":0,"name":"ButtonBar","nameCN":"按钮栏","linkWidget":"","lever":3,"info":"接收组件列表,常用于盛放若干个按钮。可指定对齐方式、边距等信息。","image":""},{"id":30,"family":0,"name":"IconButton","nameCN":"图标按钮","linkWidget":"6","lever":2,"info":"可点击的图标按钮,可指定图标信息、内边距、大小、颜色等,接收点击事件。","image":""},{"id":31,"family":0,"name":"BackButton","nameCN":"返回按钮","linkWidget":"30","lever":1,"info":"一个具有返回功能的IconButton,返回图标不可更改。在iOS和Android中表现不同","image":""},{"id":32,"family":0,"name":"CloseButton","nameCN":"关闭按钮","linkWidget":"30","lever":1.0,"info":"一个具有关闭功能的IconButton,关闭图标不可更改。","image":""},{"id":33,"family":0,"name":"ToggleButtons","nameCN":"切换按钮组","linkWidget":"332,262","lever":4,"info":"接收组件列表,可指定边线、圆角、颜色等属性。根据具体逻辑,可以实现多个按钮单选或多选的需求。","image":""},{"id":34,"family":0,"name":"Divider","nameCN":"水平分割线","linkWidget":"35,329","lever":2,"info":"水平分割线,可指定颜色、高度、粗细、左右边距信息,常用与列表的item分割线。","image":""},{"id":35,"family":0,"name":"VerticalDivider","nameCN":"竖直分割线","linkWidget":"34,329","lever":2,"info":"竖直分割线,可指定颜色、宽度、粗细、上下边距信息,常用与列表的item分割线。","image":""},{"id":36,"family":0,"name":"Placeholder","nameCN":"占位组件","linkWidget":"","lever":1,"info":"一个矩形和叉叉的占位组件,可指定颜色、线宽、宽高等属性。","image":""},{"id":37,"family":0,"name":"GridPager","nameCN":"网格线组件","linkWidget":"","lever":2,"info":"可容纳一个组件,在其上绘制网格。可指定颜色、线宽、间距等属性。","image":""},{"id":38,"family":1,"name":"Image","nameCN":"图片组件","linkWidget":"8,87","lever":5,"info":"用于显示一张图片,可以从文件、内存、网络、资源里加载。可以指定适应方式、样式、颜色混合模式、重复模式等","image":""},{"id":39,"family":1,"name":"Checkbox","nameCN":"复选框","linkWidget":"17","lever":4,"info":"复选框组件,常用于配置的切换,可指定颜色,接收状态变化回调,也可指定三态。","image":""},{"id":40,"family":1,"name":"Switch","nameCN":"切钮","linkWidget":"41,18","lever":4,"info":"切换选钮,常用于配置的切换,可指定小圆颜色、图片,滑槽颜色等,接收状态变化回调。","image":""},{"id":41,"family":1,"name":"CupertinoSwitch","nameCN":"iOS切钮","linkWidget":"40","lever":3,"info":"iOS风格的切换选钮,常用于配置的切换,可指定颜色,接收状态变化回调。","image":""},{"id":42,"family":1,"name":"Slider","nameCN":"滑块","linkWidget":"43,44,331","lever":4,"info":"滑块组件,可以在指定的最大值和最小值之间拖动选择。可指定颜色、分段数及显示的标签,接收进度变化回调。","image":""},{"id":43,"family":1,"name":"CupertinoSlider","linkWidget":"42","nameCN":"iOS滑块","lever":3,"info":"iOS风格的滑块组件,可以在指定的最大值和最小值之间拖动选择。可指定颜色,接收进度变化回调。","image":""},{"id":44,"family":1,"name":"RangeSlider","nameCN":"范围滑块","linkWidget":"42","lever":4,"info":"范围滑块组件,支持两点拖动,获取之间的范围。可指定颜色、分段数及显示的标签,接收进度变化回调。","image":""},{"id":45,"family":1,"name":"Radio","nameCN":"选钮","linkWidget":"19","lever":4,"info":"由于选中和未选择状态的圆钮,多个Radio根据逻辑可以实现单选或多选的需求。可指定颜色,接收状态变化回调。","image":""},{"id":46,"family":1,"name":"CircularProgressIndicator","nameCN":"圆形进度","linkWidget":"47,48","lever":3,"info":"圆形的进度显示,可指定颜色、线宽、进度等属性。value为null时会不停旋转。","image":""},{"id":47,"family":1,"name":"LinearProgressIndicator","nameCN":"水平进度","linkWidget":"46,48","lever":3,"info":"直线型的进度显示,可指定颜色、进度等属性。value为null时会不停旋转。","image":""},{"id":48,"family":1,"name":"CupertinoActivityIndicator","nameCN":"iOS指示器","linkWidget":"46,47","lever":2,"info":"iOS样式的loading显示组件,可指定半径和是否旋转。","image":""},{"id":49,"family":1,"name":"RefreshIndicator","nameCN":"刷新指示器","linkWidget":"","lever":4,"info":"内部嵌套可滑动区域,下滑时会显示刷新图标,松手后可以执行指定的异步方法。可指定颜色、到顶端距离等属性。","image":""},{"id":50,"family":1,"name":"Tooltip","nameCN":"提示工具","linkWidget":"","lever":3,"info":"由于显示提示信息的组件,长按时显示信息。可指定边距、显示时长、文字样式、装饰灯属性。","image":""},{"id":51,"family":1,"name":"ExpandIcon","nameCN":"展开图标","linkWidget":"66,125","lever":1,"info":"一个展开按钮,点击时会自己执行旋转180的动画。可指定颜色、大小、边距,接收点击事件。","image":""},{"id":52,"family":1,"name":"ExpansionTile","nameCN":"展开瓦片","linkWidget":"178","lever":3,"info":"一个通用的展开栏,可在指定的部位安放组件,点击时会折叠显隐下方组件。接收折叠时事件。","image":""},{"id":53,"family":1,"name":"SelectableText","nameCN":"可选择文字","linkWidget":"2","lever":3,"info":"可选择的文字,可以选择、复制。可指定浮标的颜色、大小、文字样式、对齐方式等。","image":""},{"id":54,"family":1,"name":"TextField","nameCN":"输入框","linkWidget":"199","lever":5,"info":"由于输入的组件,拥有复杂的属性。可指定控制器、文字样式、装饰线、行数限制、游标样式等。接收输入变化、完成输入等事件。","image":""},{"id":55,"family":1,"name":"DropdownButton","nameCN":"下拉按钮","linkWidget":"181","lever":4,"info":"用于下拉选择的按钮,可指定图标、影深、提示等属性,接收选中变化的事件。","image":""},{"id":56,"family":1,"name":"PopupMenuButton","nameCN":"菜单按钮","linkWidget":"174","lever":4,"info":"弹出菜单栏,可指定偏移、颜色、影深、形状等属性。接收item选中的事件和取消选择事件。","image":""},{"id":57,"family":1,"name":"AppBar","nameCN":"应用头栏","linkWidget":"64","lever":4,"info":"一个应用顶部栏的通用结构,可在指定的部位放置相应的组件,常用于Scaffold组件中。","image":""},{"id":58,"family":1,"name":"TabBar","nameCN":"标签栏","linkWidget":"57,59,148","lever":3,"info":"可滑动和点击标签栏,通常用于AppBar的底部,可与TabBarView联用,实现滑页的效果。","image":""},{"id":59,"family":1,"name":"TabBarView","nameCN":"标签页","linkWidget":"58","lever":2,"info":"通常与TabBar联用,实现滑页的效果。一般不单独使用。","image":""},{"id":60,"family":1,"name":"BottomNavigationBar","nameCN":"底部导航","linkWidget":"61","lever":4,"info":"一个底部导航栏,通常用于Scaffold组件的底部,可指定颜色和模式,接受点击回调,可与PageView实现切页效果。","image":""},{"id":61,"family":1,"name":"BottomAppBar","nameCN":"底部导航","linkWidget":"60","lever":4,"info":"一个可凹嵌的底部导航栏,通常用于Scaffold组件的底部,可指定颜色、影深、形状等属性,可与PageView实现切页效果。","image":""},{"id":62,"family":1,"name":"CupertinoNavigationBar","nameCN":"iOS导航","linkWidget":"","lever":3,"info":"一个iOS风格的应用顶部栏的通用结构,可在指定的部位放置相应的组件。可指定背景色、间距、边线等属性。","image":""},{"id":63,"family":1,"name":"CupertinoTabBar","nameCN":"iOS页签","linkWidget":"158","lever":3,"info":"一个iOS风格的TabBar,通常用于CupertinoTabScaffold。可指定颜色、图标大小、边线等数据。接收item的点击事件。","image":""},{"id":64,"family":1,"name":"Scaffold","nameCN":"脚手架","linkWidget":"57,60,61","lever":4,"info":"一个通用app结构,包括上、下、左、右、中、浮动按钮部位,对应位置可盛放组件。","image":""},{"id":65,"family":1,"name":"MaterialApp","nameCN":"Material应用","linkWidget":"64","lever":5,"info":"Material应用的顶级组件,包含路由生成器、主题、语言、主页等属性。","image":""},{"id":66,"family":2,"name":"ClipOval","nameCN":"椭圆裁剪","linkWidget":"67,68,69","lever":3,"info":"可容纳一个子组件,并将其以宽高为长轴和短轴进行椭圆裁切。","image":""},{"id":67,"family":2,"name":"ClipRect","nameCN":"矩形裁剪","linkWidget":"66,68,69","lever":3,"info":"可容纳一个子组件,并将其进行矩形裁切。可借助SizedBox、Align、AspectRadio等限定组件进行定域。","image":""},{"id":68,"family":2,"name":"ClipRRect","nameCN":"圆角矩形裁剪","linkWidget":"66,67,69","lever":3,"info":"可容纳一个子组件,并将其进行圆角矩形裁剪。指定borderRadius作为边角半径。","image":""},{"id":69,"family":2,"name":"ClipPath","nameCN":"路径裁剪","linkWidget":"66,67,68","lever":5,"info":"可容纳一个子组件,并将其按指定路径进行裁剪。可以自定义路径形状,是一个很灵活的裁剪组件。","image":""},{"id":70,"family":2,"name":"DecoratedBox","nameCN":"装饰盒","linkWidget":"1","lever":4,"info":"可容纳一个子组件,可将其进行装饰。核心属性为decoration,可设置边线、渐变、阴影、背景图等。","image":""},{"id":71,"family":2,"name":"Offstage","nameCN":"消失组件","linkWidget":"10","lever":3,"info":"可容纳一个子组件,可更改其的消失与否。offstage属性为true表示隐藏。","image":""},{"id":72,"family":2,"name":"RotatedBox","nameCN":"旋转盒","linkWidget":"90","lever":2,"info":"可容纳一个子组件,将其沿顺时针旋转quarterTurns*90°。","image":""},{"id":73,"family":2,"name":"Opacity","nameCN":"透明化","linkWidget":"89,118","lever":3,"info":"可容纳一个子组件,将其透明度变为opacity值, opacity在0~1之间。","image":""},{"id":74,"family":2,"name":"Padding","nameCN":"边距组件","linkWidget":"1,191","lever":4,"info":"可容纳一个子组件,添加自身内边距来限制孩子组件的占位,核心属性为padding。","image":""},{"id":75,"family":2,"name":"Baseline","nameCN":"基线组件","linkWidget":"2","lever":2,"info":"可容纳一个子组件,通过控制基线高度来控制子组件的位置。一般用于文字组件。","image":""},{"id":76,"family":2,"name":"SizedBox","nameCN":"定尺寸盒","linkWidget":"1","lever":4,"info":"可容纳一个子组件,通过指定宽高限定子组件容身区域。","image":""},{"id":77,"family":2,"name":"AspectRatio","nameCN":"比例盒","linkWidget":"82","lever":3,"info":"可容纳一个子组件,通过指定宽高比aspectRatio,来限定子组件容身区域。","image":""},{"id":78,"family":2,"name":"Transform","nameCN":"变换","linkWidget":"1","lever":4,"info":"可容纳一个子组件,可以通过一个4*4的变换矩阵对子组件进行变换。","image":""},{"id":79,"family":2,"name":"LimitedBox","nameCN":"限制盒","linkWidget":"80","lever":3,"info":"可容纳一个子组件,通过指定最大宽高来限定子组件容身区域。","image":""},{"id":80,"family":2,"name":"ConstrainedBox","nameCN":"约束盒","linkWidget":"1,79,81","lever":3,"info":"可容纳一个子组件,通过指定最大、最小宽高,来限定子组件容身区域。","image":""},{"id":81,"family":2,"name":"UnconstrainedBox","nameCN":"约束盒","linkWidget":"80","lever":3,"info":"可容纳一个子组件,并解除该组件的所有区域约束,展现自我尺寸。","image":""},{"id":82,"family":2,"name":"FractionallySizedBox","nameCN":"分率盒","linkWidget":"77","lever":3,"info":"可容纳一个子组件,指定宽高分率,限定子组件区域为父容器宽高*各分率,及对齐方式alignment。","image":""},{"id":83,"family":2,"name":"OverflowBox","nameCN":"溢出盒","linkWidget":"84","lever":4,"info":"可容纳一个子组件,且子组件允许溢出父组件区域,可以指定宽高的最大最小区域进行限定,拥有对齐属性alignment。","image":""},{"id":84,"family":2,"name":"SizedOverflowBox","nameCN":"尺寸溢出盒","linkWidget":"83","lever":2.8,"info":"可容纳一个子组件,且子组件允许溢出父组件区域,可以通过size属性对子组件进行偏移,拥有对齐属性alignment。","image":""},{"id":85,"family":2,"name":"Align","nameCN":"对齐组件","linkWidget":"1,86,111,120","lever":5,"info":"可容纳一个子组件,可以通过alignment让子组件,定位在父组件宽高的任何指定分率出。","image":""},{"id":86,"family":2,"name":"Center","nameCN":"居中组件","linkWidget":"85","lever":3,"info":"可容纳一个子组件,并使其居中于父组件,是Align组件的一种精简模式。","image":""},{"id":87,"family":2,"name":"FittedBox","nameCN":"适应盒","linkWidget":"38","lever":4,"info":"可容纳一个子组件,使用fit属性决定子组件区域相当于父组件的适应模式,拥有对齐属性alignment。","image":""},{"id":88,"family":2,"name":"ColorFiltered","nameCN":"滤色器","linkWidget":"277,38","lever":5,"info":"可容纳一个子组件,可以并将组件按照29中叠色模式和任意组件混合,强大到我不知道该说什么好。app一键全灰了解一下。","image":""},{"id":89,"family":2,"name":"FadeTransition","nameCN":"透明变换","linkWidget":"73,118","lever":3,"info":"可容纳一个子组件,并使其进行透明度渐变动画,需要提供动画器opacity。","image":""},{"id":90,"family":1,"name":"RotationTransition","nameCN":"旋转变换","linkWidget":"72","lever":3,"info":"可容纳一个子组件,并使其进行旋转动画,需要提供动画器turns,拥有alignment属性。","image":""},{"id":91,"family":1,"name":"ScaleTransition","nameCN":"缩放变换","linkWidget":"","lever":3,"info":"可容纳一个子组件,并使其进行缩放动画,需要提供动画器scale,拥有alignment属性。","image":""},{"id":92,"family":1,"name":"SizeTransition","nameCN":"尺寸变换","linkWidget":"201","lever":3,"info":"可容纳一个子组件,并使其进行尺寸动画,需要提供动画器sizeFactor,可指定尺寸变化轴及轴向的axisAlignment。","image":""},{"id":93,"family":1,"name":"PositionedTransition","nameCN":"位置变换","linkWidget":"97","lever":3,"info":"只能用于Stack中,可容纳一个子组件,让其在两个矩形间进行位置动画,需要提供动画器rect。","image":""},{"id":94,"family":3,"name":"Flex","nameCN":"弹性布局","linkWidget":"95,96,106,107,109","lever":5,"info":"Row和Column的父类,Flutter中最强大的布局方式。可容纳多个组件,可与Spacer、Expended、Flexible组件联用进行灵活布局","image":""},{"id":95,"family":3,"name":"Row","nameCN":"行布局","linkWidget":"94,96","lever":4,"info":"排布方向为横向的Flex布局,可容纳多个组件。其他属性全部一致,详见Flex。","image":""},{"id":96,"family":3,"name":"Column","nameCN":"列布局","linkWidget":"94,95","lever":4,"info":"排布方向为竖向的Flex布局,可容纳多个组件。其他属性全部一致,详见Flex。","image":""},{"id":97,"family":3,"name":"Stack","nameCN":"堆叠布局","linkWidget":"94,95,161","lever":5,"info":"可容纳多个组件,以堆叠的方式摆放子组件,后者居上。拥有alignment属性,可与Positioned组件联合使用,精确定位。","image":""},{"id":98,"family":3,"name":"Wrap","nameCN":"包裹布局","linkWidget":"94,95","lever":5,"info":"可容纳多个组件,按照指定方向依次排布,可以很方便处理孩子的间距,当越界时可以自动换行。拥有主轴和交叉轴的对齐方式,比较灵活。","image":""},{"id":99,"family":3,"name":"Flow","nameCN":"流动布局","linkWidget":"98,94","lever":5,"info":"可容纳多个组件, 需要自己制定排布的代理,可以高强度自定义组件的排布,实现普通布局无法达到的效果。布局王者,当之无愧。","image":""},{"id":100,"family":1,"name":"AnimatedCrossFade","nameCN":"组件切换","linkWidget":"116","lever":5,"info":"将两个组件切换时呈现动画效果,可指定动画曲线、时长、对齐方式等属性。是一个非常有用的组件。","image":""},{"id":101,"family":3,"name":"RichText","nameCN":"富文本","linkWidget":"2","lever":5,"info":"可以容纳多种文字样式或各种组件的富文本组件,应用较为广泛。","image":""},{"id":102,"family":0,"name":"DataTable","nameCN":"数据表格","linkWidget":"110","lever":3,"info":"一个表格组件,可以制订逻辑进行点击、修改、排序等操作。","image":""},{"id":103,"family":1,"name":"Draggable","nameCN":"可拖拽组件","linkWidget":"104,105","lever":4,"info":"可以让组件在界面上任意拖拽,可存放一个泛型T的数据。通常和DragTarget组合使用,来完成拖拽效果。","image":""},{"id":104,"family":1,"name":"DragTarget","nameCN":"拖拽目标","linkWidget":"103,105","lever":4,"info":"一个拖拽的目标区域,可接收Draggable组件的信息。可以获取拖拽时的回调。","image":""},{"id":105,"family":1,"name":"LongPressDraggable","nameCN":"拖拽目标","linkWidget":"103,104","lever":4,"info":"长按时让组件在界面上任意拖拽,可存放一个泛型T的数据。通常和DragTarget组合使用,来完成拖拽效果。","image":""},{"id":106,"family":5,"name":"Expanded","nameCN":"延展组件","linkWidget":"94,109","lever":4,"info":"父类是Flexible,相当于一个fit类型为tight的Flexible组件。可嵌套孩子利用剩余空间对占位空间进行延展。","image":""},{"id":107,"family":0,"name":"Spacer","nameCN":"空间组件","linkWidget":"94","lever":3,"info":"只能用于Row、Column和Flex布局中,可利用剩余空间进行占位,使用flex属性可以给多个Spacer按比例分配空间。","image":""},{"id":108,"family":5,"name":"Positioned","nameCN":"定位组件","linkWidget":"97,159,121","lever":3,"info":"只能用于Stack中,可以指定左上右下的距离对某个组件进行位置精确安放。","image":""},{"id":109,"family":5,"name":"Flexible","nameCN":"灵活组件","linkWidget":"94,106","lever":3,"info":"只能用于只能用于Row、Column和Flex布局中,可嵌套孩子利用剩余空间对占位空间进行延展,也可指定适应类型。","image":""},{"id":110,"family":6,"name":"Table","nameCN":"表格组件","linkWidget":"102","lever":4,"info":"用于展示表格的组件,可指定边线、列宽、文字方向等属性,核心对象类型是TableRow。","image":""},{"id":111,"family":1,"name":"AlignTransition","nameCN":"对齐变换","linkWidget":"85,120","lever":3,"info":"AnimatedWidget的子类,使用Alignment类型的动画器让子组件在两个Alignment对象之间进行过渡动画。","image":""},{"id":112,"family":1,"name":"SlideTransition","nameCN":"滑动变换","linkWidget":"","lever":3,"info":"AnimatedWidget的子类,使用Offset类型的动画器让子组件在两个Offset对象之间进行过渡动画。","image":""},{"id":113,"family":1,"name":"DecoratedBoxTransition","nameCN":"装饰变换","linkWidget":"70","lever":3,"info":"AnimatedWidget的子类,使用Decorated类型的动画器让子组件在两个Decorated对象之间进行过渡动画。","image":""},{"id":114,"family":1,"name":"DefaultTextStyleTransition","nameCN":"文字样式变换","linkWidget":"124,324","lever":3,"info":"AnimatedWidget的子类,使用TextStyle类型的动画器让文字组件在两个TextStyle对象之间进行过渡动画。","image":""},{"id":115,"family":1,"name":"RelativePositionedTransition","nameCN":"矩形位置变换","linkWidget":"70","lever":3,"info":"AnimatedWidget的子类,使用Rect类型的动画器让子组件在两个Rect对象之间进行过渡动画。","image":""},{"id":116,"family":1,"name":"AnimatedSwitcher","nameCN":"动画切换","linkWidget":"100","lever":4,"info":"当子组件变化时执行动画,需要指定子组件的key进行标识。动画方式可以自定义,能指定动画时长、动画曲线等属性。","image":""},{"id":117,"family":1,"name":"AnimatedList","nameCN":"动画列表","linkWidget":"162","lever":3,"info":"强化版的ListView,可以对item进行动画处理。比如在添加、删除是item的动画。","image":""},{"id":118,"family":1,"name":"AnimatedOpacity","nameCN":"透明动画","linkWidget":"89,73","lever":3,"info":"能让子组件进行Opacity(透明度)动画,可指定时长和曲线,有动画结束事件。","image":""},{"id":119,"family":1,"name":"AnimatedPadding","nameCN":"边距动画","linkWidget":"74","lever":3,"info":"能让子组件进行Padding(内边距)动画,可指定时长和曲线,有动画结束事件。","image":""},{"id":120,"family":1,"name":"AnimatedAlign","nameCN":"对齐动画","linkWidget":"85,111","lever":3,"info":"能让子组件进行Align(对齐)动画,可指定时长和曲线,有动画结束事件。","image":""},{"id":121,"family":1,"name":"AnimatedPositioned","nameCN":"定位动画","linkWidget":"108,93,122","lever":3,"info":"能让子组件进行Positioned(定位)动画,可指定时长和曲线,有动画结束事件。只能用于Stack之中。","image":""},{"id":122,"family":1,"name":"AnimatedPositionedDirectional","nameCN":"方向定位动画","linkWidget":"121,159","lever":3,"info":"能让子组件进行PositionedDirectional(方向定位)动画,可指定时长和曲线,有动画结束事件。只能用于Stack之中。","image":""},{"id":123,"family":1,"name":"AnimatedContainer","nameCN":"容器动画","linkWidget":"1","lever":5,"info":"集合alignment、padding、color、decoration、width、height、constraints、margin、transform于一身,这些属性皆可动画,可指定时长和曲线,有动画结束事件。","image":""},{"id":124,"family":1,"name":"AnimatedDefaultTextStyle","nameCN":"容器动画","linkWidget":"114,324","lever":3,"info":"能让子文字组件进行TextStyle(文字样式)动画,可指定时长和曲线,有动画结束事件。","image":""},{"id":125,"family":0,"name":"AnimatedIcon","nameCN":"图标动画","linkWidget":"6","lever":3,"info":"使用AnimatedIcons的图标数据,可以根据一个动画控制器来使图标进行动画效果。可指定图标大小、颜色等。","image":""},{"id":126,"family":0,"name":"Dialog","nameCN":"对话框","linkWidget":"","lever":2,"info":"最简易的对话框面板,包含一个内容组件,可指定影深、背景色、形状等属性。","image":""},{"id":127,"family":0,"name":"AlertDialog","nameCN":"弹出对话框","linkWidget":"129","lever":3,"info":"一个通用的对话框结构,可指定头、中、尾处的组件。拥有标题、内容的文字样式和边距,影深、形状等属性。","image":""},{"id":128,"family":0,"name":"SimpleDialog","nameCN":"简单对话框","linkWidget":"133","lever":3,"info":"一个简单的对话框结构,可指定头、中处的组件。拥有拥有标题、内容的文字样式和边距,影深、形状等属性。常与SimpleDialogOption联用。","image":""},{"id":129,"family":0,"name":"CupertinoAlertDialog","nameCN":"iOS对话框","linkWidget":"127","lever":3,"info":"iOS风格的通用的对话框结构,可指定头、中、尾处的组件。","image":""},{"id":130,"family":0,"name":"AboutDialog","nameCN":"弹出对话框","linkWidget":"193","lever":1,"info":"应用的简介对话框,可指定应用图标、应用名、应用版本号等信息和内部的子组件列表,点击左侧按钮可以跳转到证书页。","image":""},{"id":131,"family":0,"name":"CupertinoActionSheet","nameCN":"iOS行为单","linkWidget":"132","lever":3,"info":"iOS风格的弹出选择结构,可放多的按钮,一般与CupertinoActionSheetAction联用。","image":""},{"id":132,"family":0,"name":"CupertinoActionSheetAction","nameCN":"iOS行为单按键","linkWidget":"131","lever":1,"info":"一个按钮,应用场景很少,通常用于CupertinoActionSheet中,接收点击事件。","image":""},{"id":133,"family":0,"name":"SimpleDialogOption","nameCN":"简单对话框选项","linkWidget":"128","lever":1,"info":"一个按钮,应用场景很少,通常用于SimpleDialog中,接收点击事件。","image":""},{"id":134,"family":0,"name":"DayPicker","nameCN":"日期选择器","linkWidget":"135,136","lever":3,"info":"日期的选择组件,可指定当前日期、选中日期、展示月份等,接收日期选中事件。","image":""},{"id":135,"family":1,"name":"MonthPicker","nameCN":"月份选择器","linkWidget":"134,136","lever":3,"info":"月份的选择组件,自带上下月切换的监听。可指定选择的日期范围、选中日期等,接收日期选中事件。","image":""},{"id":136,"family":1,"name":"YearPicker","nameCN":"年份选择器","linkWidget":"134,135","lever":3,"info":"年份的选择组件,长相比较寒酸。可指定选择的日期范围、选中日期等,接收每份选中事件","image":""},{"id":137,"family":1,"name":"CupertinoDatePicker","nameCN":"iOS日期选择器","linkWidget":"138","lever":3,"info":"高大上的滑滚日期选择器,可指定选择的类型、日期范围等,接收日期选中事件。","image":""},{"id":138,"family":1,"name":"CupertinoTimerPicker","nameCN":"iOS时间选择器","linkWidget":"137","lever":3,"info":"高大上的滑滚时间选择器,可指定选择的类型、初始时间、背景色等,接收时间选中事件。","image":""},{"id":139,"family":1,"name":"CupertinoPicker","nameCN":"iOS选择器","linkWidget":"179","lever":3,"info":"高大上的柱面滑动选择器,精妙十足,可指定很多配置属性,接收滑动时选中事件。","image":""},{"id":140,"family":1,"name":"SnackBar","nameCN":"信息提示条","linkWidget":"141,142","lever":4,"info":"作为组件来说是一个简单的结构组件,可指定形状、影深、背景色等。一般通过ScaffoldState的showSnackBar方法从底部弹出。","image":""},{"id":141,"family":1,"name":"SnackBarAction","nameCN":"信息提示条按钮","linkWidget":"140","lever":1,"info":"一般只用于SnackBar中,接受点击事件。点击一次后该按钮就会被禁用,可以指定颜色和禁用时颜色。","image":""},{"id":142,"family":1,"name":"BottomSheet","nameCN":"底部抽屉","linkWidget":"140","lever":4,"info":"作为组件来说是一个简单的结构组件,可指定形状、影深、背景色、内部组件构造器等。一般通过ScaffoldState的showBottomSheet方法从底部弹出。","image":""},{"id":143,"family":1,"name":"CupertinoContextMenu","nameCN":"ios弹出菜单","linkWidget":"144","lever":5,"info":"一个华丽的iOS风格按钮弹出框,长按时会以动画的形式弹出菜单面板,通常和CupertinoContextMenuAction联用。","image":""},{"id":144,"family":1,"name":"CupertinoContextMenuAction","nameCN":"ios弹出菜单按钮","linkWidget":"143","lever":1,"info":"一般只用于CupertinoContextMenu中的点击按钮。可指定孩子和尾部图标,接收点击事件。","image":""},{"id":145,"family":1,"name":"LicensePage","nameCN":"证书页","linkWidget":"130,193","lever":1,"info":"应用的证书页,可指定应用图标、应用名、应用版本号等信息,其他由Flutter自动生成。","image":""},{"id":146,"family":0,"name":"GestureDetector","nameCN":"手势监听器","linkWidget":"147,150","lever":5,"info":"组件手势事件的检测器,可接受点击、长按、双击,按下、松开、移动等事件,并可以获取触点信息,居家旅行必备组件。","image":""},{"id":147,"family":0,"name":"Listener","nameCN":"事件监听器","linkWidget":"146","lever":3,"info":"组件事件的监听器,可接受按下、松开、移动、取消等事件。较GestureDetector比较原始,可获取的信息也更多。","image":""},{"id":148,"family":0,"name":"Tab","nameCN":"标签","linkWidget":"58","lever":1,"info":"一般用于TabBar中的item,上下结构,可指定图标和一个内容组件。","image":""},{"id":149,"family":1,"name":"InkResponse","nameCN":"水波纹响应","linkWidget":"150,152","lever":1,"info":"水波纹的点击效果,接收点击、双击、长按、取消、高亮变化事件,可指定水波纹颜色、半径、高亮形状等属性。","image":""},{"id":150,"family":1,"name":"InkWell","nameCN":"水波纹","linkWidget":"149,152","lever":4,"info":"InkResponse的子类,基本属性同InkResponse。一个矩形区域的水波纹,可以知道圆角半径,边线形状等。","image":""},{"id":151,"family":1,"name":"TableRowInkWell","nameCN":"表格水波纹","linkWidget":"110","lever":1,"info":"只能用于Table的水波纹,接收点击、双击、长按、高亮变化事件,水波纹会作用于表格的一行。","image":""},{"id":152,"family":1,"name":"Ink","nameCN":"水波","linkWidget":"149,150","lever":3,"info":"使InkWell和InkResponse的水波纹有效,用于绘制图像或其他装饰的Material组件。","image":""},{"id":153,"family":1,"name":"RawChip","nameCN":"原生小条","linkWidget":"11,12,13,14,15","lever":5,"info":"各自Chip组件的始祖,拥有各自Chip表现的能力,支持选中、点击、删除等事件。详见Chip、FilterChip、ActionChip、InputChip、ChoiceChip。","image":""},{"id":154,"family":0,"name":"Drawer","nameCN":"滑页栏","linkWidget":"64,155","lever":2,"info":"一般用于Scaffold中的draw和endDraw属性作为左右的滑页栏,可以容纳一个子组件,能指定影深。","image":""},{"id":155,"family":0,"name":"DrawerHeader","nameCN":"滑页栏","linkWidget":"154","lever":2,"info":"一般用于Drawer中,作为滑页栏的头部。可以指定内外边距、装饰、子组件等属性。","image":""},{"id":156,"family":1,"name":"CupertinoApp","nameCN":"iOS应用","linkWidget":"157,158","lever":4,"info":"iOS风格应用的顶级组件,包含路由生成器、主题、语言、主页等属性。","image":""},{"id":157,"family":1,"name":"CupertinoPageScaffold","nameCN":"iOS页面脚手架","linkWidget":"62","lever":3,"info":"iOS风格的页面布局脚手架结构,可指定顶部的导航栏和页面背景色。","image":""},{"id":158,"family":1,"name":"CupertinoTabScaffold","nameCN":"iOS页签脚手架","linkWidget":"63","lever":3,"info":"iOS风格的页面布局脚手架结构,可指定最底部的导航切换栏可主体内容页。","image":""},{"id":159,"family":0,"name":"PositionedDirectional","nameCN":"方向定位","linkWidget":"108,122","lever":3,"info":"和Positioned组件功能一样,属性名不同。只能用于Stack中,可以指定左上右下的距离对某个组件进行位置精确安放。","image":""},{"id":160,"family":1,"name":"Material","nameCN":"材料组件","linkWidget":"3","lever":5,"info":"Material风格组件的领军人物,灵魂核心。可指定颜色、影深、类型、阴影颜色、形状等属性。","image":""},{"id":161,"family":3,"name":"IndexedStack","nameCN":"索引堆叠","linkWidget":"97","lever":4,"info":"Stack组件的子类,可以堆叠多个组件,并通过index来指定展示的组件索引,其余的会被隐藏。","image":""},{"id":162,"family":0,"name":"ListView","nameCN":"列表组件","linkWidget":"16,163","lever":5,"info":"列表显示的领军人物,容纳多个子组件,可以通过builder、separated、custom等构造。有内边距、是否反向、滑动控制器等属性。","image":""},{"id":163,"family":0,"name":"GridView","nameCN":"网格组件","linkWidget":"21,162","lever":5,"info":"容纳多个组件,并以网格的方式。可以通过count、extent、custom、builder等构造。有内边距、是否反向、滑动控制器等属性。","image":""},{"id":164,"family":0,"name":"SingleChildScrollView","nameCN":"单子滑动","linkWidget":"","lever":5,"info":"使一个组件具有滑动的效果,可指定滑动的方向、是否反向、滑动控制器等属性。","image":""},{"id":165,"family":0,"name":"PageView","nameCN":"滑页","linkWidget":"","lever":5,"info":"容纳多个组件页面,可对它们进行滑动切换,可指定滑动的方向、是否反向、滑动控制器等属性。","image":""},{"id":166,"family":2,"name":"CustomPaint","nameCN":"绘制组件","linkWidget":"","lever":5,"info":"通过CustomPainter进行绘制,可实现一些复杂的自定义绘制组件,是Flutter中自定义组件的灵魂人物。","image":""},{"id":167,"family":5,"name":"MediaQuery","nameCN":"媒体查询","linkWidget":"","lever":4,"info":"可通过MediaQuery.of来获取屏幕尺寸、设备密度、文字缩放比例、边距等信息。","image":""},{"id":168,"family":0,"name":"Theme","nameCN":"主题","linkWidget":"65,169","lever":4,"info":"可通过Theme.of获取ThemeData对象。也可以指定主题应用于Theme的后代组件。","image":""},{"id":169,"family":0,"name":"CupertinoTheme","nameCN":"iOS主题","linkWidget":"156,168","lever":3,"info":"可通过CupertinoTheme.of获取CupertinoThemeData对象。也可以指定主题应用于CupertinoTheme的后代组件。","image":""},{"id":170,"family":1,"name":"WillPopScope","nameCN":"返回拦截","linkWidget":"","lever":5,"info":"当一个界面中有WillPopScope组件时,在页面返回时会触发回调,决定是否返回。可用于二次确认退出的场景。","image":""},{"id":171,"family":1,"name":"Hero","nameCN":"共享动画","linkWidget":"28","lever":5,"info":"可指定标签名,两个界面跳转时具有相同标签的组件会进行共享动画。一个界面中不能存在两个同名的Hero标签","image":""},{"id":172,"family":1,"name":"FutureBuilder","nameCN":"异步构造器","linkWidget":"173","lever":5,"info":"可指定一个Future对象,能够监听异步执行的状态,并在构造器中根据状态构建不同的界面。注意该Future对象不能和FutureBuilder同时创建,否则可能过渡刷新。","image":""},{"id":173,"family":1,"name":"StreamBuilder","nameCN":"流构造器","linkWidget":"172","lever":5,"info":"可指定一个stream对象,能够监听异步执行的状态,并在构造器中根据状态构建不同的界面。","image":""},{"id":174,"family":1,"name":"PopupMenuDivider","nameCN":"弹出菜单分割线","linkWidget":"56,34","lever":1,"info":"PopupMenuButton的分割线,一般不单独使用,可指定高度。","image":""},{"id":175,"family":1,"name":"RawMaterialButton","nameCN":"原始按钮","linkWidget":"23,25,26,27","lever":5,"info":"原始的Material按钮,按钮界的幕后大佬,可接受点击、长按、高亮变化事件,可指定颜色、形状。影深、内边距等属性。","image":""},{"id":176,"family":1,"name":"Dismissible","nameCN":"滑动消失","linkWidget":"162","lever":4,"info":"滑动时可显示底部组件,可指定滑动的方向和交叉轴的偏移量。接收确认消失和消失时的回调。","image":""},{"id":177,"family":1,"name":"ReorderableListView","nameCN":"可重排序列表","linkWidget":"162","lever":4,"info":"可以进行长按排序的ListView,可指定滑动方向、是否反向、滑动控制器等属性。","image":""},{"id":178,"family":1,"name":"ExpansionPanelList","nameCN":"展开列表","linkWidget":"52","lever":3,"info":"可展开的列表组件,可根据逻辑来实现单展开或多展开。可指定展开动画时长,接收展开回调","image":""},{"id":179,"family":1,"name":"ListWheelScrollView","nameCN":"滚轮列表","linkWidget":"139","lever":4,"info":"高大上的柱面滑动列表,精妙十足,可指定item高度、透视、挤压等属性,接收滑动时选中事件。","image":""},{"id":180,"family":5,"name":"ScrollConfiguration","nameCN":"ios菜单按钮","linkWidget":"162,163,164","lever":3,"info":"需要包裹一个可滑动的组件,并通过behavior属性控制滑动的效果,可以去除滑动的蓝色阴影等。","image":""},{"id":181,"family":5,"name":"DropdownButtonHideUnderline","nameCN":"下拉按钮隐藏线","linkWidget":"55","lever":1,"info":"用于去除DropdownButton的下划线,本身没有什么应用价值。","image":""},{"id":182,"family":1,"name":"Overlay","nameCN":"悬浮组件","linkWidget":"","lever":5,"info":"可以将组件在全应用中进行悬浮显示,能够添加或移除组件,它们有独立管理的栈。","image":""},{"id":183,"family":4,"name":"CustomScrollView","nameCN":"通用滑动视图","linkWidget":"184,185,188","lever":5,"info":"一个通用的滑动结构,可以指定滑动方向、是否反向、滑动控制器等属性。其中包含的子组件们必须是Sliver家族。","image":""},{"id":184,"family":4,"name":"SliverAppBar","nameCN":"Sliver头部栏","linkWidget":"183,196","lever":4,"info":"Sliver家族的顶部栏通用结构,可以指定左中右组件、收缩空间、影深、固定模式、背景色等属性。","image":""},{"id":185,"family":4,"name":"SliverList","nameCN":"Sliver列表","linkWidget":"183,186,187","lever":5,"info":"Sliver家族的列表组件,通过指定delegate构造子组件。通常用于CustomScrollView中。","image":""},{"id":186,"family":4,"name":"SliverFixedExtentList","nameCN":"Sliver固定延展列表","linkWidget":"183,185,187","lever":3,"info":"Sliver家族的列表组件,通过delegate构造子组件,可以指定item的高度。通常用于CustomScrollView中。","image":""},{"id":187,"family":4,"name":"SliverFillViewport","nameCN":"Sliver填充视图列表","linkWidget":"183,185,186","lever":3,"info":"Sliver家族的列表组件,通过delegate构造子组件,item的高度会填空视口,可以指定是否的分率。","image":""},{"id":188,"family":4,"name":"SliverGird","nameCN":"Sliver网格","linkWidget":"183","lever":4,"info":"Sliver家族的网格列表组件,和GirdView类似,通过count和extent构造。通常用于CustomScrollView中。","image":""},{"id":189,"family":4,"name":"SliverToBoxAdapter","nameCN":"Sliver适配器","linkWidget":"183","lever":4,"info":"可以容纳一个普通的组件,并将其转化成Sliver家族组件的适配器。","image":""},{"id":190,"family":4,"name":"SliverPersistentHeader","nameCN":"Sliver存留头","linkWidget":"183","lever":5,"info":"通常用于CustomScrollView中,可以让一个组件在滑动中停留在顶部,不会滑动消失。","image":""},{"id":191,"family":4,"name":"SliverPadding","nameCN":"Sliver内间距","linkWidget":"74","lever":3,"info":"可容纳一个Sliver家族的子组件,添加自身内边距来限制孩子组件的占位,核心属性为padding。","image":""},{"id":192,"family":4,"name":"SliverOpacity","nameCN":"Sliver透明度","linkWidget":"73","lever":3,"info":"可容纳一个Sliver家族的子组件,并通过opacity来指定子组件的透明度。","image":""},{"id":193,"family":0,"name":"AboutListTile","nameCN":"关于应用条目","linkWidget":"130,145","lever":3,"info":"一个点击条目,点击时可以弹出应用相关信息,可指定应用图标、应用名、应用版本号等信息和内部的子组件列表。","image":""},{"id":194,"family":1,"name":"Scrollbar","nameCN":"滑动指示栏","linkWidget":"195,164,162","lever":3,"info":"需要包裹一个可滑动区域,当可滑动时,会显示滑动的bar用于指示。","image":""},{"id":195,"family":1,"name":"CupertinoScrollbar","nameCN":"iOS滑动指示栏","linkWidget":"194,164,162","lever":3,"info":"iOS风格的滑动指示栏,需要包裹一个可滑动区域,当可滑动时,会显示滑动的bar用于指示。","image":""},{"id":196,"family":4,"name":"FlexibleSpaceBar","nameCN":"ios菜单按钮","linkWidget":"184","lever":3,"info":"通常用于SliverAppBar中的可伸展区域,可指定标题、标题间距、背景、折叠模式等。","image":""},{"id":197,"family":6,"name":"ErrorWidget","nameCN":"错误组件","linkWidget":"","lever":1,"info":"用于显示一个错误信息的组件,红底黄字,在开发过程中经常看到,一般不使用。","image":""},{"id":198,"family":1,"name":"Form","nameCN":"表单组件","linkWidget":"199","lever":4,"info":"表单组件,可以接收其下的FormField组件的变化回调,通过onWillPop拦截页面返回,通过FormState可对表单字段进行保存或校验。","image":""},{"id":199,"family":1,"name":"TextFormField","nameCN":"文字表单输入","linkWidget":"54,198","lever":4,"info":"和TextField属性基本一致,在其基础上增加字段的校验和提交的回调,FormState的save会触发onSaved回调。","image":""},{"id":200,"family":1,"name":"Stepper","nameCN":"步骤组件","linkWidget":"","lever":5,"info":"步骤组件,可指定一步步的操作,可以自定义步骤的内容,确认和返回的按钮以及步骤排列的方向。","image":""},{"id":201,"family":1,"name":"AnimatedSize","nameCN":"尺寸动画","linkWidget":"92","lever":3,"info":"子组件大小发生变化是,进行动画渐变,可指定时长、对齐方式、曲线、vsync等属性。","image":""},{"id":202,"family":0,"name":"Builder","nameCN":"构造器","linkWidget":"","lever":2,"info":"一个不影响子组件占位空间,不具有显示性的组件,存在的唯一价值是提供当前组件对应元素的上下文。","image":""},{"id":203,"family":0,"name":"OrientationBuilder","nameCN":"方向构造器","linkWidget":"202","lever":2,"info":"能够回调父组件是横向还是纵向,可以据此来构建不同的子组件。","image":""},{"id":204,"family":0,"name":"PreferredSize","nameCN":"优先尺寸","linkWidget":"57,64","lever":2,"info":"实现了PreferredSizeWidget接口,可容纳一个子组件,设置优先尺寸,不会对其子组件施加任何约束。","image":""},{"id":205,"family":0,"name":"TabPageSelector","nameCN":"页签滑动选择器","linkWidget":"206,59","lever":2,"info":"通常作为指示器与TabBarView联用,共同使用一个TabController。可指定颜色、大小、选中色。","image":""},{"id":206,"family":0,"name":"TabPageSelectorIndicator","nameCN":"页签指示器","linkWidget":"205","lever":2,"info":"一个有边线的圆形组件,可指定大小、颜色、边线色。是TabPageSelector的部分之一,一般不单独使用。","image":""},{"id":208,"family":0,"name":"Title","nameCN":"应用标题","linkWidget":"65","lever":2,"info":"该组件用于描述app在操作系统中的名称,可以在应用栏列表里看到效果。MaterialApp中的title字段效果的根源是该组件。","image":""},{"id":211,"family":0,"name":"MaterialBanner","nameCN":"横幅组件","linkWidget":"","lever":2,"info":"Material风格的横幅组件,支持左中右或左中下结构,可指定边距背景色等","image":""},{"id":214,"family":0,"name":"NavigationToolbar","nameCN":"导航工具条","linkWidget":"57","lever":2,"info":"左中右模式的通用结构组件,可指定中间组件距左侧边距及是否居中。源码在AppBar等导航条结构中有使用它。","image":""},{"id":218,"family":0,"name":"CupertinoNavigationBarBackButton","nameCN":"iOS风格返回按钮","linkWidget":"57","lever":2,"info":"Cupertino风格的导航栏返回按钮,可指定颜色和点击事件,一般不单独使用。","image":""},{"id":231,"family":1,"name":"InputDecorator","nameCN":"输入装饰","linkWidget":"54","lever":2,"info":"在外层包裹输入的装饰,是TextField的底层核心组件之一,一般不单独使用。","image":""},{"id":232,"family":1,"name":"Navigator","nameCN":"导航器","linkWidget":"65","lever":4,"info":"Navigator用堆栈规则管理一组子组件,可以将子组件切入弹出及监听出入栈事件。MaterialApp路由管理的本源就是使用了Navigator。","image":""},{"id":244,"family":1,"name":"EditableText","nameCN":"可编辑文字","linkWidget":"2,54","lever":2,"info":"可以编辑的文字,是TextField的底层最核心组件,一般不单独使用。","image":""},{"id":245,"family":1,"name":"CupertinoTextField","nameCN":"iOS风格输入框","linkWidget":"54","lever":4,"info":"Cupertino风格的输入框,属性和TextField类似,可指定控制器、文字样式、装饰线、行数限制、游标样式等。接收输入变化、完成输入等事件。","image":""},{"id":251,"family":4,"name":"NestedScrollView","nameCN":"嵌套滑动视图","linkWidget":"183","lever":4,"info":"用于多个视图滑动嵌套处理,可以指定头部、滑动控制器、滑动方向等,其中body必须是可滑动类型的组件。","image":""},{"id":253,"family":1,"name":"Scrollable","nameCN":"可滑动组件","linkWidget":"340,349","lever":4,"info":"实现了一个可滚动组件的交互模型,需要viewportBuilder进的viewport的构造。是ScrollView的核心实现组件之一,一般不直接使用。","image":""},{"id":255,"family":1,"name":"ValueListenableBuilder","nameCN":"监听值构造器","linkWidget":"","lever":5,"info":"可以监听一个值,当其变化时通过builder回调能重建界面,避免使用setState刷新。","image":""},{"id":262,"family":1,"name":"CupertinoSegmentedControl","nameCN":"iOS多栏切换","linkWidget":"33","lever":4,"info":"iOS风格的多按钮栏,表现和ToggleButtons类似,可指定内边距。","image":""},{"id":263,"family":2,"name":"FractionalTranslation","nameCN":"分度偏移","linkWidget":"","lever":3,"info":"通过offset属性将子组件进行偏移,偏移量为OffSet横纵*子组件大小。","image":""},{"id":264,"family":2,"name":"RepaintBoundary","nameCN":"重绘边界","linkWidget":"166","lever":5,"info":"为子组件创建一个单独的显示列表,提升性能。源码中在TextField、DrawerController、Scrollbar、Sliver等组件中均有应用","image":""},{"id":277,"family":2,"name":"ShaderMask","nameCN":"着色器遮罩","linkWidget":"88,38","lever":4,"info":"可容纳一个孩子,并通过着色器来对孩子进行着色,可指定混色模式。通常用于组件渐变色处理。","image":""},{"id":278,"family":2,"name":"BackdropFilter","nameCN":"背景滤镜","linkWidget":"88,97,67","lever":4,"info":"可容纳一个孩子,并将背景进行模糊滤镜。可以通过Stack将背景模糊实现组件的模糊效果。","image":""},{"id":279,"family":2,"name":"PhysicalShape","nameCN":"物理形状","linkWidget":"69","lever":4,"info":"可以让子组件按照路径进行剪裁,并且可以指定背景色、影深、阴影颜色、剪切行为。","image":""},{"id":285,"family":2,"name":"CustomSingleChildLayout","nameCN":"通用单子布局","linkWidget":"341","lever":3,"info":"可容纳一个子组件,并指定代理类对子组件进行排布。代理类可获取父容器区域和子组件的区域大小,及区域约束情况。","image":""},{"id":287,"family":2,"name":"LayoutBuilder","nameCN":"布局构造器","linkWidget":"","lever":4,"info":"可以检测到父容器的区域大小,并根据父容器的尺寸信息可以完成自定义布局。是一个非常实用的布局组件。","image":""},{"id":292,"family":2,"name":"IgnorePointer","nameCN":"忽视点击","linkWidget":"295,146,149,150","lever":4,"info":"容纳一个子组件,可以通过指定ignoring属性,来决定孩子是否忽略手势事件,其本身不接受事件。","image":""},{"id":293,"family":1,"name":"MouseRegion","nameCN":"鼠标区域","linkWidget":"","lever":3,"info":"用于鼠标事件监听的组件,通常用于桌面和Web平台,可监听鼠标的移入、移除、移动事件。","image":""},{"id":295,"family":2,"name":"AbsorbPointer","nameCN":"吸收点击","linkWidget":"146,149,150,292","lever":4,"info":"容纳一个子组件,可以通过指定ignoring属性,来决定孩子是否忽略手势事件,其本身接受事件。","image":""},{"id":297,"family":2,"name":"IntrinsicWidth","nameCN":"固有宽","linkWidget":"298","lever":4,"info":"根据子元素的固有宽度度调整其子元素大小的组件,可解决很多布局的疑难杂症,但相对昂贵。","image":""},{"id":298,"family":2,"name":"IntrinsicHeight","nameCN":"固有高","linkWidget":"297","lever":4,"info":"根据子元素的固有高度调整其子元素大小的组件,可解决很多布局的疑难杂症,但相对昂贵。","image":""},{"id":307,"family":4,"name":"SliverOverlapAbsorber","nameCN":"重叠吸收器","linkWidget":"251,308","lever":3,"info":"包裹另一个的sliver,并迫使其布局范围被视为重叠。需要和SliverOverlapInjector联用。","image":""},{"id":308,"family":4,"name":"SliverOverlapInjector","nameCN":"重叠注射器","linkWidget":"251,307","lever":3,"info":"一个sliver,需要和SliverOverlapAbsorber联用,处理视图重叠问题。","image":""},{"id":312,"family":6,"name":"PerformanceOverlay","nameCN":"性能浮层","linkWidget":"65","lever":2,"info":"可以非常方便地开启性能监测的两个柱图,方便查看刷新界面时帧率的变化情况。","image":""},{"id":313,"family":6,"name":"RawImage","nameCN":"原图片","linkWidget":"38","lever":2,"info":"是实现Image组件的核心组件,可以显示ui的Image,基本属性同Image,一般很少单独使用。","image":""},{"id":315,"family":5,"name":"LayoutId","nameCN":"布局Id","linkWidget":"341","lever":2,"info":"只能用于CustomMultiChildLayout组件中,为其子组件标识身份。","image":""},{"id":324,"family":5,"name":"DefaultTextStyle","nameCN":"默认字体样式","linkWidget":"2,114,124","lever":3,"info":"可容纳一个孩子,为后代的文字指定默认样式。常用于多个相同文字的样式统一,避免一一设置。","image":""},{"id":325,"family":5,"name":"IconTheme","nameCN":"图标样式","linkWidget":"6","lever":3,"info":"可容纳一个孩子,为后代的图标指定默认样式。常用于多个相同图标的样式统一,避免一一设置。","image":""},{"id":326,"family":5,"name":"ButtonTheme","nameCN":"按钮样式","linkWidget":"23,25,26,27","lever":3,"info":"主要用于为后代的Button类型组件统一设置默认属性,也可以通过该组件获取默认Button的属性。","image":""},{"id":327,"family":5,"name":"MaterialBannerTheme","nameCN":"横幅样式","linkWidget":"211","lever":2,"info":"主要用于为后代的MaterialBanner组件统一设置默认属性,也可以通过该组件获取默认MaterialBanner的属性。","image":""},{"id":328,"family":5,"name":"ChipTheme","nameCN":"小条样式","linkWidget":"11,153,12,13,14,15","lever":3,"info":"主要用于为后代的Chip类型组件统一设置默认属性,也可以通过该组件获取默认Chip的属性。","image":""},{"id":329,"family":5,"name":"DividerTheme","nameCN":"分割线样式","linkWidget":"34,35","lever":3,"info":"主要用于为后代的Divider类型组件统一设置默认属性,也可以通过该组件获取默认Divider的属性。","image":""},{"id":330,"family":5,"name":"PopupMenuTheme","nameCN":"弹出菜单样式","linkWidget":"56","lever":2,"info":"主要用于为后代的PopupMenuButton组件统一设置默认属性,也可以通过该组件获取默认PopupMenu的属性。","image":""},{"id":331,"family":5,"name":"SliderTheme","nameCN":"滑块样式","linkWidget":"42","lever":3,"info":"可容纳一个孩子,为后代的Slider指定默认样式。常用于Slider的样式统一,避免一一设置,也可以对Slider进行样式定制。","image":""},{"id":332,"family":5,"name":"ToggleButtonsTheme","nameCN":"滑块样式","linkWidget":"33","lever":2,"info":"主要用于为后代的ToggleButtons组件统一设置默认属性,也可以通过该组件获取默认ToggleButtons的属性。","image":""},{"id":333,"family":5,"name":"TooltipTheme","nameCN":"提示主题","linkWidget":"50","lever":2,"info":"主要用于为后代的Tooltip组件统一设置默认属性,也可以通过该组件获取默认TooltipTheme的属性。","image":""},{"id":334,"family":5,"name":"ListTileTheme","nameCN":"ListTile主题","linkWidget":"16","lever":2,"info":"主要用于为后代的ListTile组件统一设置默认属性,也可以通过该组件获取默认ListTile的属性。","image":""},{"id":338,"family":5,"name":"ButtonBarTheme","nameCN":"按钮条主题","linkWidget":"29","lever":2,"info":"主要用于为后代的ButtonBar组件统一设置默认属性,也可以通过该组件获取默认ButtonBarTheme的属性。","image":""},{"id":340,"family":3,"name":"Viewport","nameCN":"视口组件","linkWidget":"253,349","lever":1,"info":"通常用于为滑动视图提供视口,仅构建显示和预加载的部位。可指定预加载的长度、滑动轴向等。是ScrollView的核心实现组件之一,一般不直接使用。","image":""},{"id":341,"family":3,"name":"CustomMultiChildLayout","nameCN":"通用多子布局","linkWidget":"315,285","lever":4,"info":"使用一个代理类对子组件集进行布局控制,子组件必须使用LayoutId组件进行标识。","image":""},{"id":342,"family":3,"name":"ListBody","nameCN":"列表体","linkWidget":"162","lever":1,"info":"将若干子组件按照轴向进行排列,可设置的属性很少,一般很少使用,而选择使用ListView。","image":""},{"id":351,"family":1,"name":"InteractiveViewer","nameCN":"交互视图","linkWidget":"147,146,78","lever":4,"info":"主要对移动、缩放等手势交互进行封装,简化使用,可指定移动边界、缩放比例、手势监听等。","image":""},{"id":352,"family":0,"name":"CupertinoDialogAction","nameCN":"交互视图","linkWidget":"129","lever":1,"info":" 一个简单的按钮,通常用于CupertinoAlertDialog中,一般不单独使用。","image":""}] ================================================ FILE: assets/version.json ================================================ { "dbVersion": 5 } ================================================ FILE: desiredFileName.txt ================================================ {} ================================================ FILE: devtools_options.yaml ================================================ description: This file stores settings for Dart & Flutter DevTools. documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states extensions: ================================================ FILE: doc/development/architecture.md ================================================ # FlutterUnit 架构设计文档 ## 项目概述 FlutterUnit 是一个全平台的 Flutter 组件展示和学习应用,支持 Android、iOS、Web、Windows、macOS 和 Linux 平台。项目采用模块化架构,提供了 300+ Flutter 组件的详细展示、代码示例和交互演示。 ## 技术栈 - **框架**: Flutter 3.35.1 - **状态管理**: flutter_bloc (BLoC 模式) - **路由管理**: go_router - **数据库**: SQLite (本地数据存储) - **网络请求**: dio - **国际化**: flutter_localizations - **UI组件**: 自研 TolyUI 组件库 ## 整体架构 ### 分层架构 ``` ┌─────────────────────────────────────┐ │ Presentation Layer │ ← UI层 (Views/Pages) ├─────────────────────────────────────┤ │ Business Logic Layer │ ← 业务逻辑层 (BLoC) ├─────────────────────────────────────┤ │ Repository Layer │ ← 仓储层 (Repository) ├─────────────────────────────────────┤ │ Data Source Layer │ ← 数据源层 (Database/API) └─────────────────────────────────────┘ ``` ### 模块化设计 ``` FlutterUnit/ ├── modules/ │ ├── basic_system/ # 基础系统模块 │ │ ├── app/ # 应用核心配置 │ │ ├── app_update/ # 应用更新 │ │ ├── authentication/ # 用户认证 │ │ ├── components/ # 通用组件 │ │ ├── l10n/ # 国际化 │ │ ├── storage/ # 数据存储 │ │ ├── toly_ui/ # UI组件库 │ │ └── utils/ # 工具类 │ ├── widget_system/ # 组件系统模块 │ │ ├── widget_module/ # 组件业务逻辑 │ │ ├── widget_repository/ # 组件数据仓储 │ │ └── widget_ui/ # 组件UI展示 │ ├── knowledge_system/ # 知识系统模块 │ │ ├── algorithm/ # 算法相关 │ │ ├── artifact/ # 工件管理 │ │ ├── awesome/ # 精选内容 │ │ ├── layout/ # 布局相关 │ │ └── note/ # 笔记功能 │ ├── painting_system/ # 绘制系统模块 │ │ └── draw_system/ # 自定义绘制 │ └── tools_system/ # 工具系统模块 │ └── treasure_tools/ # 实用工具集 └── lib/ ├── src/ │ ├── navigation/ # 路由导航 │ ├── l10n/ # 本地化 │ └── starter/ # 应用启动 └── main.dart ``` ## 核心架构组件 ### 1. 应用启动器 (FxApplication) ```dart class FxApplication with FxStarter { @override Widget get app => const AppBlocProvider(child: FlutterUnit3()); @override AppStartRepository get repository => const FlutterUnitStartRepo(); } ``` **职责**: - 应用启动流程管理 - 全局配置初始化 - 错误处理和监控 ### 2. 状态管理 (BLoC 模式) ``` Event → BLoC → State → UI ↑ ↓ └── User Interaction ┘ ``` **主要 BLoC**: - `AppConfigBloc`: 应用配置管理 - `WidgetsBloc`: 组件数据管理 - `CategoryBloc`: 分类管理 - `LikeWidgetBloc`: 收藏功能 ### 3. 路由架构 ```dart GoRoute( path: AppRoute.home.path, routes: [ if (kAppEnv.isDesktopUI) ShellRoute( builder: (_, __, Widget child) => AppDeskNavigation(content: child), routes: body, ), if (!kAppEnv.isDesktopUI) ...body, ], ) ``` **特点**: - 支持桌面端和移动端不同导航 - 声明式路由配置 - 深度链接支持 ### 4. 数据层架构 #### Repository 模式 ```dart abstract class WidgetRepository { Future> loadWidgets(); Future findWidget(int id); Future> loadNodeByWidgetId(int widgetId); } ``` **实现层**: - `WidgetDbRepository`: 数据库实现 - `MemoryWidgetRepository`: 内存缓存实现 #### 数据库设计 **核心表**: - `widget`: Widget基本信息 - `node`: 示例代码节点 - `category`: Widget分类 详见: [数据表结构总览](../modules/widget_system/widget_repository/doc/tables_overview.md) ## 平台适配 ### 平台检测 ```dart class kAppEnv { static bool get isWeb; static bool get isDesktopUI; static bool get isMobile; } ``` ### 响应式设计 - **移动端**: 底部导航 + 侧滑菜单 - **桌面端**: 侧边栏导航 + 多窗口布局 - **Web端**: 响应式布局适配 ## 核心功能 ### 1. 组件展示系统 - 300+ Flutter组件收录 - 实时代码演示 - 交互式组件体验 - 组件关联跳转 ### 2. 搜索与过滤 - 组件名称搜索 - 星级过滤 - 分类筛选 ### 3. 收藏系统 - 自定义收藏集 - 收藏集管理 - 批量操作 ### 4. 主题系统 - 明暗主题切换 - 8种预设颜色主题 - 6种字体选择 - 自定义代码高亮 ## 性能优化 ### 1. 数据缓存 - 内存缓存: 热点数据常驻内存 - 数据库缓存: 本地SQLite存储 ### 2. 懒加载 - 路由懒加载: 按需加载页面 - 组件懒加载: 滚动时动态加载 ### 3. 构建优化 - 代码分割: 模块化打包 - 资源优化: 图片压缩、字体子集化 ## 构建与部署 ### 多平台构建 ```bash # Android flutter build apk --target-platform --split-per-abi # iOS flutter build ios # Web flutter build web # Desktop flutter build windows flutter build macos flutter build linux ``` ### 发布渠道 - Android: APK 直接下载 - iOS: App Store - Web: 在线访问 - Desktop: 可执行文件下载 ## 开发规范 ### 代码规范 - 遵循 Dart 官方代码规范 - 使用 `flutter_lints` 进行代码检查 ### 模块规范 - 每个模块独立的 `pubspec.yaml` - 清晰的模块边界和依赖关系 ### 测试规范 - 单元测试覆盖核心业务逻辑 - Widget测试验证UI行为 ================================================ FILE: doc/version/3.1.0.md ================================================ 桌面版: windows/macos 支持应用内更新,优化更新过程交互 增加: 知识集锦/布局宝库 增加 Ctrl+F 全局搜索功能 全端: 增加收录组件,目前共 354 个 支持寻路算法演绎 优化组件详情展示 支持复制局部代码 ================================================ FILE: doc/version/3.2.0.md ================================================ 全端: 增加最新咨询功能 优化组件列表展示,增加logo设计图 增加世界留言板 优化项目结构 组件数据支持 10 国语言国际化 下载失败,可到下面网站下载最新版 https://gitee.com/toly1994328/FlutterUnit/releases ================================================ FILE: ios/.gitignore ================================================ **/dgph *.mode1v3 *.mode2v3 *.moved-aside *.pbxuser *.perspectivev3 **/*sync/ .sconsign.dblite .tags* **/.vagrant/ **/DerivedData/ Icon? **/Pods/ **/.symlinks/ profile xcuserdata **/.generated/ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !default.mode1v3 !default.mode2v3 !default.pbxuser !default.perspectivev3 ================================================ FILE: ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 13.0 ================================================ FILE: ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: ios/Runner/AppDelegate.swift ================================================ import UIKit import Flutter @main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ================================================ FILE: ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # Launch Screen Assets You can customize the launch screen with your own desired assets by replacing the image files in this directory. 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. ================================================ FILE: ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: ios/Runner/Info.plist ================================================ CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName Flutter Unit CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName flutter_unit CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UIApplicationSupportsIndirectInputEvents UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance NSPhotoLibraryUsageDescription FlutterUnit 申请访问相册,以提升您的图片选择功能的体验 NSPhotoLibraryAddUsageDescription FlutterUnit 申请访问相册,以支持您保存图片到相册 ================================================ FILE: ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; D1D82669BF3BEF35AB05B2B1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C7383EE4A15C8DD5DC0FFE49 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 64130BD95C8A67B133F535A6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 8124494143C478106CBB5535 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; ADBDEEDA55D5FE126765D6DA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; C7383EE4A15C8DD5DC0FFE49 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( D1D82669BF3BEF35AB05B2B1 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3357CCFDF19B51BC0DBF2F91 /* Frameworks */ = { isa = PBXGroup; children = ( C7383EE4A15C8DD5DC0FFE49 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; 47118212ECDE60EDA8D81265 /* Pods */ = { isa = PBXGroup; children = ( ADBDEEDA55D5FE126765D6DA /* Pods-Runner.debug.xcconfig */, 8124494143C478106CBB5535 /* Pods-Runner.release.xcconfig */, 64130BD95C8A67B133F535A6 /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 47118212ECDE60EDA8D81265 /* Pods */, 3357CCFDF19B51BC0DBF2F91 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( BF5658AA55AEB255A85F6911 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 690C1029D98F763064F681B2 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 690C1029D98F763064F681B2 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; BF5658AA55AEB255A85F6911 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Profile; }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = XQPP7CHG9D; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = XQPP7CHG9D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DISABLE_PUSH_NOTIFICATIONS=1", ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = FlutterUnit; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 2.9.2; PRODUCT_BUNDLE_IDENTIFIER = "com.toly1994.flutter-unit"; PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = flutter_unit_profile; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = XQPP7CHG9D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DISABLE_PUSH_NOTIFICATIONS=1", ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = FlutterUnit; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 2.9.2; PRODUCT_BUNDLE_IDENTIFIER = "com.toly1994.flutter-unit"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = flutter_unit_profile; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = XQPP7CHG9D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DISABLE_PUSH_NOTIFICATIONS=1", ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = FlutterUnit; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 2.9.2; PRODUCT_BUNDLE_IDENTIFIER = "com.toly1994.flutter-unit"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = flutter_unit_profile; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: l10n.yaml ================================================ arb-dir: lib/src/l10n/arb template-arb-file: app_zh.arb output-localization-file: app_l10n.dart synthetic-package: false output-dir: lib/src/l10n/gen output-class: AppL10n nullable-getter: false untranslated-messages-file: desiredFileName.txt ================================================ FILE: lib/main.dart ================================================ import 'src/starter/fx_application.dart'; void main(List args) => const FxApplication().run(args); ================================================ FILE: lib/src/flutter_unit.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:l10n/gen_l10n/app_localizations.dart'; import 'package:l10n/l10n.dart'; import 'package:pkg_player/pkg_player.dart'; import 'package:tolyui/app/toly_ui.dart'; import 'package:widget_module/widget_module.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:note/note.dart'; import 'l10n/gen/app_l10n.dart'; import 'l10n/locale_provider.dart'; import 'navigation/router/app_route.dart'; /// create by 张风捷特烈 on 2020/4/28 /// contact me by email 1981462002@qq.com /// 说明: 应用主程序 class FlutterUnit3 extends StatefulWidget { const FlutterUnit3({super.key}); @override State createState() => _FlutterUnit3State(); } class _FlutterUnit3State extends State with LocalProvider { final GoRouter _router = GoRouter( initialLocation: AppRoute.splash.url, routes: [appRoute], onException: (BuildContext ctx, GoRouterState state, GoRouter router) { router.go(AppRoute.globalError.url, extra: state.uri.toString()); }, ); @override void initState() { super.initState(); _initWeb(); } @override Widget build(BuildContext context) { AppConfig state = context.watch().state; ThemeData dark = darkTheme(state); ThemeData light = lightTheme(state); return BlocListener( listenWhen: (p, n) => p.language != n.language, listener: _onLocaleChange, child: DefaultTextStyle( style: TextStyle(fontFamily: ''), child: TolyUiApp.router( routerConfig: _router, showPerformanceOverlay: state.showPerformanceOverlay, title: StrUnit.appName, debugShowCheckedModeBanner: false, localizationsDelegates: localizationsDelegates, supportedLocales: supportedLocales, locale: state.language.locale, themeMode: state.themeMode, darkTheme: dark, theme: light, ), ), ); } void _initWeb() { if (!kAppEnv.isWeb) return; GoRouter.optionURLReflectsImperativeAPIs = true; context.initWidgetData(); } void _onLocaleChange(BuildContext context, AppConfig state) { context.read().changeLocale(state.language.locale); FxDio().auth(UnitApiAuth(state.language.isZh)); } @override Iterable? get localizationsDelegates => const [ AppL10n.delegate, AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, FlutterQuillLocalizations.delegate, PkgL10n.delegate, ]; @override List get supportedLocales => l10nLocales; } ================================================ FILE: lib/src/l10n/arb/app_en.arb ================================================ { "deskTabWidgets": "Widgets", "deskTabPainter": "Painter", "deskTabKnowledge": "Knowledge", "deskTabTools": "Treasure", "deskTabMine": "About", "messageBoard": "Message Board", "mobileTabWidgets": "Widgets", "mobileTabPainter": "Painter", "mobileTabKnowledge": "Knowledge", "mobileTabTools": "Treasure", "mobileTabMine": "Mine", "newBoard": "New", "package": "packages ", "news": "News", "moreNews": "More News" } ================================================ FILE: lib/src/l10n/arb/app_zh.arb ================================================ { "deskTabWidgets": "组件集录", "deskTabPainter": "绘制集录", "deskTabKnowledge": "知识集锦", "deskTabTools": "工具宝箱", "deskTabMine": "应用信息", "messageBoard": "留言板", "mobileTabWidgets": "组件", "mobileTabPainter": "绘制", "mobileTabKnowledge": "知识", "mobileTabTools": "工具", "mobileTabMine": "我的", "newBoard": "新建 ", "package": "三方库 ", "news": "最新资讯", "moreNews": "查看更多" } ================================================ FILE: lib/src/l10n/gen/app_l10n.dart ================================================ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:intl/intl.dart' as intl; import 'app_l10n_en.dart'; import 'app_l10n_zh.dart'; // ignore_for_file: type=lint /// Callers can lookup localized strings with an instance of AppL10n /// returned by `AppL10n.of(context)`. /// /// Applications need to include `AppL10n.delegate()` in their app's /// `localizationDelegates` list, and the locales they support in the app's /// `supportedLocales` list. For example: /// /// ```dart /// import 'gen/app_l10n.dart'; /// /// return MaterialApp( /// localizationsDelegates: AppL10n.localizationsDelegates, /// supportedLocales: AppL10n.supportedLocales, /// home: MyApplicationHome(), /// ); /// ``` /// /// ## Update pubspec.yaml /// /// Please make sure to update your pubspec.yaml to include the following /// packages: /// /// ```yaml /// dependencies: /// # Internationalization support. /// flutter_localizations: /// sdk: flutter /// intl: any # Use the pinned version from flutter_localizations /// /// # Rest of dependencies /// ``` /// /// ## iOS Applications /// /// iOS applications define key application metadata, including supported /// locales, in an Info.plist file that is built into the application bundle. /// To configure the locales supported by your app, you’ll need to edit this /// file. /// /// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. /// Then, in the Project Navigator, open the Info.plist file under the Runner /// project’s Runner folder. /// /// Next, select the Information Property List item, select Add Item from the /// Editor menu, then select Localizations from the pop-up menu. /// /// Select and expand the newly-created Localizations item then, for each /// locale your application supports, add a new item and select the locale /// you wish to add from the pop-up menu in the Value field. This list should /// be consistent with the languages listed in the AppL10n.supportedLocales /// property. abstract class AppL10n { AppL10n(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; static AppL10n of(BuildContext context) { return Localizations.of(context, AppL10n)!; } static const LocalizationsDelegate delegate = _AppL10nDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. /// /// Returns a list of localizations delegates containing this delegate along with /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, /// and GlobalWidgetsLocalizations.delegate. /// /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. static const List> localizationsDelegates = >[ delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ]; /// A list of this localizations delegate's supported locales. static const List supportedLocales = [ Locale('en'), Locale('zh') ]; /// No description provided for @deskTabWidgets. /// /// In zh, this message translates to: /// **'组件集录'** String get deskTabWidgets; /// No description provided for @deskTabPainter. /// /// In zh, this message translates to: /// **'绘制集录'** String get deskTabPainter; /// No description provided for @deskTabKnowledge. /// /// In zh, this message translates to: /// **'知识集锦'** String get deskTabKnowledge; /// No description provided for @deskTabTools. /// /// In zh, this message translates to: /// **'工具宝箱'** String get deskTabTools; /// No description provided for @deskTabMine. /// /// In zh, this message translates to: /// **'应用信息'** String get deskTabMine; /// No description provided for @messageBoard. /// /// In zh, this message translates to: /// **'留言板'** String get messageBoard; /// No description provided for @mobileTabWidgets. /// /// In zh, this message translates to: /// **'组件'** String get mobileTabWidgets; /// No description provided for @mobileTabPainter. /// /// In zh, this message translates to: /// **'绘制'** String get mobileTabPainter; /// No description provided for @mobileTabKnowledge. /// /// In zh, this message translates to: /// **'知识'** String get mobileTabKnowledge; /// No description provided for @mobileTabTools. /// /// In zh, this message translates to: /// **'工具'** String get mobileTabTools; /// No description provided for @mobileTabMine. /// /// In zh, this message translates to: /// **'我的'** String get mobileTabMine; /// No description provided for @newBoard. /// /// In zh, this message translates to: /// **'新建 '** String get newBoard; /// No description provided for @package. /// /// In zh, this message translates to: /// **'三方库 '** String get package; /// No description provided for @news. /// /// In zh, this message translates to: /// **'最新资讯'** String get news; /// No description provided for @moreNews. /// /// In zh, this message translates to: /// **'查看更多'** String get moreNews; } class _AppL10nDelegate extends LocalizationsDelegate { const _AppL10nDelegate(); @override Future load(Locale locale) { return SynchronousFuture(lookupAppL10n(locale)); } @override bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode); @override bool shouldReload(_AppL10nDelegate old) => false; } AppL10n lookupAppL10n(Locale locale) { // Lookup logic when only language code is specified. switch (locale.languageCode) { case 'en': return AppL10nEn(); case 'zh': return AppL10nZh(); } throw FlutterError( 'AppL10n.delegate failed to load unsupported locale "$locale". This is likely ' 'an issue with the localizations generation tool. Please file an issue ' 'on GitHub with a reproducible sample app and the gen-l10n configuration ' 'that was used.'); } ================================================ FILE: lib/src/l10n/gen/app_l10n_en.dart ================================================ // ignore: unused_import import 'package:intl/intl.dart' as intl; import 'app_l10n.dart'; // ignore_for_file: type=lint /// The translations for English (`en`). class AppL10nEn extends AppL10n { AppL10nEn([String locale = 'en']) : super(locale); @override String get deskTabWidgets => 'Widgets'; @override String get deskTabPainter => 'Painter'; @override String get deskTabKnowledge => 'Knowledge'; @override String get deskTabTools => 'Treasure'; @override String get deskTabMine => 'About'; @override String get messageBoard => 'Message Board'; @override String get mobileTabWidgets => 'Widgets'; @override String get mobileTabPainter => 'Painter'; @override String get mobileTabKnowledge => 'Knowledge'; @override String get mobileTabTools => 'Treasure'; @override String get mobileTabMine => 'Mine'; @override String get newBoard => 'New'; @override String get package => 'packages '; @override String get news => 'News'; @override String get moreNews => 'More News'; } ================================================ FILE: lib/src/l10n/gen/app_l10n_zh.dart ================================================ // ignore: unused_import import 'package:intl/intl.dart' as intl; import 'app_l10n.dart'; // ignore_for_file: type=lint /// The translations for Chinese (`zh`). class AppL10nZh extends AppL10n { AppL10nZh([String locale = 'zh']) : super(locale); @override String get deskTabWidgets => '组件集录'; @override String get deskTabPainter => '绘制集录'; @override String get deskTabKnowledge => '知识集锦'; @override String get deskTabTools => '工具宝箱'; @override String get deskTabMine => '应用信息'; @override String get messageBoard => '留言板'; @override String get mobileTabWidgets => '组件'; @override String get mobileTabPainter => '绘制'; @override String get mobileTabKnowledge => '知识'; @override String get mobileTabTools => '工具'; @override String get mobileTabMine => '我的'; @override String get newBoard => '新建 '; @override String get package => '三方库 '; @override String get news => '最新资讯'; @override String get moreNews => '查看更多'; } ================================================ FILE: lib/src/l10n/locale_provider.dart ================================================ import 'package:flutter/material.dart'; mixin LocalProvider { Iterable>? get localizationsDelegates; List get supportedLocales; } ================================================ FILE: lib/src/navigation/model/app_tab.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_unit/src/l10n/gen/app_l10n.dart'; import 'package:tolyui/tolyui.dart'; enum AppTab { widgets('/widget', TolyIcon.icon_layout), packages('/packages', Icons.batch_prediction_rounded), knowledge('/knowledge', TolyIcon.icon_artifact), painter('/painter', TolyIcon.dingzhi1), tools('/tools', TolyIcon.icon_fast), mine('/account', TolyIcon.yonghu); final IconData icon; final String path; static List get mobileTabs => [widgets, painter, knowledge, packages, mine]; const AppTab(this.path, this.icon); String label(AppL10n l10n) { if (kAppEnv.isDesktopUI) { return switch (this) { AppTab.widgets => l10n.deskTabWidgets, AppTab.painter => l10n.deskTabPainter, AppTab.knowledge => l10n.deskTabKnowledge, AppTab.tools => l10n.deskTabTools, AppTab.mine => l10n.deskTabMine, AppTab.packages => l10n.package, }; } return switch (this) { AppTab.widgets => l10n.mobileTabWidgets, AppTab.painter => l10n.mobileTabPainter, AppTab.knowledge => l10n.mobileTabKnowledge, AppTab.tools => l10n.mobileTabTools, AppTab.mine => l10n.mobileTabMine, AppTab.packages => l10n.package, }; } IconMenu menu(AppL10n l10n) => IconMenu( icon, label: label(l10n), route: path, ); } ================================================ FILE: lib/src/navigation/router/app_route.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_unit/src/l10n/gen/app_l10n.dart'; import 'package:pkg_player/pkg_player.dart'; import '../view/desktop/flutter_unit_desk_navigation.dart'; import 'system/app.dart'; import 'system/global.dart'; import 'system/settings.dart'; import 'widgets/collection_route.dart'; import 'widgets/widgets_route.dart'; import 'package:note/note.dart'; RouteBase get appRoute { List body = [ widgetsRoute, noteRoute, collectRoute, GoRoute( path: AppRoute.packages.path, builder: (_, __) { return PkgPlayerPage(); }, // routes: [ // GoRoute(path: AppRoute.collectionDetail.path, builder: collectionDetailBuilder), // ], ), settingsRoute, GoRoute( path: AppRoute.moreNews.path, builder: (ctx, __) => NewsPage( title: AppL10n.of(ctx).news, ), ), ...systemRoutes, ]; return GoRoute( path: AppRoute.home.path, redirect: (_, __) => null, routes: [ ...globalRoutes, if (kAppEnv.isDesktopUI) ShellRoute( builder: (_, __, Widget child) => AppDeskNavigation(content: child), routes: body, ), if (!kAppEnv.isDesktopUI) ...body, ], ); } ================================================ FILE: lib/src/navigation/router/system/app.dart ================================================ import 'package:app/app.dart'; import 'package:artifact/artifact.dart'; import 'package:authentication/authentication.dart'; import 'package:draw_system/draw_system.dart'; import 'package:treasure_tools/treasure_tools.dart'; List get systemRoutes => [ GoRoute( path: AppRoute.dataManage.path, builder: (_, __) => const DataManagePage(), ), GoRoute( path: AppRoute.account.path, builder: (_, __) => const DeskAccountPage(), ), GoRoute( path: AppRoute.aboutApp.path, builder: (_, __) => const AboutAppPage(), ), GoRoute( path: AppRoute.aboutMe.path, builder: (_, __) => const AboutMePage(), ), GoRoute( path: AppRoute.supportMe.path, builder: (_, __) => const SupportMe(), ), if (kAppEnv.isDesktopUI) ...deskTopRoutes ]; List get deskTopRoutes => [ GoRoute( path: AppRoute.knowledge.path, builder: (_, __) => const DeskKnowledgePage(), ), GoRoute( path: AppRoute.painter.path, builder: (_, __) => const GalleryUnit(), ), GoRoute( path: AppRoute.tools.path, builder: (_, __) => const CodeGenPage(), ), ]; ================================================ FILE: lib/src/navigation/router/system/global.dart ================================================ import 'package:app/app.dart'; import '../../../starter/fx_application.dart'; List get globalRoutes => [ GoRoute( path: AppRoute.splash.path, builder: (_, __) => const FlutterUnitSplash(), ), GoRoute( path: AppRoute.startError.path, builder: (_, GoRouterState state) => AppStartErrorPage(error: state.extra), ), GoRoute( path: AppRoute.globalError.path, builder: (_, GoRouterState state) => AppStartErrorPage(error: state.extra), ), ]; ================================================ FILE: lib/src/navigation/router/system/settings.dart ================================================ import 'package:app/app.dart'; GoRoute get settingsRoute => GoRoute( path: AppRoute.settings.path, builder: (_, __) => const SettingPage(), routes: [ GoRoute( path: AppRoute.darkModel.path, builder: (_, __) => const ThemeModelSetting(), ), GoRoute( path: AppRoute.codeStyle.path, builder: (_, __) => const CodeStyleSettingPage(), ), GoRoute( path: AppRoute.themeColor.path, builder: (_, __) => const ThemeColorSettingPage(), ), GoRoute( path: AppRoute.fontSetting.path, builder: (_, __) => const FontSettingPage(), ), GoRoute( path: AppRoute.version.path, builder: (_, __) => const VersionInfo(), ), ], ); ================================================ FILE: lib/src/navigation/router/widgets/collection_route.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:widget_module/widget_module.dart'; import 'package:note/note.dart'; GoRoute get collectRoute => GoRoute( path: AppRoute.collection.path, builder: (_, __) => const CollectPageAdapter(), routes: [ GoRoute( path: AppRoute.collectionDetail.path, builder: collectionDetailBuilder), ], ); GoRoute get noteRoute => GoRoute( path: AppRoute.note.path, builder: (_, __) { return ArtSysScope( child: kAppEnv.isDesktopUI ? const ArticleAdmin() : const MobileArticlePage(), ); }, // routes: [ // GoRoute(path: AppRoute.collectionDetail.path, builder: collectionDetailBuilder), // ], ); Widget collectionDetailBuilder(BuildContext context, GoRouterState state) { Object? extra = state.extra; CategoryModel? model; if (extra is CategoryModel) { model = extra; } return CategoryShow(model: model!); } ================================================ FILE: lib/src/navigation/router/widgets/widgets_route.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:widget_module/widget_module.dart'; import '../../view/mobile/news.dart'; import '../../view/mobile/unit_navigation.dart'; GoRoute get widgetsRoute => GoRoute( path: AppRoute.widget.path, builder: (_, __) { if (kAppEnv.isDesktopUI) { return const DeskWidgetPanel( header: NewsHeader(), ); } return const UnitPhoneNavigation(); }, routes: [ GoRoute(path: AppRoute.widgetDetail.path, builder: widgetDetailBuilder), ], ); Widget widgetDetailBuilder(BuildContext context, GoRouterState state) { Object? extra = state.extra; String? widgetName = state.pathParameters['name']; WidgetModel? model; if (extra is WidgetModel) { model = extra; } if (kAppEnv.isDesktopUI) { return DeskWidgetDetailPageScope( model: model, widgetName: widgetName, ); } assert(model != null); return WidgetDetailPageScope(model: model!); } ================================================ FILE: lib/src/navigation/view/app_bloc_provider.dart ================================================ import 'package:app/app.dart'; import 'package:fx_updater/fx_updater.dart'; import 'package:note/note.dart'; import 'package:authentication/authentication.dart'; import 'package:draw_system/draw_system.dart'; import 'package:storage/storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_module/widget_module.dart'; /// create by 张风捷特烈 on 2020/4/28 /// contact me by email 1981462002@qq.com /// 说明: Bloc提供器包裹层 class AppBlocProvider extends StatefulWidget { final Widget child; const AppBlocProvider({Key? key, required this.child}) : super(key: key); @override State createState() => _AppBlocProviderState(); } class _AppBlocProviderState extends State { @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ // 全局 bloc : 维护应用存储状态、更新、认证 BlocProvider( create: (_) => AuthBloc(repository: HttpAuthRepository())), BlocProvider(create: (_) => AppConfigBloc()), BlocProvider( create: (_) => UpgradeBloc(api: UnitUpgradeApi())), BlocProvider(create: (_) => UserBloc()), BlocProvider(create: (_) => NewsBloc()..initByCache()), BlocProvider( create: (_) => GalleryUnitBloc()..loadGalleryInfo()), ], child: WidgetsBlocProvider(child: widget.child), ); } @override void dispose() { AppStorage().close(); super.dispose(); } } ================================================ FILE: lib/src/navigation/view/desktop/flutter_unit_desk_navigation.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_unit/src/l10n/gen/app_l10n.dart'; import 'package:flutter_unit/src/navigation/model/app_tab.dart'; import 'package:flutter_unit/src/navigation/view/desktop/unit_shortcuts_scope.dart'; import 'package:go_router/go_router.dart'; import 'package:tolyui_navigation/tolyui_navigation.dart'; import 'menu_bar_leading.dart'; import 'menu_bar_tail.dart'; import 'toly_unit_menu_cell.dart'; class AppDeskNavigation extends StatelessWidget { final Widget content; const AppDeskNavigation({super.key, required this.content}); @override Widget build(BuildContext context) { return Scaffold( body: UnitShortcutsScope( child: Row( children: [ const DragToMoveWrapper(child: DeskNavigationRail()), Expanded(child: content), ], ), ), ); } } class DeskNavigationRail extends StatefulWidget { const DeskNavigationRail({super.key}); @override State createState() => _DeskNavigationRailState(); } class _DeskNavigationRailState extends State { @override Widget build(BuildContext context) { return TolyRailMenuBar( cellBuilder: FlutterUnitMenuCell.create, width: 140, gap: 8, padding: EdgeInsets.zero, backgroundColor: const Color(0xff2C3036), menus: deskNavBarMenus, activeId: activePath, enableWidthChange: false, onSelected: context.go, tail: (_) => const MenuBarTail(), leading: (_) => const MenuBarLeading(), ); } late List deskNavBarMenus; @override void didChangeDependencies() { super.didChangeDependencies(); AppL10n l10n = AppL10n.of(context); deskNavBarMenus = AppTab.values.map((e)=>e.menu(l10n)).toList(); } final RegExp _segReg = RegExp(r'/\w+'); String? get activePath { final String path = GoRouterState.of(context).uri.toString(); RegExpMatch? match = _segReg.firstMatch(path); if (match == null) return null; String? target = match.group(0); return target; } } ================================================ FILE: lib/src/navigation/view/desktop/locale_change_menu.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:l10n/l10n.dart'; import 'package:tolyui/tolyui.dart'; import 'package:app/app.dart'; class LocaleChangeMenu extends StatelessWidget { const LocaleChangeMenu({super.key}); @override Widget build(BuildContext context) { List labels = Language.values.map((e) => e.label).toList(); DropMenuCellStyle lightStyle = const DropMenuCellStyle( padding: EdgeInsets.symmetric(horizontal: 4, vertical: 0), borderRadius: BorderRadius.all(Radius.circular(6)), foregroundColor: Color(0xff1f1f1f), backgroundColor: Colors.transparent, disableColor: Color(0xffbfbfbf), hoverBackgroundColor: Color(0xfff5f5f5), hoverForegroundColor: Color(0xff1f1f1f), textStyle: TextStyle(fontFamily: '微软雅黑', fontSize: 12,)); Language language = context.select((AppConfigBloc bloc) => bloc.state.language); int index = Language.values.indexOf(language); return Stack( alignment: Alignment.centerLeft, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 6.0), child: Icon(Icons.translate,color: Colors.white,size: 14), ), IconTheme( data: const IconThemeData(color: Colors.white), child: DefaultTextStyle( style: const TextStyle(color: Colors.white), child: TolySelect( fontSize: 12, cellStyle: lightStyle, data: labels, selectIndex: index, iconSize: 16, height: 26, width: 100, minWidth: 100, maxHeight: 180, padding: const EdgeInsets.only(right: 6,left: 24), onSelected: (int index) async { Language type = Language.values[index]; context.read().switchLanguage(type); }, ), ), ), ], ); } } ================================================ FILE: lib/src/navigation/view/desktop/menu_bar_leading.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-05-13 // Contact Me: 1981462002@qq.com import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:tolyui/tolyui.dart'; import 'package:url_launcher/url_launcher.dart'; class MenuBarLeading extends StatelessWidget { const MenuBarLeading({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 20, bottom: 8), child: Column( children: [ Wrap( direction: Axis.vertical, spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [ GestureDetector( onDoubleTap: () { sendEvent(1); }, child: const CircleImage( image: AssetImage('assets/images/icon_head.webp'), size: 60, ), ), const Text( '张风捷特烈', style: TextStyle(color: Colors.white70), ) ], ), _buildIcons(), const Divider(color: Colors.white, height: 1, endIndent: 20), const SizedBox(height: 16), ], ), ); } final List menus = const [ LinkIconMenu( TolyIcon.icon_github, "https://github.com/toly1994328/FlutterUnit"), LinkIconMenu(TolyIcon.icon_juejin, 'https://juejin.im/user/5b42c0656fb9a04fe727eb37'), LinkIconMenu(TolyIcon.icon_item, 'http://toly1994.com'), ]; Widget _buildIcons() { return Padding( padding: const EdgeInsets.only(bottom: 8, top: 8), child: Wrap( spacing: 8, children: menus .map((menu) => TolyAction( style: const ActionStyle.dark(), onTap: menu.launch, child: Icon(menu.icon, color: Colors.white, size: 22), )) .toList(), ), ); } } class LinkIconMenu { final IconData icon; final String url; const LinkIconMenu(this.icon, this.url); void launch() => _launchUrl(url); void _launchUrl(String url) async { if (!await launchUrl(Uri.parse(url))) {} } } ================================================ FILE: lib/src/navigation/view/desktop/menu_bar_tail.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-05-13 // Contact Me: 1981462002@qq.com import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_unit/src/flutter_unit.dart'; import 'package:fx_updater/fx_updater.dart'; import 'package:go_router/go_router.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:tolyui/basic/basic.dart'; import 'locale_change_menu.dart'; import 'theme_model_switch_icon.dart'; enum ActionType { settings(path: '/settings'), collection(path: '/collection'); final String path; const ActionType({required this.path}); } class MenuBarTail extends StatelessWidget { const MenuBarTail({super.key}); @override Widget build(BuildContext context) { return Column( children: [ const Divider(indent: 20, color: Colors.white, height: 1), const SizedBox( height: 8, ), const LocaleChangeMenu(), Padding( padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8, top: 2), child: Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: 8, children: [ const SettingIcon(), TolyAction( style: const ActionStyle.dark(), onTap: () => context.push(ActionType.collection.path), child: const Icon( TolyIcon.icon_collect, color: Colors.white, size: 22, ), ), const ThemeModelSwitchIcon(), ], ), ), ], ); } } class SettingIcon extends StatelessWidget { const SettingIcon({super.key}); @override Widget build(BuildContext context) { UpdateState state = context.watch().state; Color tipColor = Colors.redAccent; Widget child = TolyAction( style: const ActionStyle.dark(), onTap: () => context.push(ActionType.settings.path), child: const Icon(Icons.settings, color: Colors.white, size: 22), ); return switch (state) { ShouldUpdateState() => Badge(backgroundColor: tipColor, child: child), _ => child, }; } } ================================================ FILE: lib/src/navigation/view/desktop/theme_model_switch_icon.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tolyui/tolyui.dart'; class ThemeModelSwitchIcon extends StatelessWidget { const ThemeModelSwitchIcon({Key? key}) : super(key: key); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; return MouseRegion( cursor: SystemMouseCursors.click, child: TolyAction( style: const ActionStyle.dark(), onTap: (){ context.read().changeThemeMode(isDark?ThemeMode.light:ThemeMode.dark); }, child: Icon( !isDark?TolyIcon.dark:TolyIcon.wb_sunny, color: Colors.white, size: 22, ), ), ); } } ================================================ FILE: lib/src/navigation/view/desktop/toly_unit_menu_cell.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-05-13 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import 'package:tolyui_navigation/tolyui_navigation.dart'; final Tween _widthTween = Tween(begin: 0.82, end: 0.95); final Tween _sizeTween = Tween(begin: 18.0, end: 22.0); final Tween _fontSizeTween = Tween(begin: 14.0, end: 15); class FlutterUnitMenuCell extends StatelessWidget { final MenuMeta menu; final bool enableTooltip; final DisplayMeta display; const FlutterUnitMenuCell.create(this.menu, this.display, {super.key, this.enableTooltip = false}); Color? get foregroundColor => display.selected ? Colors.white : Colors.white70; @override Widget build(BuildContext context) { double height = 42; double anim = display.rate; Color? color = ColorTween( begin: Colors.white.withAlpha(33), end: Theme.of(context).primaryColor) .transform(anim); double iconSize = _sizeTween.transform(anim); double fontSize = _fontSizeTween.transform(anim); IconData? icon; if (menu is IconMenu) { icon = (menu as IconMenu).icon; } TextStyle style = TextStyle(color: foregroundColor, fontSize: fontSize); Radius radius = Radius.circular(height / 2); BorderRadius br = BorderRadius.only(topRight: radius, bottomRight: radius); Widget child = Container( padding: EdgeInsets.only(left: 12), alignment: Alignment.centerLeft, decoration: BoxDecoration(color: color, borderRadius: br), width: _widthTween.transform(anim) * 140, height: height, child: Row( spacing: 6, // crossAxisAlignment: WrapCrossAlignment.center, children: [ Icon(icon, color: foregroundColor, size: iconSize), Expanded( child: Text( menu.label, style: style, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), const SizedBox( width: 2, ) ], ), ); if (enableTooltip) { child = TolyTooltip( placement: Placement.right, message: menu.label, child: child, ); } return Align( alignment: Alignment.centerLeft, child: child, ); } } ================================================ FILE: lib/src/navigation/view/desktop/unit_shortcuts_scope.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fx_trace/fx_trace.dart'; import 'package:go_router/go_router.dart'; import 'package:widget_module/widget_module.dart'; class GlobalFind extends Intent { const GlobalFind(); } class UnitShortcutsScope extends StatefulWidget { final Widget child; const UnitShortcutsScope({super.key, required this.child}); @override State createState() => _UnitShortcutsScopeState(); } class _UnitShortcutsScopeState extends State with FxEmitterMixin { @override Widget build(BuildContext context) { return Shortcuts( shortcuts: { LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyF): const GlobalFind(), }, child: Actions( actions: >{ GlobalFind: CallbackAction(onInvoke: _onGlobalSearch), }, child: widget.child, ), ); } @override void onEvent(FxEvent event) { if (event is SelectWidgetEvent) { context.push('/widget/detail/${event.name}', extra: event.model); } } Object? _onGlobalSearch(GlobalFind intent) { showDialog(context: context, builder: (_) => const GlobalFindDialog()); return null; } } ================================================ FILE: lib/src/navigation/view/mobile/carousel.dart ================================================ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:app/app.dart'; import 'news.dart'; typedef TypeWidgetBuilder = Widget Function(BuildContext context, T data); class Carousel extends StatefulWidget { final List data; final TypeWidgetBuilder itemBuilder; const Carousel({ super.key, required this.data, required this.itemBuilder, }); @override State createState() => _CarouselState(); } class _CarouselState extends State> { final ValueNotifier factor = ValueNotifier(0); late PageController _ctrl; final int _firstOffset = 1000; //初始偏移 int _position = 0; //页面位置 @override void initState() { super.initState(); _position = _position + _firstOffset; double value = ((_position - _firstOffset + 1) % 5) / 5; factor.value = value == 0 ? 1 : value; _ctrl = PageController( viewportFraction: kAppEnv.isDesktopUI ? 0.38 : 0.8, initialPage: _position, )..addListener(() { if (_ctrl.page != null) { double value = (_ctrl.page! - _firstOffset + 1) % 5 / 5; factor.value = value == 0 ? 1 : value; } }); } @override void dispose() { _ctrl.dispose(); factor.dispose(); super.dispose(); } Color get color => Colors.blue; Color get nextColor => Colors.orangeAccent; bool get isDark => Theme.of(context).brightness == Brightness.dark; @override Widget build(BuildContext context) { List data = widget.data; if (data.isEmpty) return const SizedBox(); Widget child = PageView.builder( controller: _ctrl, // itemCount: 7, itemBuilder: (_, index) { int realIndex = _fixPosition(index, _firstOffset, data.length); return GestureDetector( child: AnimatedBuilder( animation: _ctrl, builder: (context, child) => _buildAnimItemByIndex( context, child, index, ), child: widget.itemBuilder(context, data[realIndex]), ), ); }, onPageChanged: (index) { _position = index; setState(() {}); }, ); int realIndex = _fixPosition(_position, _firstOffset, data.length); child = Stack( alignment: Alignment.bottomCenter, children: [ child, Padding( padding: const EdgeInsets.only(bottom: 16.0), child: Wrap( spacing: 6, children: widget.data.asMap().keys.map((int index) { return GestureDetector( onTap: () { int deta = index - realIndex; _position += deta; print('$_position,$realIndex'); _ctrl.animateToPage(_position, duration: Duration(milliseconds: 500), curve: Curves.easeIn); }, child: Container( width: 8, height: 8, decoration: BoxDecoration( color: realIndex == index ? Colors.white : Colors.black.withValues(alpha: 0.4), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.white.withValues(alpha: 0.3), spreadRadius: 1, blurRadius: 10, blurStyle: BlurStyle.outer) ]), ), ); }).toList(), ), ) ], ); if (!kIsDesk) { return child; } return Stack( alignment: Alignment.center, children: [ child, Positioned( right: 0, child: HoverIndicator( onTap: () { _position += 1; _ctrl.animateToPage(_position, duration: Duration(milliseconds: 500), curve: Curves.easeIn); }, icon: Icons.navigate_next_outlined, )), Positioned( left: 0, child: HoverIndicator( onTap: () { _position -= 1; _ctrl.animateToPage(_position, duration: Duration(milliseconds: 500), curve: Curves.easeIn); }, icon: Icons.navigate_before)), ], ); } Widget _buildAnimItemByIndex(BuildContext context, Widget? child, int index) { double value; if (_ctrl.position.haveDimensions && _ctrl.page != null) { value = _ctrl.page! - index; } else { value = (_position - index).toDouble(); } value = (1 - ((value.abs()) * 0.2)).clamp(0, 1).toDouble(); value = Curves.easeOut.transform(value); return Transform( transform: Matrix4.diagonal3Values(value, value, 1.0), alignment: Alignment.center, child: child, ); } int _fixPosition(int realPos, int initPos, int length) { if (length == 0) return 0; final int offset = realPos - initPos; int result = offset % length; return result < 0 ? length + result : result; } } class HoverIndicator extends StatefulWidget { final IconData icon; final VoidCallback onTap; const HoverIndicator({super.key, required this.icon, required this.onTap}); @override State createState() => _HoverIndicatorState(); } class _HoverIndicatorState extends State { @override Widget build(BuildContext context) { return MouseRegion( onEnter: _onEnter, onExit: _onExit, cursor: SystemMouseCursors.click, child: GestureDetector( onTap: widget.onTap, child: Container( width: 36, height: 260, child: !_hover ? null : Icon( widget.icon, color: Colors.white, ), color: _hover ? Colors.blue.withValues(alpha: 0.2) : Colors.transparent, ), )); } bool _hover = false; void _onEnter(PointerEnterEvent event) { setState(() { _hover = true; }); } void _onExit(PointerExitEvent event) { setState(() { _hover = false; }); } } ================================================ FILE: lib/src/navigation/view/mobile/news.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:note/note.dart'; import 'package:tolyui/basic/basic.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../l10n/gen/app_l10n.dart'; import 'carousel.dart'; import 'package:app/app.dart'; class NewsHeader extends StatefulWidget { const NewsHeader({super.key}); @override State createState() => _NewsHeaderState(); } class _NewsHeaderState extends State { @override Widget build(BuildContext context) { AppL10n l10n = AppL10n.of(context); List data = context.select((NewsBloc bloc) => bloc.state.headerNews); return ConstrainedBox( constraints: const BoxConstraints(maxHeight: 64), child: Column( spacing: 2, crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 4), child: Row( children: [ Text( l10n.news, style: TextStyle(fontWeight: FontWeight.w600), ), if (kAppEnv.isDesktopUI) TolyAction( style: ActionStyle(padding: EdgeInsets.all(2)), child: Icon( Icons.refresh, size: 16, ), onTap: () { context.read().refreshFromNet(); }), Spacer(), MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: () { context.push('/more_news'); }, child: Text( l10n.moreNews, style: TextStyle(fontSize: 12, color: Color(0xff999999)), ), ), ) ], ), ), Expanded( child: Carousel( data: data, itemBuilder: (BuildContext context, ArticlePo data) { return NewsArticleDisplay(article: data); }, ), ), ], ), ); } } void launch(String url) => _launchUrl(url); void _launchUrl(String url) async { if (!await launchUrl(Uri.parse(url))) {} } class NewsArticleDisplay extends StatelessWidget { const NewsArticleDisplay({ super.key, required this.article, }); final ArticlePo article; @override Widget build(BuildContext context) { return GestureDetector( onTap: () { _launchUrl(article.url); }, child: Container( // margin: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), image: DecorationImage( image: NetworkImage(article.cover ?? ''), fit: BoxFit.cover)), padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Row( children: [ Container( width: 16, height: 16, margin: EdgeInsets.only(right: 6), alignment: Alignment.center, decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(4)), child: Text( '新', style: TextStyle(fontSize: 8, color: Colors.white, height: 1), ), ), Flexible( child: Text( article.title, style: TextStyle( color: Colors.white, shadows: [ Shadow( color: Colors.black, offset: Offset(.5, .5), blurRadius: 4) ], fontSize: 14, fontWeight: FontWeight.bold), softWrap: false, ), ), ], ), ], ), ), ); } } ================================================ FILE: lib/src/navigation/view/mobile/pure_bottom_bar.dart ================================================ import 'package:flutter/material.dart'; import '../../../l10n/gen/app_l10n.dart'; import '../../model/app_tab.dart'; class PureBottomBar extends StatelessWidget { final ValueChanged? onTap; final AppTab activeTab; const PureBottomBar({ super.key, this.onTap, required this.activeTab, // required this.labels, // required this.icons, }); @override Widget build(BuildContext context) { AppL10n l10n = AppL10n.of(context); return BottomNavigationBar( onTap: onTap, currentIndex: activeTab.index, elevation: 3, // fixedColor: themeColor.activeColor, type: BottomNavigationBarType.fixed, iconSize: 22, selectedItemColor: Theme.of(context).primaryColor, selectedLabelStyle: const TextStyle(fontWeight: FontWeight.bold), showUnselectedLabels: true, showSelectedLabels: true, // backgroundColor: themeColor.itemColor, items: AppTab.mobileTabs .map((AppTab tab) => BottomNavigationBarItem( label: tab.label(l10n), icon: Icon(tab.icon), )) .toList() // labels // .asMap() // .keys // .map((index) => // // .toList(), ); } } ================================================ FILE: lib/src/navigation/view/mobile/unit_navigation.dart ================================================ import 'dart:io'; import 'package:algorithm/algorithm.dart'; import 'package:app/app.dart'; import 'package:artifact/artifact.dart'; import 'package:authentication/authentication.dart'; import 'package:draw_system/draw_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_unit/src/navigation/model/app_tab.dart'; import 'package:fx_updater/fx_updater.dart'; import 'package:pkg_player/pkg_player.dart'; import 'package:widget_module/widget_module.dart'; import 'news.dart'; import 'pure_bottom_bar.dart'; /// create by 张风捷特烈 on 2020-04-11 /// contact me by email 1981462002@qq.com /// 说明: 主题结构 左右滑页 + 底部导航栏 class UnitPhoneNavigation extends StatefulWidget { const UnitPhoneNavigation({super.key}); @override State createState() => _UnitPhoneNavigationState(); } class _UnitPhoneNavigationState extends State { //页面控制器,初始 0 final PageController _controller = PageController(); final ValueNotifier _activeTab = ValueNotifier(AppTab.widgets); // 禁止 PageView 滑动 final ScrollPhysics _neverScroll = const NeverScrollableScrollPhysics(); @override void initState() { super.initState(); if (Platform.isAndroid || Platform.isIOS) {} String locale = context.read().state.language.locale.toString(); context.read().add(CheckUpdate(appId: 1, locale: locale)); } @override void dispose() { _controller.dispose(); //释放控制器 _activeTab.dispose(); super.dispose(); } /// extendBody = true 凹嵌透明,需要处理底部 边距 @override Widget build(BuildContext context) { return Scaffold( extendBody: true, endDrawer: const HomeRightDrawer(), body: PageView( physics: _neverScroll, controller: _controller, children: [ StandardHomePage(heard: NewsHeader()), GalleryUnit(), AlgoScope(child: ArtifactPage()), PkgPlayerPage(), UserPage(), ], ), bottomNavigationBar: _buildBottomNav(context), ); } bool get isDark => Theme.of(context).brightness == Brightness.dark; // 由于 bottomNavigationBar 颜色需要随 点击头部栏 状态而改变, // 使用 BlocBuilder 构建 Widget _buildBottomNav(BuildContext context) { return Stack( children: [ ValueListenableBuilder( valueListenable: _activeTab, builder: (_, value, __) => PureBottomBar( onTap: _onTapBottomNav, activeTab: value, )), const Positioned(right: 22, top: 8, child: UpdateRedPoint()) ], ); } // 点击底部按钮事件,切换页面 void _onTapBottomNav(int index) { _controller.jumpToPage(index); _activeTab.value = AppTab.values[index]; if (index == 3) { context.read().loadLikeData(); } } } ================================================ FILE: lib/src/starter/bridge/unit_bridge.dart ================================================ import 'package:fx_dio/fx_dio.dart'; import 'package:fx_dio/src/client/host.dart'; import 'package:note/note.dart'; import 'package:app/app.dart'; class UnitNoteBridge with NoteModuleBridge { @override Host get host => FxDio()(); } ================================================ FILE: lib/src/starter/fx_application.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fx_boot_starter/fx_boot_starter.dart'; import 'package:fx_updater/fx_updater.dart'; import 'package:go_router/go_router.dart'; import 'package:widget_module/widget_module.dart'; import 'package:widget_module/blocs/blocs.dart'; import '../flutter_unit.dart'; import '../navigation/view/app_bloc_provider.dart'; import 'start_repository.dart'; import 'package:note/note.dart'; export 'view/splash/Flutter_unit_splash.dart'; export 'view/error/app_start_error.dart'; class FxApplication with FxStarter { const FxApplication(); @override Widget get app => const AppBlocProvider(child: FlutterUnit3()); @override AppStartRepository get repository => const FlutterUnitStartRepo(); @override void onLoaded(BuildContext context, int cost, AppConfig state) async { debugPrint("App启动耗时:$cost ms"); context.read().init(state); context.initWidgetData(); if (!kAppEnv.isWeb) { context.read().loadLikeData(); context.read().add(const EventLoadCategory()); context.read().load(); } } @override void onStartSuccess(BuildContext context, AppConfig state) { CheckUpdate event = CheckUpdate(appId: 1, locale: state.localeValue); context.read().add(event); context.go(AppRoute.widget.url); sendEvent(1); } @override void onStartError(BuildContext context, Object error, StackTrace trace) { context.go(AppRoute.startError.url, extra: error); } @override void onGlobalError(Object error, StackTrace stack) { print(error); } } ================================================ FILE: lib/src/starter/start_repository.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:note/note.dart'; import 'package:app/app.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:fx_boot_starter/fx_boot_starter.dart'; import 'package:flutter/services.dart'; import 'package:pkg_player/pkg_player.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:storage/storage.dart'; import 'package:path/path.dart' as path; import 'package:utils/utils.dart'; import 'package:path/path.dart' as p; import 'bridge/unit_bridge.dart'; import 'package:widget_module/widget_module.dart'; class FlutterUnitStartRepo implements AppStartRepository { const FlutterUnitStartRepo(); /// 初始化 app 的异步任务 /// 返回本地持久化的 AppConfig 对象 @override Future initApp() async { WidgetsFlutterBinding.ensureInitialized(); // 滚动性能优化 1.22.0 GestureBinding.instance.resamplingEnabled = true; WindowSizeAdapter.setSize(); await SpStorage().initSp(); await initAppMeta(); AppConfigPo po = await SpStorage().appConfig.read(); AppConfig state = AppConfig.fromPo(po); registerHttpClient(state.language.isZh); NoteEnv().attachBridge(UnitNoteBridge()); if (!kAppEnv.isWeb) await initDb(); await initWidgetStatistics(); // 加载统计数据 HttpUtil.instance.rebase(PathUnit.baseUrl); return state; } Future initDb() async { //数据库不存在,执行拷贝 String dbPath = await AppStorage().flutter.dbpath; bool shouldCopy = await _checkShouldCopy(dbPath, SpStorage().spf); if (shouldCopy) { await _doCopyAssetsDb(dbPath); } else { print("=====flutter.db 已存在===="); } String pkgPath = path.join(path.dirname(dbPath), 'packages.db'); PkgDatabaseHelper().setDbPath(pkgPath); await AppStorage().init(); } Future _doCopyAssetsDb(String dbPath) async { Directory dir = Directory(path.dirname(dbPath)); if (!dir.existsSync()) { await dir.create(recursive: true); } { ByteData data = await rootBundle.load("assets/flutter.db"); List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); await File(dbPath).writeAsBytes(bytes, flush: true); } { ByteData data = await rootBundle.load("assets/article.db"); List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); await File(p.join(dir.path, 'article.db')) .writeAsBytes(bytes, flush: true); } // { // ByteData data = await rootBundle.load("assets/packages.db"); // List bytes = // data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); // // await File(p.join(dir.path, 'packages.db')) // .writeAsBytes(bytes, flush: true); // } print("=====flutter.db==== assets ======拷贝完成===="); } Future _checkShouldCopy(String dbPath, SharedPreferences prefs) async { bool shouldCopy = false; String versionStr = await rootBundle.loadString('assets/version.json'); int dbVersion = await json.decode(versionStr)['dbVersion']; int versionInSP = prefs.getInt(SpKey.dbVersionKey) ?? -1; // 版本升级,执行拷贝 if (dbVersion > versionInSP) { shouldCopy = true; await prefs.setInt(SpKey.dbVersionKey, dbVersion); } //非 release模式,执行拷贝 if (kDebugMode) { shouldCopy = true; } //数据库不存在,执行拷贝 if (!File(dbPath).existsSync()) { shouldCopy = true; } return shouldCopy; } } ================================================ FILE: lib/src/starter/view/error/app_start_error.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:tolyui/basic/basic.dart'; class AppStartErrorPage extends StatelessWidget { final Object? error; const AppStartErrorPage({super.key, required this.error}); @override Widget build(BuildContext context) { return Scaffold( appBar: PreferredDragToMoveWrapper( child: AppBar( title: const Text( "App 启动异常", style: TextStyle(fontFamily: '宋体'), ), actions: const [WindowButtons()], ), ), body: Center( child: Column( children: [ Expanded( child: Center( child: Wrap( direction: Axis.vertical, children: [ const Text('应用启动异常:'), Text( error.toString(), style: const TextStyle(color: Colors.redAccent), ), ], ))), TolyLink( href: 'https://github.com/toly1994328/', text: 'Github 开源地址: FlutterUnit', onTap: (l) {}), const Text("联系邮箱: 1981462002@qq.com"), const SizedBox( height: 12, ), ], ), ), ); } } ================================================ FILE: lib/src/starter/view/splash/Flutter_unit_splash.dart ================================================ import 'dart:math'; import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fx_boot_starter/fx_boot_starter.dart'; import 'dart:ui' as ui; import 'flutter_unit_text.dart'; /// create by 张风捷特烈 on 2020-03-07 /// contact me by email 1981462002@qq.com /// 说明: app 闪屏页 class FlutterUnitSplash extends StatelessWidget { const FlutterUnitSplash({super.key}); @override Widget build(BuildContext context) { return const AppStartListener( child: AnnotatedRegion( value: SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, systemNavigationBarColor: Colors.transparent, ), child: Material(color: Colors.white, child: _SplashBody()), ), ); } } class _SplashBody extends StatelessWidget { const _SplashBody({super.key}); @override Widget build(BuildContext context) { final Color color = Theme.of(context).primaryColor; const TextStyle shadowStyle = UnitTextStyle.splashShadows; const TextStyle titleStyle = TextStyle(fontWeight: FontWeight.bold); return Column( children: [ const SplashTopBar( leading: Text('Flutter Unit', style: titleStyle), logo: CircleAvatar( backgroundImage: AssetImage('assets/images/icon_head.webp'), radius: 14, ), ), const Spacer(), Expanded( child: Wrap( direction: Axis.vertical, alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Stack(children: [ColorfulText(), FlutterLogo(size: 60)]), const SizedBox(height: 20), FlutterUnitText( text: StrUnit.appName, color: color, ), ], )), const Expanded( child: Stack( alignment: Alignment.bottomCenter, children: [ Positioned( bottom: 15, child: Wrap( direction: Axis.vertical, alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ Text("Power By 张风捷特烈", style: shadowStyle), Text("· 2025 · @编程之王 ", style: shadowStyle), ], )), ], )) ], ); } } class ColorfulText extends StatelessWidget { const ColorfulText({super.key}); @override Widget build(BuildContext context) { final Paint paint = Paint() ..style = PaintingStyle.stroke ..shader = ui.Gradient.linear( const Offset(0, 0), const Offset(22, 0), [Colors.red, Colors.yellow, Colors.blue, Colors.green], [1 / 4, 2 / 4, 3 / 4, 1], TileMode.mirror, Matrix4.rotationZ(pi / 4).storage, ); return Text( "U", style: TextStyle( fontSize: 26, height: 1, fontWeight: FontWeight.bold, foreground: paint), ); } } class SplashTopBar extends StatelessWidget { final Widget? leading; final Widget? logo; const SplashTopBar({super.key, this.leading, this.logo}); @override Widget build(BuildContext context) { if (!kIsDesk) return const SizedBox.shrink(); return DragToMoveWrapper( child: Stack( children: [ Container( alignment: Alignment.topLeft, padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8), child: Row( children: [ if (leading != null) Row( children: [ if (logo != null) logo!, const SizedBox( width: 8, ), leading!, ], ), const Spacer(), const SizedBox( width: 20, ), ], ), ), const Positioned( right: 0, child: WindowButtons(), ) ], ), ); } } ================================================ FILE: lib/src/starter/view/splash/flutter_unit_text.dart ================================================ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class FlutterUnitText extends StatefulWidget { final String text; final Color color; final double fontSize; const FlutterUnitText({ this.text = "Toly", this.color = Colors.blue, this.fontSize = 32, Key? key, }) : super(key: key); @override State createState() => _FlutterUnitTextState(); } class _FlutterUnitTextState extends State with SingleTickerProviderStateMixin { late AnimationController _ctrl; final TextPainter _textPainter = TextPainter(textDirection: TextDirection.ltr); late Animation animation; @override void initState() { super.initState(); TextSpan text = TextSpan( text: widget.text, style: TextStyle(fontSize: widget.fontSize, color: Colors.blue)); _textPainter.text = text; _textPainter.layout(); // 进行布局 _ctrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 800)); animation = CurvedAnimation(parent: _ctrl, curve: const Interpolator()); _ctrl.forward(); } @override void dispose() { _ctrl.dispose(); super.dispose(); } @override void didUpdateWidget(FlutterUnitText oldWidget) { super.didUpdateWidget(oldWidget); _ctrl.forward(); } String msg = ''; @override Widget build(BuildContext context) { return CustomPaint( size: _textPainter.size, painter: SpringPainter( fontSize: widget.fontSize, textPainter: _textPainter, color: widget.color, skew: animation, )); } } class Interpolator extends Curve { const Interpolator(); @override double transformInternal(double t) { t -= 1.0; return t * t * t * t * t + 1.0; } } class SpringPainter extends CustomPainter { final ValueListenable skew; final TextPainter textPainter; final double fontSize; String _text = ''; Color color; SpringPainter( {required this.skew, required this.textPainter, this.color = Colors.blue,required this.fontSize}) : super(repaint: skew) { _text = textPainter.text?.toPlainText() ?? ''; } @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); TextSpan text = TextSpan(text: _text, style: TextStyle(fontSize: fontSize, color: color)); textPainter.text = text; textPainter.layout(); // 进行布局 Size textSize = textPainter.size; // 尺寸必须在布局后获取 canvas.save(); canvas.translate(-textSize.width / 2, -textSize.height / 2); textPainter.paint(canvas, Offset.zero); TextSpan textShadow = TextSpan( text: _text, style: TextStyle(fontSize: fontSize, color: color.withAlpha(88))); textPainter.text = textShadow; textPainter.layout(); // 进行布局 Matrix4 matrix4 = Matrix4.skewX((6 / 180 * pi) * skew.value); canvas.transform(matrix4.storage); textPainter.paint(canvas, Offset.zero); canvas.restore(); } @override bool shouldRepaint(covariant SpringPainter oldDelegate) => oldDelegate.textPainter != textPainter || oldDelegate.skew != skew; } ================================================ FILE: linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: linux/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "flutter_unit") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "com.toly1994.flutter_unit") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Load bundled libraries from the lib/ directory relative to the binary. set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Root filesystem for cross-building. if(FLUTTER_TARGET_PLATFORM_SYSROOT) set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) endif() # Define build configuration options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Define the application target. To change its name, change BINARY_NAME above, # not the value here, or `flutter run` will no longer work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add dependency libraries. Add any application-specific dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endforeach(bundled_library) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: linux/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: linux/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // // clang-format off #include "generated_plugin_registrant.h" #include #include #include #include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) open_file_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); g_autoptr(FlPluginRegistrar) window_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); window_manager_plugin_register_with_registrar(window_manager_registrar); } ================================================ FILE: linux/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void fl_register_plugins(FlPluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST open_file_linux screen_retriever_linux url_launcher_linux window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: linux/main.cc ================================================ #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: linux/my_application.cc ================================================ #include "my_application.h" #include #ifdef GDK_WINDOWING_X11 #include #endif #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; char** dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); // Use a header bar when running in GNOME as this is the common style used // by applications and is the setup most users will be using (e.g. Ubuntu // desktop). // If running on X and not using GNOME then just use a traditional title bar // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 GdkScreen* screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { use_header_bar = FALSE; } } #endif if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "flutter_unit"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { gtk_window_set_title(window, "flutter_unit"); } gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { g_warning("Failed to register: %s", error->message); *exit_status = 1; return TRUE; } g_application_activate(application); *exit_status = 0; return TRUE; } // Implements GObject::dispose. static void my_application_dispose(GObject* object) { MyApplication* self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, "flags", G_APPLICATION_NON_UNIQUE, nullptr)); } ================================================ FILE: linux/my_application.h ================================================ #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/dgph **/xcuserdata/ ================================================ FILE: macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: macos/Flutter/GeneratedPluginRegistrant.swift ================================================ // // Generated file. Do not edit. // import FlutterMacOS import Foundation import file_picker import open_file_mac import package_info_plus import path_provider_foundation import screen_retriever_macos import share_plus import shared_preferences_foundation import sqflite_darwin import url_launcher_macos import webview_flutter_wkwebview import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } ================================================ FILE: macos/Podfile ================================================ platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: macos/Runner/AppDelegate.swift ================================================ import Cocoa import FlutterMacOS @main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { return true } } ================================================ FILE: macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = flutter_unit // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = com.toly1994.flutterUnit // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2022 com.toly1994. All rights reserved. ================================================ FILE: macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server com.apple.security.network.client com.apple.security.device.camera com.apple.security.device.microphone ================================================ FILE: macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: macos/Runner/MainFlutterWindow.swift ================================================ import Cocoa import FlutterMacOS import window_manager class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) { super.order(place, relativeTo: otherWin) hiddenWindowAtLaunch() } } ================================================ FILE: macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server com.apple.security.network.client com.apple.security.device.camera com.apple.security.device.microphone ================================================ FILE: macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 04C873F32A42825600BAB8F5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70442F268D8DC1467C4B98E1 /* Pods_Runner.framework */; }; 04C873F42A42825600BAB8F5 /* Pods_Runner.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 70442F268D8DC1467C4B98E1 /* Pods_Runner.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 04C873F42A42825600BAB8F5 /* Pods_Runner.framework in Bundle Framework */, ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1DFE55036C5A10261BA01266 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* flutter_unit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flutter_unit.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 70442F268D8DC1467C4B98E1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; B432D8A33593253D20752670 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; F04E92F35B305ECDEB7ABD24 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 04C873F32A42825600BAB8F5 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 09AE68A50FF2EF7290782883 /* Pods */ = { isa = PBXGroup; children = ( 1DFE55036C5A10261BA01266 /* Pods-Runner.debug.xcconfig */, F04E92F35B305ECDEB7ABD24 /* Pods-Runner.release.xcconfig */, B432D8A33593253D20752670 /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, 09AE68A50FF2EF7290782883 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* flutter_unit.app */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( 70442F268D8DC1467C4B98E1 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 284E66EAF54928405B1CB4E9 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 393027C358E745B59CB4E335 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* flutter_unit.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 284E66EAF54928405B1CB4E9 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; 393027C358E745B59CB4E335 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = XQPP7CHG9D; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: modules/basic_system/app/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .packages build/ ================================================ FILE: modules/basic_system/app/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 channel: stable project_type: package ================================================ FILE: modules/basic_system/app/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/basic_system/app/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/basic_system/app/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/basic_system/app/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/basic_system/app/lib/app/action/action.dart ================================================ export 'url.dart'; ================================================ FILE: modules/basic_system/app/lib/app/action/url.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Author: 张风捷特烈 // CreateTime: 2024-07-07 // Contact Me: 1981462002@qq.com import 'package:flutter/foundation.dart'; import 'package:url_launcher/url_launcher.dart'; void jumpURL(String url) async { Uri uri = Uri.parse(url); if (await canLaunchUrl(Uri.parse(url))) { await launchUrl(uri,mode: LaunchMode.externalApplication); } else { debugPrint('Could not launch $url'); } } ================================================ FILE: modules/basic_system/app/lib/app/cons/cons.dart ================================================ import 'package:flutter/material.dart'; import 'package:l10n/l10n.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:widget_module/widget_module.dart'; import '../res/toly_icon.dart'; class Cons { static const List tabColors = [ Color(0xff44D1FD), Color(0xffFD4F43), Color(0xffB375FF), Color(0xFF4CAF50), Color(0xFFFF9800), Color(0xFF00F1F1), Color(0xFFDBD83F), ]; static const List kFontFamilySupport = [ 'local', 'ComicNeue', 'IndieFlower', 'BalooBhai2', 'Inconsolata', 'Neucha' ]; static const Map kWidgetFamilyLabelMap = { WidgetFamily.stateless: "Stateless", WidgetFamily.stateful: "Stateful", WidgetFamily.singleChildRender: "SingleChild", WidgetFamily.multiChildRender: "MultiChild", WidgetFamily.sliver: "Sliver", WidgetFamily.proxy: "Proxy", WidgetFamily.other: "Other", }; static Map codeThemeSupport = { HighlighterStyle.fromColors(HighlighterStyle.gitHub):"GitHub - Power By 张风捷特烈", HighlighterStyle.fromColors(HighlighterStyle.darkColor):"捷特黑 - Power By 张风捷特烈", HighlighterStyle.fromColors(HighlighterStyle.lightColor):"捷特白 - Power By 张风捷特烈", HighlighterStyle.fromColors(HighlighterStyle.zenburn):"zenburn - Power By 张风捷特烈", HighlighterStyle.fromColors(HighlighterStyle.mf):"mf - Power By MF", HighlighterStyle.fromColors(HighlighterStyle.solarized):"cst - Power By cst", }; } enum ThemeColor { red(Colors.red), orange(Colors.orange), yellow(Colors.yellow), green(Colors.green), blue(Colors.blue), indigo(Colors.indigo), purple(Colors.purple), dark(MaterialColor(0xff2D2D2D, { 50: Color(0xFF8A8A8A), 100: Color(0xFF747474), 200: Color(0xFF616161), 300: Color(0xFF484848), 400: Color(0xFF3D3D3D), 500: Color(0xff2D2D2D), 600: Color(0xFF252525), 700: Color(0xFF141414), 800: Color(0xFF050505), 900: Color(0xff000000), })); final MaterialColor color; const ThemeColor(this.color); String label(BuildContext context){ return switch(this){ ThemeColor.red => context.l10n.destructionRed, ThemeColor.orange => context.l10n.rageOrange, ThemeColor.yellow => context.l10n.warningYellow, ThemeColor.green => context.l10n.camouflageGreen, ThemeColor.blue => context.l10n.coldBlue, ThemeColor.indigo => context.l10n.infiniteBlue, ThemeColor.purple => context.l10n.mysteryPurple, ThemeColor.dark => context.l10n.destinyBlack, }; } } ================================================ FILE: modules/basic_system/app/lib/app/cons/global_value.dart ================================================ import 'dart:io'; import 'dart:ui'; import 'package:storage/storage.dart'; import 'package:flutter/foundation.dart'; import 'package:uuid/uuid.dart'; import 'package:package_info_plus/package_info_plus.dart'; double px1 = 1 / window.devicePixelRatio; bool kIsDesk = kIsWeb || Platform.isMacOS || Platform.isWindows || Platform.isLinux; late AppMeta kAppMeta; class AppMeta { final String appVersion; final String appId; final String uuid; String get platform => Platform.operatingSystem; AppMeta(this.appVersion, this.appId, this.uuid); Map toHeaderJson() => { 'X-App-Version': appVersion, 'X-App-Id': appId, 'X-Platform': platform, 'X-Uuid': uuid, }; } Future initAppMeta() async { String? uuid = SpStorage().spf.getString('uuid'); if (uuid == null) { uuid = const Uuid().v4(); SpStorage().spf.setString('uuid', uuid); } PackageInfo packageInfo = await PackageInfo.fromPlatform(); String version = packageInfo.version; String appId = '1'; kAppMeta = AppMeta(version, appId, uuid); } ================================================ FILE: modules/basic_system/app/lib/app/cons/path_unit.dart ================================================ /// create by 张风捷特烈 on 2021/1/17 /// contact me by email 1981462002@qq.com /// 说明: class PathUnit { static const baseUrl = 'http://82.157.176.209:8080/api/v1'; static const sendEmail = '/sendEmail/'; static const register = '/register'; static const categoryDataSync = '/categoryData/sync'; static const categoryData = '/categoryData'; static const appInfo = '/appInfo/name'; static const login = '/login'; } ================================================ FILE: modules/basic_system/app/lib/app/cons/sp.dart ================================================ /// create by 张风捷特烈 on 2020-04-10 /// contact me by email 1981462002@qq.com /// 说明: class SpKey{ SpKey._(); static const themeColorIndex = 'theme_color_index'; static const iconFontGenConfig = 'icon_font_gen_config'; static const showBackground = 'show_background'; static const showTool = 'show_tool'; static const fontFamily = 'font_family'; static const codeStyleIndex = 'code_style'; static const itemStyleIndex = 'item_style_index'; static const appStyleIndex = 'app_style_index'; static String dbVersionKey= "db_version_key"; static String tokenKey= "token_key"; static String userKey= "user_key"; } ================================================ FILE: modules/basic_system/app/lib/app/cons/str_unit.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:l10n/l10n.dart'; /// create by 张风捷特烈 on 2020/11/29 /// contact me by email 1981462002@qq.com /// 说明: class StrUnit { // 小文字大小 static const String version = 'V3.2.2'; static const String appName = 'Flutter Unit'; static String galleryDesc(BuildContext context) => """ [ { "image":"assets/images/anim_draw.webp", "name": "${context.l10n.basicDrawing}", "info": "${context.l10n.basicDrawingDesc}" }, { "image":"assets/images/draw_bg3.webp", "name": "${context.l10n.animationGesture}", "info": "${context.l10n.animationGestureDesc}" }, { "image":"assets/images/base_draw.webp", "name": "${context.l10n.particleDrawing}", "info": "${context.l10n.particleDrawingDesc}" }, { "image":"assets/images/draw_bg4.webp", "name": "${context.l10n.interestingDrawing}", "info": "${context.l10n.interestingDrawingDesc}"}, { "image":"assets/images/caver.webp", "name": "${context.l10n.artGallery}", "info": "${context.l10n.artGalleryDesc}"} ] """; } ================================================ FILE: modules/basic_system/app/lib/app/res/toly_icon.dart ================================================ import 'package:flutter/widgets.dart'; // Power By 张风捷特烈--- Generated file. Do not edit. // 欢迎支持: https://github.com/toly1994328/FlutterUnit class TolyIcon { TolyIcon._(); static const IconData icon_artifact = IconData(0xe726, fontFamily: "TolyIcon"); static const IconData dark = IconData(0xe72f, fontFamily: "TolyIcon"); static const IconData wb_sunny = IconData(0xe746, fontFamily: "TolyIcon"); static const IconData icon_fast = IconData(0xe607, fontFamily: "TolyIcon"); static const IconData icon_layout = IconData(0xe85e, fontFamily: "TolyIcon"); static const IconData upload_success = IconData(0xe60b, fontFamily: "TolyIcon"); static const IconData download = IconData(0xea51, fontFamily: "TolyIcon"); static const IconData upload = IconData(0xea52, fontFamily: "TolyIcon"); static const IconData error = IconData(0xe614, fontFamily: "TolyIcon"); static const IconData dingzhi1 = IconData(0xe60e, fontFamily: "TolyIcon"); static const IconData icon_collect = IconData(0xe672, fontFamily: "TolyIcon"); static const IconData yonghu = IconData(0xe619, fontFamily: "TolyIcon"); static const IconData icon_common = IconData(0xe634, fontFamily: "TolyIcon"); static const IconData icon_see = IconData(0xe608, fontFamily: "TolyIcon"); static const IconData icon_issues = IconData(0xe7a7, fontFamily: "TolyIcon"); static const IconData icon_fork = IconData(0xe623, fontFamily: "TolyIcon"); static const IconData icon_github_star = IconData(0xe7df, fontFamily: "TolyIcon"); static const IconData icon_show = IconData(0xe648, fontFamily: "TolyIcon"); static const IconData icon_hide = IconData(0xe649, fontFamily: "TolyIcon"); static const IconData icon_email = IconData(0xe694, fontFamily: "TolyIcon"); static const IconData icon_github = IconData(0xe689, fontFamily: "TolyIcon"); static const IconData icon_juejin = IconData(0xe601, fontFamily: "TolyIcon"); static const IconData icon_share = IconData(0xe613, fontFamily: "TolyIcon"); static const IconData icon_background = IconData(0xe60a, fontFamily: "TolyIcon"); static const IconData icon_code = IconData(0xe70b, fontFamily: "TolyIcon"); static const IconData icon_item = IconData(0xe66f, fontFamily: "TolyIcon"); static const IconData icon_kafei = IconData(0xe6aa, fontFamily: "TolyIcon"); static const IconData icon_tag = IconData(0xe6e7, fontFamily: "TolyIcon"); static const IconData icon_them = IconData(0xe6c2, fontFamily: "TolyIcon"); static const IconData icon_bug = IconData(0xe7af, fontFamily: "TolyIcon"); static const IconData icon_sound = IconData(0xe606, fontFamily: "TolyIcon"); static const IconData icon_search = IconData(0xe604, fontFamily: "TolyIcon"); static const IconData icon_star_ok = IconData(0xe6ae, fontFamily: "TolyIcon"); static const IconData icon_star = IconData(0xe609, fontFamily: "TolyIcon"); static const IconData icon_star_add = IconData(0xe68e, fontFamily: "TolyIcon"); } ================================================ FILE: modules/basic_system/app/lib/app/router/app_route.dart ================================================ enum AppRoute { home('/', url: '/'), splash('splash', url: '/splash'), startError('start_error', url: '/start_error'), globalError('404', url: '/404'), /// widget module widget('widget', url: '/widget'), widgetDetail('detail/:name', url: '/widget/detail/'), collection('collection', url: '/collection'), collectionDetail('widgets/:id', url: '/collection/widgets/'), note('note', url: '/note'), packages('packages', url: '/packages'), moreNews('more_news', url: '/more_news'), painter('painter', url: '/painter'), knowledge('knowledge', url: '/knowledge'), tools('tools', url: '/tools'), /// user/app aboutApp('about_app', url: '/about_app'), account('account', url: '/account'), dataManage('data_manage', url: '/data_manage'), aboutMe('about_me', url: '/about_me'), supportMe('support_me', url: '/support_me'), /// settings settings('settings', url: '/settings'), darkModel('dark_mode', url: '/setting/dark_mode'), codeStyle('code_style', url: '/setting/code_style'), themeColor('theme_color', url: '/setting/theme_color'), fontSetting('font_setting', url: '/setting/font_setting'), version('version', url: '/settings/version'), ; final String path; final String url; const AppRoute( this.path, { required this.url, }); } ================================================ FILE: modules/basic_system/app/lib/app/style/behavior/no_scroll_behavior.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/6/16 /// contact me by email 1981462002@qq.com /// 说明: class NoScrollBehavior extends ScrollBehavior { @override Widget buildOverscrollIndicator( BuildContext context, Widget child, ScrollableDetails details) => child; } ================================================ FILE: modules/basic_system/app/lib/app/style/gap.dart ================================================ // ignore_for_file: constant_identifier_names import 'package:flutter/material.dart'; import 'unit_color.dart'; class Gap{ static const Widget H5 = SizedBox(width: 5); static const Widget H10 = SizedBox(height: 10); static const Widget W5 = SizedBox(width: 5); static const Widget W10 = SizedBox(width: 10); static const Widget sfl10 = SizedBox(height: 10,); } ================================================ FILE: modules/basic_system/app/lib/app/style/shape/coupon_shape_border.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020-03-06 /// contact me by email 1981462002@qq.com /// 说明: class CouponShapeBorder extends ShapeBorder { final int holeCount; final double lineRate; final bool dash; final bool hasLine; final Color color; final bool hasTopHole; final bool hasBottomHole; final double? edgeRadius; const CouponShapeBorder( {this.holeCount = 6, this.hasTopHole =true, this.hasBottomHole =false, this.lineRate = 0.718, this.dash = true, this.hasLine = true, this.color = Colors.white,this.edgeRadius}); @override EdgeInsetsGeometry get dimensions => EdgeInsets.zero; @override Path getInnerPath(Rect rect, {TextDirection? textDirection}) { return Path(); } @override Path getOuterPath(Rect rect, {TextDirection? textDirection}) { double w = rect.width; double h = rect.height; double d = h / (1 + 2 * holeCount); Path path = Path(); path.addRect(rect); _formHoldLeft(path, d); _formHoldRight(path, w, d); if (hasLine) { _formHoleTop(path, rect, d); _formHoleBottom(path, rect, d); } if(edgeRadius!=null){ if(hasTopHole){ _formHoleTop(path, rect, edgeRadius!); } if(hasBottomHole){ _formHoleBottom(path, rect, edgeRadius!); } } path.fillType = PathFillType.evenOdd; return path; } void _formHoleBottom(Path path, Rect rect, double d) { path.addArc( Rect.fromCenter( center: Offset(lineRate * rect.width, rect.height), width: d, height: d), pi, pi); } void _formHoleTop(Path path, Rect rect, double d) { path.addArc( Rect.fromCenter( center: Offset(lineRate * rect.width, 0), width: d, height: d), 0, pi); } _formHoldLeft(Path path, double d) { for (int i = 0; i < holeCount; i++) { double left = -d / 2; double top = 0.0 + d + 2 * d * (i); double right = left + d; double bottom = top + d; path.addArc(Rect.fromLTRB(left, top, right, bottom), -pi / 2, pi); } } _formHoldRight(Path path, double w, double d) { for (int i = 0; i < holeCount; i++) { double left = -d / 2 + w; double top = 0.0 + d + 2 * d * (i); double right = left + d; double bottom = top + d; path.addArc(Rect.fromLTRB(left, top, right, bottom), pi / 2, pi); } } @override void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { if(!hasLine) return; Paint paint = Paint() ..color = color ..strokeWidth = 1.5 ..style = PaintingStyle.stroke ..strokeJoin = StrokeJoin.round; double d = rect.height / (1 + 2 * holeCount); if (dash) { _drawDashLine(canvas, Offset(lineRate * rect.width, d / 2), rect.height / 16, rect.height - 13, paint); } else { canvas.drawLine(Offset(lineRate * rect.width, d / 2), Offset(lineRate * rect.width, rect.height - d / 2), paint); } } _drawDashLine( Canvas canvas, Offset start, double count, double length, Paint paint) { double step = length / count / 2; for (int i = 0; i < count; i++) { Offset offset = start + Offset(0, 2 * step * i); canvas.drawLine(offset, offset + Offset(0, step), paint); } } @override ShapeBorder scale(double t) { return this; } } ================================================ FILE: modules/basic_system/app/lib/app/style/shape/techno_shape.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020-03-06 /// contact me by email 1981462002@qq.com /// 说明: 打个洞 /// offset 洞的偏移量分率 x,y 在 0~1 之间 /// size 洞的大小 class TechnoShapeBorder extends ShapeBorder { final Path outLinePath = Path(); final Paint _paint = Paint(); final Path innerLinePathTop = Path(); final Color color; final double cornerWidth; final double spanWidth; final double storkWidth; final double innerRate; TechnoShapeBorder( {this.color = Colors.green, this.cornerWidth = 10.0, this.spanWidth = 2.5, this.innerRate = 0.15, this.storkWidth = 1.0}) { _paint ..color = color ..strokeWidth = storkWidth; } @override EdgeInsetsGeometry get dimensions => EdgeInsets.zero; @override Path getInnerPath(Rect rect, {TextDirection? textDirection}) { Path path = Path(); path.addRRect(RRect.fromRectAndRadius(rect, const Radius.circular(5))); return path; } @override Path getOuterPath(Rect rect, {TextDirection? textDirection}) { double width = rect.width; outLinePath ..moveTo(cornerWidth, 0) ..relativeLineTo(width - cornerWidth*2, 0) ..relativeLineTo(cornerWidth, cornerWidth) ..relativeLineTo(0,rect.height - cornerWidth*2) ..relativeLineTo( - cornerWidth, cornerWidth) ..relativeLineTo(-((width-innerRate*2*width)/2-cornerWidth-2*spanWidth), 0) ..relativeLineTo(-spanWidth*2, -spanWidth) ..relativeLineTo(-rect.width * innerRate * 2, 0) ..relativeLineTo(-spanWidth * 2, spanWidth) ..relativeLineTo(-((width-innerRate*2*width)/2-cornerWidth-2*spanWidth), 0) ..lineTo(0, rect.height - cornerWidth) ..lineTo(0, cornerWidth) ..close(); return outLinePath; } @override void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { canvas.drawPath( outLinePath, _paint..style = PaintingStyle.stroke); innerLinePathTop ..moveTo(rect.width / 2, 0) ..relativeLineTo(rect.width * innerRate, 0) ..relativeLineTo(-spanWidth * 2, spanWidth) ..relativeLineTo(-rect.width * innerRate * 2, 0) ..relativeLineTo(-spanWidth * 2, -spanWidth) ..close(); canvas.drawPath(innerLinePathTop.shift(Offset(spanWidth*2,0)), _paint..style = PaintingStyle.fill); } @override ShapeBorder scale(double t) { return this; } } ================================================ FILE: modules/basic_system/app/lib/app/style/unit_color.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020-04-19 /// contact me by email 1981462002@qq.com /// 说明: class UnitColor { // 收藏夹提供的颜色 static const collectColorSupport = [ Color(0xFFF2F2F2), Colors.black, Colors.red, Colors.orange, Colors.yellow, Colors.green, Colors.blue, Colors.indigo, Colors.purple, Colors.cyanAccent, Color(0xffd1d08f), Colors.pink, Colors.amber, Colors.lime, Colors.teal, Colors.cyan, Color(0xff586CF2), Colors.purpleAccent, ]; // 文字相关 static const Color input_border_color = Color(0xffD0D7DD); static const Color text_color = Color(0xff323C47); static const Color input_hit_text_color = Color(0xff939EA7); static const Color head_text_color = Color(0xff666666); static const Color scaffoldBgLight = Color(0xffF3F4F6); // 缺省相关 static const Color error_color = Colors.red; static const Color warning_color = Colors.orangeAccent; } ================================================ FILE: modules/basic_system/app/lib/app/style/unit_text_style.dart ================================================ import 'package:flutter/material.dart'; import '../theme/size_unit.dart'; import 'unit_color.dart'; ///文本样式 class UnitTextStyle { // 标题加黑 static const labelBold = TextStyle(fontWeight: FontWeight.bold, fontSize: 16); // 闪屏页文字阴影样式 static const splashShadows = TextStyle( color: Colors.grey, shadows: [ Shadow( color: Colors.black, blurRadius: 0.5, offset: Offset(0.1, 0.1)) ], fontSize: 12); static const shadowTextStyle = TextStyle(color: Colors.grey, shadows: [ Shadow(color: Colors.white, offset: Offset(.5, .5), blurRadius: .5) ]); // 过时文字样式 static const deprecatedChip = TextStyle( fontSize: 12, color: Colors.white, decoration: TextDecoration.lineThrough, decorationThickness: 2, ); static const commonChip = TextStyle( fontSize: 12, color: Colors.white, ); static const TextStyle hintStyle = TextStyle( color: UnitColor.input_hit_text_color, fontSize: SizeUnit.input_hit_text_size); static const TextStyle primary = TextStyle( color: UnitColor.text_color, fontSize: SizeUnit.input_text_size); static const TextStyle headTextStyle = TextStyle( color: UnitColor.head_text_color, fontSize: SizeUnit.head_text_size); static const TextStyle smallSubTextStyle = TextStyle( color: UnitColor.input_hit_text_color, fontSize: SizeUnit.small_text_size); static const TextStyle bigTextStyle = TextStyle( color: UnitColor.text_color, fontSize: SizeUnit.big_text_size); } ================================================ FILE: modules/basic_system/app/lib/app/theme/app_theme.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; ThemeData darkTheme(AppConfig state) { const Color scaffoldBackgroundColor = Color(0xff010201); SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarBrightness: Brightness.dark, statusBarIconBrightness: Brightness.light, systemNavigationBarColor: Color(0xff181818)); return ThemeData( scaffoldBackgroundColor: scaffoldBackgroundColor, pageTransitionsTheme: const PageTransitionsTheme(builders: { TargetPlatform.android: SlidePageTransitionsBuilder(), TargetPlatform.iOS: SlidePageTransitionsBuilder(), TargetPlatform.macOS: FadePageTransitionsBuilder(), TargetPlatform.windows: FadePageTransitionsBuilder(), TargetPlatform.linux: FadePageTransitionsBuilder(), }), tabBarTheme: const TabBarThemeData( dividerColor: Colors.transparent, ), fontFamily: state.fontFamily, useMaterial3: true, brightness: Brightness.dark, primaryColor: const Color(0xff4699FB), listTileTheme: const ListTileThemeData( tileColor: Color(0xff181818), textColor: Color(0xffD6D6D6), ), ///设置选中的文本颜色 textSelectionTheme: TextSelectionThemeData( selectionColor: Colors.blue.withOpacity(0.3), ), appBarTheme: AppBarTheme( systemOverlayStyle: overlayStyle, elevation: 0, centerTitle: true, backgroundColor: const Color(0xff181818), iconTheme: const IconThemeData(color: Color(0xffCCCCCC)), titleTextStyle: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xffCCCCCC))), floatingActionButtonTheme: const FloatingActionButtonThemeData( foregroundColor: Colors.white, backgroundColor: Color(0xff4699FB)), dividerTheme: DividerThemeData( color: const Color(0xff2F2F2F), space: px1, thickness: divHeight, ), bottomNavigationBarTheme: const BottomNavigationBarThemeData( backgroundColor: Color(0xff181818), selectedItemColor: Color(0xff4699FB)), ); } double get divHeight { if (kAppEnv.isAndroid) { return 0.2; } return px1; } ThemeData lightTheme(AppConfig state) { SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.dark, systemNavigationBarColor: Colors.transparent); String fontFamily = state.fontFamily; if (kAppEnv.isWindows) { fontFamily = '宋体'; } return ThemeData( fontFamily: '', primaryColor: state.themeColor.color, scaffoldBackgroundColor: const Color(0xffF3F4F6), useMaterial3: true, // Android 使用 Material3 chipTheme: const ChipThemeData(padding: EdgeInsets.symmetric(horizontal: 10)), listTileTheme: const ListTileThemeData( tileColor: Colors.white, textColor: Color(0xff333333)), ///设置选中的文本颜色 textSelectionTheme: TextSelectionThemeData( selectionColor: Colors.blue.withOpacity(0.3), ), dividerTheme: DividerThemeData( color: const Color(0xffDEE0E2), space: px1, thickness: divHeight, ), pageTransitionsTheme: const PageTransitionsTheme(builders: { TargetPlatform.android: SlidePageTransitionsBuilder(), TargetPlatform.iOS: SlidePageTransitionsBuilder(), TargetPlatform.macOS: FadePageTransitionsBuilder(), TargetPlatform.windows: FadePageTransitionsBuilder(), TargetPlatform.linux: FadePageTransitionsBuilder(), }), tabBarTheme: TabBarThemeData( dividerColor: Colors.transparent, // labelStyle: TextStyle(fontFamily: fontFamily), // unselectedLabelStyle: TextStyle(fontFamily: fontFamily), splashFactory: NoSplash.splashFactory, overlayColor: WidgetStateProperty.resolveWith( (Set states) { return states.contains(WidgetState.focused) ? null : Colors.transparent; }, ), ), bottomNavigationBarTheme: const BottomNavigationBarThemeData(backgroundColor: Colors.white), appBarTheme: AppBarTheme( systemOverlayStyle: overlayStyle, elevation: 0, centerTitle: true, backgroundColor: Colors.white, titleTextStyle: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black, fontFamily: fontFamily, ), ), ); } ================================================ FILE: modules/basic_system/app/lib/app/theme/size_unit.dart ================================================ /// create by 张风捷特烈 on 2020/11/17 /// contact me by email 1981462002@qq.com /// 说明: // ignore_for_file: constant_identifier_names class SizeUnit { // 小文字大小 static const double home_h_padding = 10; static const double home_v_padding = 8; // 文字相关 static const double input_hit_text_size = 13; static const double input_text_size = 13; // 小文字大小 static const double small_text_size = 12; // 标题文字大小 static const double title_text_size = 14; // 头文字大小 static const double head_text_size = 18; // 大文字大小 static const double big_text_size = 22; //底栏图标大小 static const double default_bottom_nav_icon = 24; static const double active_bottom_nav_icon = 29; //底栏高 static const double bottom_nav_height = 50; static const double bottom_nav_right_width = 120; } ================================================ FILE: modules/basic_system/app/lib/app.dart ================================================ library app; export 'app_config/bloc/bloc.dart'; export 'app_config/repository/repository.dart'; export 'app/cons/cons.dart'; export 'app/cons/global_value.dart'; export 'app/cons/path_unit.dart'; export 'app/cons/sp.dart'; export 'app/cons/str_unit.dart'; export 'app/res/toly_icon.dart'; export 'app/theme/size_unit.dart'; export 'app/theme/app_theme.dart'; export 'app/style/unit_text_style.dart'; export 'app/style/unit_color.dart'; export 'app/style/gap.dart'; export 'app/style/shape/coupon_shape_border.dart'; export 'app/style/shape/techno_shape.dart'; export 'app/style/behavior/no_scroll_behavior.dart'; export 'package:fx_platform_adapter/fx_platform_adapter.dart'; export 'package:fx_go_router_ext/fx_go_router_ext.dart'; export 'app_config/app_config.dart'; export 'app/action/action.dart'; export 'app/router/app_route.dart'; export 'view/view.dart'; export 'http/http.dart'; export 'event/api.dart'; export 'news/news_bloc.dart'; export 'news/cacheable.dart'; ================================================ FILE: modules/basic_system/app/lib/app_config/app_config.dart ================================================ export 'bloc/state.dart'; export 'bloc/bloc.dart'; export 'repository/repository.dart'; ================================================ FILE: modules/basic_system/app/lib/app_config/bloc/bloc.dart ================================================ import 'dart:async'; import 'package:app/app.dart'; // import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:l10n/l10n.dart'; import 'package:storage/storage.dart'; /// create by 张风捷特烈 on 2020-03-22 /// contact me by email 1981462002@qq.com /// 说明: 全局信息的bloc class AppConfigBloc extends Cubit { AppConfigBloc() : super(const AppConfig()); @override Future close() async { // _subscription.cancel(); super.close(); } void init(AppConfig state) { emit(state); } AppConfigCao get cao => SpStorage().appConfig; // 切换字体事件处理 : 固化索引 + 产出新状态 void switchFontFamily(String family) async { AppConfig newState = state.copyWith(fontFamily: family); cao.write(newState.toAppConfigPo()); emit(newState); } // 切换语言事件处理 : 固化索引 + 产出新状态 void switchLanguage(Language language) async { AppConfig newState = state.copyWith(language: language); cao.write(newState.toAppConfigPo()); emit(newState); } // 切换主题色事件处理 : 固化索引 + 产出新状态 void switchThemeColor(ThemeColor color) async { AppConfig newState = state.copyWith(themeColor: color); cao.write(newState.toAppConfigPo()); emit(newState); } // 切换背景显示事件处理 : 固化数据 + 产出新状态 void switchShowBg(bool show) async { AppConfig newState = state.copyWith(showBackGround: show); cao.write(newState.toAppConfigPo()); emit(newState); } // 切换背景显示事件处理 : 产出新状态 void switchShowOver(bool show) async { AppConfig newState = state.copyWith(showPerformanceOverlay: show); cao.write(newState.toAppConfigPo()); emit(newState); } // 切换code样式事件处理 : 固化索引 + 产出新状态 void switchCoderTheme(int codeStyleIndex) async { AppConfig newState = state.copyWith(codeStyleIndex: codeStyleIndex); cao.write(newState.toAppConfigPo()); emit(newState); } // 切换item样式事件处理 : 固化索引 + 产出新状态 void changeItemStyle(int index) async { AppConfig newState = state.copyWith(itemStyleIndex: index); cao.write(newState.toAppConfigPo()); emit(newState); } void changeThemeMode(ThemeMode style) async { AppConfig newState = state.copyWith(themeMode: style); cao.write(newState.toAppConfigPo()); emit(newState); } void switchShowTool(bool show) async { AppConfig newState = state.copyWith(showOverlayTool: show); cao.write(newState.toAppConfigPo()); emit(newState); } } ================================================ FILE: modules/basic_system/app/lib/app_config/bloc/state.dart ================================================ import 'dart:io'; import 'package:app/app.dart'; import 'package:app/app/cons/cons.dart'; // import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:l10n/l10n.dart'; import 'package:storage/storage.dart'; import 'package:toly_ui/code/code.dart'; /// create by 张风捷特烈 on 2020-04-11 /// contact me by email 1981462002@qq.com /// 说明: 全局状态类 class AppConfig extends Equatable { /// [fontFamily] 文字字体 final String fontFamily; /// [themeColor] 主题色 final ThemeColor themeColor; /// [showBackGround] 是否显示主页背景图 final bool showBackGround; /// [codeStyleIndex] 代码样式 索引 final int codeStyleIndex; /// [itemStyleIndex] 主页item样式 索引 final int itemStyleIndex; /// [showPerformanceOverlay] 是否显示性能浮层 final bool showPerformanceOverlay; /// [showOverlayTool] 是否显示浮动工具 final bool showOverlayTool; /// [appStyle] app 深色样式; final ThemeMode themeMode; // final ConnectivityResult netConnect; final Language language; const AppConfig({ this.fontFamily = '宋体', this.language = Language.zh_CN, this.themeColor = ThemeColor.indigo, this.themeMode = ThemeMode.system, this.showBackGround = true, this.codeStyleIndex = 0, this.itemStyleIndex = 0, this.showPerformanceOverlay = false, this.showOverlayTool = true, // this.netConnect = ConnectivityResult.none, }); String get localeValue => language.locale.toString(); @override List get props => [ fontFamily, themeColor, showBackGround, codeStyleIndex, itemStyleIndex, themeMode, showOverlayTool, showPerformanceOverlay, // netConnect, language, ]; AppConfig copyWith({ String? fontFamily, String? dbPath, ThemeColor? themeColor, bool? showBackGround, Language? language, int? codeStyleIndex, int? itemStyleIndex, bool? showPerformanceOverlay, bool? showOverlayTool, ThemeMode? themeMode, // ConnectivityResult? netConnect, }) => AppConfig( fontFamily: fontFamily ?? this.fontFamily, language: language ?? this.language, themeColor: themeColor ?? this.themeColor, showBackGround: showBackGround ?? this.showBackGround, codeStyleIndex: codeStyleIndex ?? this.codeStyleIndex, showOverlayTool: showOverlayTool ?? this.showOverlayTool, itemStyleIndex: itemStyleIndex ?? this.itemStyleIndex, themeMode: themeMode ?? this.themeMode, showPerformanceOverlay: showPerformanceOverlay ?? this.showPerformanceOverlay, // netConnect: netConnect ?? this.netConnect, ); // 将 AppState 状态数据转换为配置对象,以便存储 AppConfigPo toAppConfigPo() => AppConfigPo( showBackGround: showBackGround, showOverlayTool: showOverlayTool, showPerformanceOverlay: showPerformanceOverlay, fontFamilyIndex: Cons.kFontFamilySupport.indexOf(fontFamily), themeColorIndex: themeColor.index, codeStyleIndex: codeStyleIndex, themeModeIndex: themeMode.index, itemStyleIndex: itemStyleIndex, languageIndex: language.index, ); // 根据存储的配置信息对象,形成 AppState 状态数据 factory AppConfig.fromPo(AppConfigPo po) { return AppConfig( fontFamily: Cons.kFontFamilySupport[po.fontFamilyIndex], themeColor: ThemeColor.values[po.themeColorIndex], showBackGround: po.showBackGround, language: Language.values[po.languageIndex], codeStyleIndex: po.codeStyleIndex, itemStyleIndex: po.itemStyleIndex, showPerformanceOverlay: po.showPerformanceOverlay, showOverlayTool: po.showOverlayTool, themeMode: ThemeMode.values[po.themeModeIndex], ); } HighlighterStyle get codeStyle => Cons.codeThemeSupport.keys.toList()[codeStyleIndex]; @override String toString() { return 'AppState{fontFamily: $fontFamily, themeColor: $themeColor, showBackGround: $showBackGround, codeStyleIndex: $codeStyleIndex, itemStyleIndex: $itemStyleIndex, showPerformanceOverlay: $showPerformanceOverlay}'; } } ================================================ FILE: modules/basic_system/app/lib/app_config/repository/repository.dart ================================================ ================================================ FILE: modules/basic_system/app/lib/event/api.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/foundation.dart'; import 'package:fx_dio/fx_dio.dart'; void sendEvent(int id, {String? extra}) async { if (kDebugMode) return; Host host = FxDio()(); ApiRet ret = await host.post( '/event', data: {"event": id}, convertor: (data) => data, ); } ================================================ FILE: modules/basic_system/app/lib/http/flutter_unit/api/upgrade_api.dart ================================================ import 'dart:async'; import 'package:fx_dio/fx_dio.dart'; import 'package:app/app.dart'; import 'package:fx_updater/fx_updater.dart'; class UnitUpgradeApi implements UpgradeApi { @override Future> fetch(int appId, String locale) async { Host host = FxDio()(); String path = ScienceApi.appVersion.path; return host.get( path, queryParameters: { 'app_id': 1, 'os': kAppEnv.os.name, 'locale': locale, }, convertor: (data) => AppInfo.fromMap(data), ); } } ================================================ FILE: modules/basic_system/app/lib/http/flutter_unit/unit_host.dart ================================================ import 'package:fx_dio/fx_dio.dart'; class UnitHost extends Host { const UnitHost(); @override Map get value => { HostEnv.release: 'api.toly1994.com', HostEnv.dev: '127.0.0.1', }; @override HostConfig get config => const HostConfig( scheme: 'http', port: 8080, apiNest: '/api/v1', ); @override HostEnv get env => HostEnv.dev; } enum UnitApi { hello("/hello"), repository("/repository/name/FlutterUnit"), point("/point"), pointComment("/pointComment/"), appInfo("/appInfo/name/"), ; final String path; final Method? method; const UnitApi(this.path, [this.method = Method.get]); } ================================================ FILE: modules/basic_system/app/lib/http/http.dart ================================================ export 'flutter_unit/api/upgrade_api.dart'; export 'flutter_unit/unit_host.dart'; export 'science/science_host.dart'; export 'science/science_rep_interceptor.dart'; export 'register.dart'; ================================================ FILE: modules/basic_system/app/lib/http/register.dart ================================================ import 'dart:async'; import 'package:app/app.dart'; import 'package:fx_dio/fx_dio.dart'; import 'package:unit_env/unit_env.dart'; import 'http.dart'; void registerHttpClient(bool isZh) { FxDio() .register(const ScienceHost(), interceptors: [ScienceRepInterceptor()]); FxDio().register(const Unit3Host()); FxDio().register(const UnitHost()); UnitEnv.userName = '游客:${kAppMeta.uuid.substring(0, 6)}'; FxDio().auth(ScienceAuth()); FxDio().auth(UnitApiAuth(isZh)); } class ScienceAuth extends ApiAuth { @override FutureOr> get buildHeaders => kAppMeta.toHeaderJson(); } class UnitApiAuth extends ApiAuth { final bool isZh; UnitApiAuth(this.isZh); @override FutureOr> get buildHeaders => { 'locale': isZh ? 'zh-CN' : 'en', }; } ================================================ FILE: modules/basic_system/app/lib/http/science/science_host.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:fx_dio/fx_dio.dart'; class ScienceHost extends Host { const ScienceHost(); @override Map get value => { HostEnv.release: 'toly1994.com', HostEnv.dev: '172.20.10.4', // HostEnv.dev: '192.168.1.107', }; @override HostConfig get config => const HostConfig( scheme: 'http', port: 3000, apiNest: '/api/v1', ); @override HostEnv get env => HostEnv.release; } enum ScienceApi { appVersion("/app_version"), ; final String path; final Method? method; const ScienceApi(this.path, [this.method = Method.get]); } ================================================ FILE: modules/basic_system/app/lib/http/science/science_rep_interceptor.dart ================================================ import 'package:dio/dio.dart'; import 'package:fx_trace/fx_trace.dart'; class ScienceRepInterceptor extends InterceptorsWrapper { @override void onResponse(Response response, ResponseInterceptorHandler handler) { handleResponse(response); super.onResponse(response, handler); } void handleResponse(Response response) { if (response.statusCode == HttpCode.ok.value) { bool success = response.data?['status'] == true; String message = response.data['msg']; if (success) { response.data = response.data['data']; response.statusMessage = message; } else { throw ApiTrace(message: message, error: response.data); } return; } throw ApiTrace(message: response.statusMessage ?? '', error: response.data); } } class ApiTrace with Code, Trace { @override final int? value; @override final String message; @override final Object error; ApiTrace({ this.value, required this.message, required this.error, }); @override Code? get code => this; @override StackTrace? get stack => null; } ================================================ FILE: modules/basic_system/app/lib/news/cacheable.dart ================================================ import 'dart:async'; import 'dart:convert'; import 'package:storage/storage.dart'; import 'package:fx_dao/fx_dao.dart'; import 'package:shared_preferences/shared_preferences.dart'; mixin Cacheable { FutureOr save(V po); FutureOr find({bool shouldRemove = true}); FutureOr remove(); } mixin TimeoutCache on Cacheable { String get cacheKey; int get maxCacheMs => 1000 * 60 * 30; SharedPreferences get spf => SpStorage().spf; ConvertorList get convertor; @override FutureOr find({bool shouldRemove = true}) async { String? data = spf.getString(cacheKey); if (data == null || data.isEmpty) return null; try { dynamic map = jsonDecode(data); dynamic jsonValue = map['value']; if (jsonValue == null) return null; int time = map['time'] ?? 0; int nowMs = DateTime.now().millisecondsSinceEpoch; int deadMs = time + maxCacheMs; if (nowMs > deadMs && shouldRemove) { await remove(); return null; } List v = jsonDecode(jsonValue); return convertor(v); } catch (e) { print(e); } return null; } @override FutureOr save(V po) async { String data = json.encode(TimeoutPo.value(json.encode(po))); return spf.setString(cacheKey, data); } @override FutureOr remove() { return spf.remove(cacheKey); } } class TimeoutPo extends Po { final int time; final String data; TimeoutPo.value(this.data) : time = DateTime.now().millisecondsSinceEpoch; TimeoutPo({ required this.time, required this.data, }); @override Map toJson() => { 'time': time, 'value': data, }; factory TimeoutPo.fromJson(dynamic map) { return TimeoutPo( time: map['time'], data: map['value'], ); } } ================================================ FILE: modules/basic_system/app/lib/news/news_bloc.dart ================================================ // import 'package:flutter_bloc/flutter_bloc.dart'; // import 'package:fx_dao/src/model/po.dart'; // import 'package:fx_dao/src/table/dao.dart'; // import 'package:note/note.dart'; // import 'package:fx_dio/fx_dio.dart'; // import 'cacheable.dart'; // // class NewsBloc extends Cubit // with Cacheable>, TimeoutCache> { // NewsBloc() : super(NewsState(headerNews: [])); // // ArticleRepository _repository = HttpArticleRepository(); // // @override // String get cacheKey => 'flutter.unit.news'; // // void load() async { // List? retCache = await find(); // if (retCache != null) { // print("=====load in cache========="); // emit(NewsState(headerNews: retCache)); // return; // } // refreshFromNet(); // } // // Future refreshFromNet() async { // ApiRet> ret = await _repository.getArticlesByTag(1); // print("=====load in net========="); // if (ret.success) { // save(ret.data); // emit(NewsState(headerNews: ret.data)); // } // } // // @override // ConvertorList> get convertor => (e) { // return e.map(ArticlePo.fromCache).toList(); // }; // } // // class NewsState { // final List headerNews; // // NewsState({ // required this.headerNews, // }); // } ================================================ FILE: modules/basic_system/app/lib/view/about/about_app_page.dart ================================================ /// create by 张风捷特烈 on 2020-04-13 /// contact me by email 1981462002@qq.com /// 说明: ... import 'package:app/app.dart'; import 'package:components/components.dart'; import 'package:flutter/material.dart'; import 'package:fx_go_router_ext/fx_go_router_ext.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:url_launcher/url_launcher.dart'; class AboutAppPage extends StatelessWidget { const AboutAppPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Theme( data: ThemeData( brightness: Brightness.light ), child: Scaffold( backgroundColor: Colors.white, body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ Container( height: 150, width: MediaQuery.of(context).size.width, margin: const EdgeInsets.only(bottom: 50), child: Image.asset( 'assets/images/sabar.webp', fit: BoxFit.cover, ), ), _buildBar(context), Positioned( bottom: 0, left: 50, child: FeedbackWidget( onEnd : (){ Navigator.push(context, SlidePageRoute(child: const FlutterUnitTimeLine())); }, child: CircleImage( size: 100, shadowColor: Theme.of(context).primaryColor, image: const AssetImage('assets/images/icon_head.webp'), ), )), ], ), Expanded( child: SingleChildScrollView( child: Container( margin: const EdgeInsets.all(24), child: _buildInfo(), ), ), ), ], ), ), ); } Widget _buildBar(BuildContext context) { return Container( height: kToolbarHeight, margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top), child: Row( children: [ GestureDetector( onTap: () => Navigator.of(context).pop(), child: Container( padding: const EdgeInsets.only(left: 10), child: Icon( Icons.arrow_back, size: 30, color: Theme.of(context).primaryColor, ), ), ), const Spacer(), FeedbackWidget( onPressed: () => _launchURL("mailto:1981462002@qq.com?subject=来自Flutter Unit"), child: Icon( TolyIcon.icon_email, size: 20, color: Theme.of(context).primaryColor, ), ), const SizedBox( width: 20, ) ], ), ); } _launchURL(String url) async { if (await canLaunch(url)) { await launch(url); } else { } } Widget _buildInfo() { return Stack( children: [ Positioned( right: 10, top: 0, child: Wrap( spacing: 20, children: [ FeedbackWidget( onPressed: () => _launchURL("https://github.com/toly1994328/FlutterUnit"), child: Wrap( direction: Axis.vertical, crossAxisAlignment: WrapCrossAlignment.center, children: const [ Icon( TolyIcon.icon_github, size: 35, ), Text('Github') ], )), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ Text( 'Flutter Unit', style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), ), SizedBox(height: 20), Text( 'The Unity Of Flutter, The Unity Of Coder.', style: TextStyle(fontSize: 16), ), SizedBox(height: 10), Text( 'Flutter的联合,编程者的联合。', style: TextStyle(fontSize: 16), ), Divider( height: 20, ), InfoPanel( title: '项目简介', info: 'Flutter Unit 是一个非盈利性的开源项目,' '旨在提供全面的 Flutter 学习指南及编程者的交流技术的接口。' '由【张风捷特烈】提供技术支持和全权维护。唯一开源网站网址:\n ' 'https://github.com/toly1994328/FlutterUnit', ), Divider( height: 20, ), InfoPanel( title: 'Flutter Unit 1.0', info: 'Flutter Unit 1.0 核心计划是收录widget,即widget集录。' '目前收录组件 283 个,均可在 app 中进行查看。' '项目中提供widget图鉴文件可供下载参考。功能主要如下:\n' '○ 280+的 Flutter 组件收录和详情介绍。\n' '○ 对一些重要的组件提供操作体验。\n' '○ link to功能,查看组件时可以切换到相关组件。\n' '○ 组件收藏和取消收藏功能。\n' '○ 主题、字体设置,代码风格等全局状态管理。\n' '○ 搜索功能和组件星级分类。', ), Divider( height: 20, ), InfoPanel( title: 'Flutter Unit 2.0 ', info: '○ 317 个 Flutter 组件收录和详情介绍。\n' '○ 绘制集录用于收录绘制相关的优秀示例。\n' '○ 要点集录用于收录 Flutter 相关的小知识。\n' '○ 时光轴,查看 FlutterUnit 重要事件。\n' '○ 实现应用内更新功能,方便使用者及时更新到最新版体验。' ) ], ), ], ); } } class InfoPanel extends StatelessWidget { final String title; final String info; const InfoPanel({Key? key, required this.title,required this.info}) : super(key: key); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Circle(color: Theme.of(context).primaryColor), Padding( padding: const EdgeInsets.only(left: 15,top: 15,bottom: 15), child: Text(title,style: const TextStyle(fontSize: 16,fontWeight: FontWeight.bold),), ) ], ), Panel( color: Theme.of(context).primaryColor.withAlpha(33), child: Text( info, style: const TextStyle(color: Colors.grey, fontSize: 13, shadows: [ Shadow( color: Colors.white, offset: Offset(1,1) ) ]), ), ), ], ); } } ================================================ FILE: modules/basic_system/app/lib/view/about/about_me_page.dart ================================================ /// create by 张风捷特烈 on 2020-04-13 /// contact me by email 1981462002@qq.com /// 说明: ... import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:url_launcher/url_launcher.dart'; class AboutMePage extends StatelessWidget { const AboutMePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Theme( data: ThemeData( brightness: Brightness.light ), child:Scaffold( backgroundColor: Colors.white, body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ Container( height: 180, width: MediaQuery.of(context).size.width, margin:const EdgeInsets.only(bottom: 50), child: Image.asset( 'assets/images/sabar.webp', fit: BoxFit.cover, ), ), _buildBar(context), Positioned( bottom: 0, left: 50, child: CircleImage( size: 100, shadowColor: Theme.of(context).primaryColor, image: const AssetImage('assets/images/icon_head.webp'), )), ], ), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 15), child: Stack(children: [ Positioned( right: 10, top: 0, child: _buildLinkIcon(), ), _buildInfo() ]), ), ), ], ), )); } Widget _buildBar(BuildContext context) { return Container( height: kToolbarHeight, margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top), child: Row( children: [ GestureDetector( onTap: () => Navigator.of(context).pop(), child: Padding( padding: const EdgeInsets.only(left: 10), child: Icon( Icons.arrow_back, size: 30, color: Theme.of(context).primaryColor, ), ), ), const Spacer(), FeedbackWidget( onPressed: () => _launchURL("mailto:1981462002@qq.com?subject=来自Flutter Unit"), child: Icon( TolyIcon.icon_email, size: 20, color: Theme.of(context).primaryColor, ), ), const SizedBox(width: 20) ], ), ); } _launchURL(String url) async { if (await canLaunch(url)) { await launch(url); } else { debugPrint('Could not launch $url'); } } Widget _buildInfo() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('张风捷特烈', style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)), const SizedBox(height: 20), const Divider(height: 18), const Text('The King Of Coder. 「编程之王」', style: TextStyle(fontSize: 16)), const SizedBox(height: 10), const Text('海的彼岸有我未曾见证的风采。', style: TextStyle(fontSize: 16)), const SizedBox(height: 10), const Text( '微信群: 编程技术交流圣地 -【Flutter群】\n' '愿青梅煮酒,与君天涯共话。', style: TextStyle(color: Colors.grey)), const SizedBox(height: 10), Expanded( child: Padding( padding: const EdgeInsets.all(30.0), child: AspectRatio( aspectRatio: 1, child: Image.asset( 'assets/images/wechat.webp', fit: BoxFit.fitWidth, )), )), const Center( child: Text( '我的微信', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), ], ); } Wrap _buildLinkIcon() { return Wrap( spacing: 20, children: [ FeedbackWidget( onPressed: () => _launchURL("https://juejin.im/user/5b42c0656fb9a04fe727eb37"), child: Wrap( direction: Axis.vertical, crossAxisAlignment: WrapCrossAlignment.center, children:const [ Icon( TolyIcon.icon_juejin, size: 35, color: Colors.blue, ), Text('掘金') ], )), FeedbackWidget( onPressed: () => _launchURL("https://github.com/toly1994328"), child: Wrap( direction: Axis.vertical, crossAxisAlignment: WrapCrossAlignment.center, children: const[ Icon( TolyIcon.icon_github, size: 35, ), SizedBox(height: 4,), Text('Github') ], )), ], ); } } ================================================ FILE: modules/basic_system/app/lib/view/about/version_info.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:fx_updater/fx_updater.dart'; import 'package:go_router/go_router.dart'; import 'package:l10n/l10n.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:url_launcher/url_launcher.dart'; /// create by 张风捷特烈 on 2020/6/16 /// contact me by email 1981462002@qq.com /// 说明: class VersionInfo extends StatelessWidget { const VersionInfo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { Color? bgColor = Theme.of(context).listTileTheme.tileColor; return Scaffold( backgroundColor: bgColor, appBar: AppBar( backgroundColor: bgColor, elevation: 0, iconTheme: const IconThemeData(color: Colors.grey), ), body: ConstrainedBox( constraints: const BoxConstraints.expand(), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.only(top: 28.0), child: _buildTop(), ), _buildCenter(context), const Spacer(), Padding( padding: const EdgeInsets.only(bottom: 8.0), child: buildBottom(context), ) ], ), ), ); } Widget _buildTop() { return Wrap( direction: Axis.vertical, crossAxisAlignment: WrapCrossAlignment.center, spacing: 10, children: const [ CircleImage( image: AssetImage("assets/images/icon_head.webp"), size: 80, ), Text( 'Flutter Unit', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), VersionShower(), ], ); } Widget _buildCenter(BuildContext context) { const TextStyle labelStyle = TextStyle(fontSize: 13); return Padding( padding: const EdgeInsets.only(left: 20.0, right: 20, top: 20), child: ScrollConfiguration( behavior: NoScrollBehavior(), child: ListView( shrinkWrap: true, children: [ const Divider( height: 1, ), ListTile( title: Text( context.l10n.appDetails, style: labelStyle, ), trailing: _nextIcon(context), onTap: () => context.push('/about_app'), ), const Divider(height: 1, indent: 10), const AppUpdatePanel(), const Divider(height: 1, indent: 10), ListTile( title: Text(context.l10n.checkDatabaseNewVersion, style: labelStyle), trailing: _nextIcon(context), onTap: () async {}, ), const Divider( height: 1, ), ], ), ), ); } Widget _nextIcon(BuildContext context) => const Icon(Icons.chevron_right, color: Colors.grey); Widget buildBottom(BuildContext context) { return Wrap( direction: Axis.vertical, crossAxisAlignment: WrapCrossAlignment.center, spacing: 4, children: [ FeedbackWidget( onPressed: () { _launchURL("https://github.com/toly1994328/FlutterUnit"); }, child: Text( context.l10n.viewThisProjectGithubRepository, style: TextStyle( fontSize: 12, color: Color(0xff616C84), ), )), const Text( 'Power By 张风捷特烈', style: TextStyle(fontSize: 12, color: Colors.grey), ), const Text( 'Copyright © 2018-2024 Toly1994', style: TextStyle(fontSize: 12, color: Colors.grey), ), ], ); } void _launchURL(String url) async { if (await canLaunch(url)) { await launch(url); } else {} } } ================================================ FILE: modules/basic_system/app/lib/view/account/desk/desk_account_page.dart ================================================ import 'package:components/components.dart'; import 'package:flutter/material.dart'; import 'package:l10n/l10n.dart'; import 'package:toly_ui/toly_ui.dart'; import 'sliver_cellection_panel.dart'; import 'sliver_list_panel.dart'; import 'sliver_share_panel.dart'; import 'user_header.dart'; class DeskAccountPage extends StatefulWidget { const DeskAccountPage({super.key}); @override State createState() => _DeskAccountPageState(); } class _DeskAccountPageState extends State with SingleTickerProviderStateMixin { late TabController tabController; int activeIndex = 0; @override void initState() { super.initState(); tabController = TabController(length: 3, vsync: this); } @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; List tabs = [ context.l10n.homeAccountTabInfo, context.l10n.homeAccountTabMe, context.l10n.homeAccountSupport, ]; return Scaffold( body: Column( children: [ // DeskAccountTopBar( // leading: Row( // children: [ // FlutterUnitText( // text: 'Flutter Unit', // color: Theme.of(context).primaryColor, // fontSize: 24, // ), // const SizedBox( // width: 20, // ), // Text( // context.l10n.slogan, // style: TextStyle(color: Colors.grey), // ) // ], // ), // ), Expanded( child: CustomScrollView( slivers: [ SliverToBoxAdapter( child: UserHeader(), ), SliverPinnedHeader( color: isDark ? Color(0xff2C3036) : Colors.white, child: TabBar( onTap: (i) { setState(() { activeIndex = i; }); }, tabAlignment: TabAlignment.start, indicatorSize: TabBarIndicatorSize.label, isScrollable: true, // indicator: RoundRectTabIndicator( // borderSide: BorderSide(color: themeColor, width: 3), // ), labelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), controller: tabController, // labelColor: themeColor, indicatorWeight: 3, unselectedLabelColor: Colors.grey, // indicatorColor: themeColor, tabs: tabs .map((String name) => Tab(text: name)) .toList(), )), if (activeIndex == 0) const SliverCollectionPanel(), if (activeIndex == 1) const SliverSharePanel(), if (activeIndex == 2) const SliverListPanel(), ], )) ], ), ); } } ================================================ FILE: modules/basic_system/app/lib/view/account/desk/sliver_cellection_panel.dart ================================================ import 'package:flutter/material.dart'; import '../../about/about_app_page.dart'; class SliverCollectionPanel extends StatelessWidget { const SliverCollectionPanel({super.key}); @override Widget build(BuildContext context) { List items = [ InfoPanel( title: '项目简介', info: 'Flutter Unit 是一个非盈利性的开源项目,' '旨在提供全面的 Flutter 学习指南及编程者的交流技术的接口。' '由【张风捷特烈】提供技术支持和全权维护。唯一开源网站网址:\n ' 'https://github.com/toly1994328/FlutterUnit', ), const SizedBox(height: 10,), InfoPanel( title: 'Flutter Unit 1.0', info: 'Flutter Unit 1.0 核心计划是收录widget,即widget集录。' '目前收录组件 283 个,均可在 app 中进行查看。' '项目中提供widget图鉴文件可供下载参考。功能主要如下:\n' '○ 280+的 Flutter 组件收录和详情介绍。\n' '○ 对一些重要的组件提供操作体验。\n' '○ link to功能,查看组件时可以切换到相关组件。\n' '○ 组件收藏和取消收藏功能。\n' '○ 主题、字体设置,代码风格等全局状态管理。\n' '○ 搜索功能和组件星级分类。', ), const SizedBox(height: 10,), InfoPanel( title: 'Flutter Unit 2.0 ', info: '○ 317 个 Flutter 组件收录和详情介绍。\n' '○ 绘制集录用于收录绘制相关的优秀示例。\n' '○ 要点集录用于收录 Flutter 相关的小知识。\n' '○ 时光轴,查看 FlutterUnit 重要事件。\n' '○ 实现应用内更新功能,方便使用者及时更新到最新版体验。' ), const SizedBox(height: 10,), InfoPanel( title: 'Flutter Unit 3.0 ', info: '○ 335 个 Flutter 组件收录和详情介绍。\n' '○ 知识宝库收录 Flutter 精品文章。\n' '○ 算法演绎尝试基于 Flutter 可视化展示算法流程。\n' '○ 工具宝箱,通过 Flutter 界面交互实现一些全平台辅助工具。\n' '○ 功能全面升级,多语言、暗色模式、Navigator2.0 支持。' ), const SizedBox(height: 20,) ]; return SliverList( delegate: SliverChildBuilderDelegate( (_, index) => Padding( padding: const EdgeInsets.symmetric(horizontal: 48.0), child: items[index], ), childCount: items.length), ); } } ================================================ FILE: modules/basic_system/app/lib/view/account/desk/sliver_list_panel.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:toly_ui/toly_ui.dart'; import '../../about/about_app_page.dart'; class SliverListPanel extends StatelessWidget { const SliverListPanel({super.key}); //coffee1.webp @override Widget build(BuildContext context) { List items = [ InfoPanel( title: '开源不易,请我喝咖啡 ~', info: 'Flutter Unit 是一个非盈利性的开源项目,' '旨在提供全面的 Flutter 学习指南及编程者的交流技术的接口。' '由【张风捷特烈】提供技术支持和全权维护。唯一开源网站网址:\n ' 'https://github.com/toly1994328/FlutterUnit', ), Divider( height: 20, ), InfoPanel( title: 'Flutter Unit 1.0', info: 'Flutter Unit 1.0 核心计划是收录widget,即widget集录。' '目前收录组件 283 个,均可在 app 中进行查看。' '项目中提供widget图鉴文件可供下载参考。功能主要如下:\n' '○ 280+的 Flutter 组件收录和详情介绍。\n' '○ 对一些重要的组件提供操作体验。\n' '○ link to功能,查看组件时可以切换到相关组件。\n' '○ 组件收藏和取消收藏功能。\n' '○ 主题、字体设置,代码风格等全局状态管理。\n' '○ 搜索功能和组件星级分类。', ), Divider( height: 20, ), InfoPanel( title: 'Flutter Unit 2.0 ', info: '○ 317 个 Flutter 组件收录和详情介绍。\n' '○ 绘制集录用于收录绘制相关的优秀示例。\n' '○ 要点集录用于收录 Flutter 相关的小知识。\n' '○ 时光轴,查看 FlutterUnit 重要事件。\n' '○ 实现应用内更新功能,方便使用者及时更新到最新版体验。') ]; return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 28.0), child: Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Circle(color: Theme.of(context).primaryColor), Padding( padding: const EdgeInsets.only(left: 15, top: 15, bottom: 15), child: Text( '开源不易,请我喝咖啡 ~', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold), ), ), ], ), Row( children: [ Expanded( child: Image.asset( 'assets/images/coffee_zfb.webp', ), ), const SizedBox(width: 8,), Expanded( child: Image.asset( 'assets/images/coffee_wx.webp', ), ), const SizedBox(width: 8,), Expanded( child: Image.asset( 'assets/images/coffee_wx_ac.webp', ), ), ], ) ], ), ), ); } } ================================================ FILE: modules/basic_system/app/lib/view/account/desk/sliver_share_panel.dart ================================================ import 'package:flutter/material.dart'; class SliverSharePanel extends StatelessWidget { const SliverSharePanel({super.key}); @override Widget build(BuildContext context) { List items = [ const SizedBox(height: 12), const Text( '邮箱: 1981462002@qq.com\n' '微信群: 编程技术交流圣地 -【Flutter群】\n' '公众号: 编程之王\n' '愿青梅煮酒,与君天涯共话。', style: TextStyle(color: Colors.grey)), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Column( children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Image.asset( 'assets/images/wechat.webp', width: 200, height: 200, ), ), const Center( child: Text( '我的微信', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), ], ), const SizedBox(width: 20,), Column( children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Image.asset( 'assets/images/wxgzh.webp', width: 200, height: 200, ), ), const Center( child: Text( '我的公众号', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), ], ), ], ), ]; return SliverToBoxAdapter( child: Column( // crossAxisAlignment: CrossAxisAlignment.start, children: items, ), ); } } ================================================ FILE: modules/basic_system/app/lib/view/account/desk/user_header.dart ================================================ import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; class UserHeader extends StatelessWidget { const UserHeader({super.key}); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; String image = isDark?'anim_draw.webp':'base_draw.webp'; return Stack( // clipBehavior: Clip.none, children: [ Column( children: [ Image.asset( 'assets/images/$image', height: 150, fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width, ), Container( alignment: Alignment.topLeft, padding: EdgeInsets.only(left: 32 + 100 + 16, top: 12), color: isDark?Color(0xff2C3036):Colors.white, height: 86, child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '张风捷特烈', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold), ), Text( '海的彼岸有我未曾见证的风采', style: TextStyle(fontSize: 12, color: Colors.grey), ), Text( '公众号@编程之王', style: TextStyle(fontSize: 12, color: Colors.grey), ), ], ) ], ), ) ], ), Positioned( bottom: 16, left: 32, child: CircleImage( size: 100, shadowColor: Theme.of(context) .primaryColor .withAlpha(33), // image: NetworkImage(state.user.userAvatar), image: const AssetImage("assets/images/icon_head.webp"), ), ), ], ); } } ================================================ FILE: modules/basic_system/app/lib/view/data_manage/data_manage_page.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:app/app.dart'; import 'package:l10n/l10n.dart'; import 'package:storage/storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:authentication/views/authentic_widget.dart'; import 'package:utils/utils.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:path/path.dart' as path; import 'package:sqflite/sqflite.dart'; import 'package:widget_module/views/mobile/category_page/sync/category_api.dart'; import 'package:widget_module/widget_module.dart'; /// create by 张风捷特烈 on 2021/2/26 /// contact me by email 1981462002@qq.com /// 说明: /// class DataManagePage extends StatelessWidget { const DataManagePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(context.l10n.dataManagement), ), body: Builder( builder: (ctx) => ListView( children: [ const SizedBox( height: 8, ), AuthenticWidget.just( ListTile( trailing: Icon( TolyIcon.upload, color: Theme.of(context).primaryColor, ), title: const Text('备份收藏集数据'), onTap: () => _doUploadCategoryData(ctx), ), ), AuthenticWidget.just(const Divider()), AuthenticWidget.just(ListTile( trailing: Icon( TolyIcon.download, color: Theme.of(context).primaryColor, ), title: const Text('同步收藏集数据'), onTap: () => _doSync(ctx), )), AuthenticWidget.just(const Divider()), ListTile( trailing: Icon( Icons.refresh, color: Theme.of(context).primaryColor, ), title: Text(context.l10n.favoritesCollectionDataReset), // trailing: _nextIcon(context), onTap: () => _recallDatabase(ctx), ), const Divider(), ], ), ), ); } _recallDatabase(BuildContext context) async { String databasesPath = await getDatabasesPath(); String dbPath = path.join(databasesPath, "flutter.db"); ByteData data = await rootBundle.load(path.join("assets", "flutter.db")); List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); await File(dbPath).writeAsBytes(bytes, flush: true); print("==== debug ===== assets ======拷贝完成===="); BlocProvider.of(context).add(const EventLoadCategory()); context.read().loadLikeData(); Toast.toast(context, '重置成功!'); } void _doUploadCategoryData(BuildContext context) async { // CategoryRepository rep = BlocProvider.of(context).repository; // List loadCategories = await rep.loadCategoryData(); // // List likeData = await AppStorage().flutter().likeWidgetIds(); // // String json = jsonEncode(loadCategories); // String likeJson = jsonEncode(likeData); // // TaskResult result = // await CategoryApi.uploadCategoryData(data: json, likeData: likeJson); // // if (result.success) { // Toast.toast(context, '数据集备份成功!'); // } else { // Toast.toast(context, '数据集备份失败!'); // } } void _doSync(BuildContext context) async { TaskResult result = await CategoryApi.getCategoryData(); if (result.success) { // 说明请求成功 if (result.data != null) { //说明有后台备份数据,进行同步操作 CategoryRepository repository = BlocProvider.of(context).repository; await repository.syncCategoryByData( result.data!.data, result.data!.likeData); BlocProvider.of(context).add(const EventLoadCategory()); context.read().loadLikeData(); } else { // 说明还没有后台数据, // 这里防止有傻孩子没点备份,就点同步,哥哥好心,给备份一下。 // CategoryRepository rep = // BlocProvider.of(context).repository; // List loadCategories = await rep.loadCategoryData(); // List likeData = await AppStorage().flutter().likeWidgetIds(); // // String json = jsonEncode(loadCategories); // String likeJson = jsonEncode(likeData); // await CategoryApi.uploadCategoryData(data: json, likeData: likeJson); } Toast.toast(context, '数据同步份成功!'); } else { Toast.toast(context, '数据同步份失败!'); } } } // class LoadingIndicate extends StatefulWidget { // Future Function task; // @override // _LoadingIndicateState createState() => _LoadingIndicateState(); // } // // class _LoadingIndicateState extends State { // @override // Widget build(BuildContext context) { // return Container(); // } // } ================================================ FILE: modules/basic_system/app/lib/view/setting/app_style_setting.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; /// create by 张风捷特烈 on 2020-04-10 /// contact me by email 1981462002@qq.com /// 说明: void showAppStyleSelectDialog(BuildContext context) { // List data = Cons.kAppStyleStringMap.values.toList(); showCupertinoModalPopup( context: context, builder: (context) => AppThemeSettingDialog( data: [], )); } class AppThemeSettingDialog extends StatelessWidget { final List data; const AppThemeSettingDialog({Key? key,required this.data}) : super(key: key); @override Widget build(BuildContext context) { return Material( child: SizedBox( height: 350, width: MediaQuery.of(context).size.width, child: Column( children: [ const Padding( padding: EdgeInsets.all(16.0), child: Text( '选择应用风格样式', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), ), const Divider(height: 1,), Expanded( child: ListView.builder( padding: EdgeInsets.zero, itemBuilder: _buildItem, itemCount: data.length, ), ) ], ), // color: Colors.orange, ), ); } Widget? _buildItem(BuildContext context, int index) { // AppStyle locale = Cons.kAppStyleStringMap.keys.toList()[index]; // AppStyle style = BlocProvider.of(context).state.appStyle; // bool checked = style == locale; // Color color = Theme.of(context).primaryColor; // return ListTile( // title: Text(data[index]), // onTap: () => _onSelect(context, index), // trailing: checked ? Icon(Icons.check, size: 20, color: color) : null, // ); } void _onSelect(BuildContext context, int index) { // AppStyle appStyle = Cons.kAppStyleStringMap.keys.toList()[index]; // BlocProvider.of(context).changeThemeMode(appStyle); // Navigator.of(context).pop(); } } ================================================ FILE: modules/basic_system/app/lib/view/setting/code_style_setting.dart ================================================ import 'package:app/app.dart'; import 'package:l10n/l10n.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; /// create by 张风捷特烈 on 2020-04-10 /// contact me by email 1981462002@qq.com /// 说明: class CodeStyleSettingPage extends StatelessWidget { const CodeStyleSettingPage({Key? key}) : super(key: key); final String code = """ const String _kCounty = 'China'; class Hello { final String name;//言语 final String county;//国家 final int age;//年龄 Hello({ this.name = "张风捷特烈", this.age = 26, this.county = _kCounty }); }"""; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(context.l10n.codeHighlightStyle)), body: BlocBuilder( builder: (_, state) => _buildFontCell(context, Cons.codeThemeSupport.keys.toList(), state.codeStyleIndex)), ); } Widget _buildFontCell( BuildContext context, List styles, int index) { return ListView.builder( itemCount: styles.length, itemBuilder: (_ctx, i) => FeedbackWidget( a: 0.95, duration: const Duration(milliseconds: 200), onPressed: (){ BlocProvider.of(context).switchCoderTheme(i); }, child: Stack( fit: StackFit.passthrough, children: [ Card( margin: const EdgeInsets.all(10), child: CodeWidget( code: code, style: styles[i], ), ), Positioned( right: 20, bottom: 20, child: Text(Cons.codeThemeSupport.values.toList()[i],style: TextStyle( fontSize: 14, color: styles[i].stringStyle!.color, shadows: const [Shadow( color: Colors.white, offset: Offset(.5,.5), blurRadius: 1 ),] ),), ), if(index == i) Positioned( right: 20, top: 20, child: Circle(radius: 10, color: Theme.of(context).primaryColor, child: const Icon(Icons.check,color:Colors.white,size: 15,),), ) ], ), )); } } ================================================ FILE: modules/basic_system/app/lib/view/setting/font_setting.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; /// create by 张风捷特烈 on 2020-04-10 /// contact me by email 1981462002@qq.com /// 说明: class FontSettingPage extends StatelessWidget { const FontSettingPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder( builder: (_, state) =>Scaffold( appBar: AppBar(title: Text('字体设置 - font setting',style: TextStyle(fontFamily:state.fontFamily ),)), body: _buildFontCell( context, Cons.kFontFamilySupport, state.fontFamily)), ); } Widget _buildFontCell( BuildContext context, List fontFamilySupport, String fontFamily) { return GridView.count( padding: const EdgeInsets.only(top: 20, left: 10, right: 10), crossAxisCount: kIsDesk?4:2, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1.5, children: fontFamilySupport.map((e) { return FontCell( active: fontFamily == e, fontFamily: e, onSelect: (font) { BlocProvider.of(context).switchFontFamily(font); }, ); }).toList(), ); } } class FontCell extends StatelessWidget { final bool active; final ValueChanged onSelect; final String fontFamily; const FontCell( {Key? key, required this.active, required this.onSelect, required this.fontFamily}) : super(key: key); @override Widget build(BuildContext context) { return FeedbackWidget( a: 0.95, duration: const Duration(milliseconds: 200), onPressed: () => onSelect(fontFamily), child: GridTile( header: Container( padding: const EdgeInsets.only(left: 10, right: 5), height: 30, color: active ? Colors.blue.withAlpha(88) : Colors.grey.withAlpha(88), child: Row( children: [ Text(fontFamily, style: TextStyle( color: Colors.black, fontFamily: fontFamily, )), const Spacer(), if (active) Circle(color: Theme.of(context).primaryColor) ], ), ), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8), ), gradient: LinearGradient(colors: [ Colors.blueAccent.withAlpha(22), Colors.blueAccent.withAlpha(22), Theme.of(context).primaryColor.withAlpha(88) ])), alignment: const Alignment(0, 0.4), child: Text( '张风捷特烈\n@toly1994', style: TextStyle(fontFamily: fontFamily, fontSize: 16), )), )); } } ================================================ FILE: modules/basic_system/app/lib/view/setting/item_style_setting.dart ================================================ // import 'package:app/app.dart'; // import 'package:components/components.dart'; // import 'package:flutter/material.dart'; // import 'package:flutter_bloc/flutter_bloc.dart'; // // /// create by 张风捷特烈 on 2020-04-10 // /// contact me by email 1981462002@qq.com // /// 说明: item样式切换支持 // // class ItemStyleSettingPage extends StatelessWidget { // const ItemStyleSettingPage({Key? key}) : super(key: key); // // @override // Widget build(BuildContext context) { // return Scaffold( // backgroundColor: UnitColor.scaffoldBgLight, // appBar: const UnitAppbar(title: 'item样式设置'), // body: BlocBuilder(builder: (_, state) { // return _buildCell(context, state.itemStyleIndex); // }), // ); // } // // List get items=> HomeItemSupport.itemSimples(); // // Widget _buildCell(BuildContext context, int index) { // return ListView.builder( // itemCount: items.length, // itemBuilder: (_, i) => Padding( // padding: const EdgeInsets.only(bottom: 8,left: 8,right: 8), // child: FeedbackWidget( // a: 0.95, // duration: const Duration(milliseconds: 200), // onPressed: () { // BlocProvider.of(context).changeItemStyle(i); // }, // child: Stack( // children: [ // items[i], // if (index == i) // Positioned( // left: 25, // top: 15, // child: Circle( // color: Theme.of(context).primaryColor, // radius: 10, // child: const Icon( // Icons.check, // color: Colors.white, // size: 15, // ), // ), // ) // ], // )), // )); // } // } ================================================ FILE: modules/basic_system/app/lib/view/setting/language_setting.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:l10n/l10n.dart'; class LanguageSettingPage extends StatelessWidget { const LanguageSettingPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(context.l10n.settingLanguage), ), body: const LanguageSetting(), ); } } class LanguageSetting extends StatelessWidget { const LanguageSetting({super.key}); @override Widget build(BuildContext context) { List languages = Language.values; Language activeLanguage = context.select((bloc) => bloc.state.language); Color iconColor = Theme.of(context).primaryColor; return ListView.separated( padding: const EdgeInsets.only(top: 8), separatorBuilder: (_, __) => const Divider(), itemBuilder: (_, index) { Language language = languages[index]; return ListTile( title: Text(language.label), onTap: () { context.read().switchLanguage(language); }, trailing: activeLanguage == language ? Icon(Icons.check, size: 20, color: iconColor) : null, ); }, itemCount: languages.length, ); } } class LanguageSwitchTile extends StatelessWidget { const LanguageSwitchTile({super.key}); @override Widget build(BuildContext context) { Language activeLanguage = context.select((bloc) => bloc.state.language); Color color = Theme.of(context).primaryColor; return ListTile( leading: Icon( Icons.language, color: color, ), title: Text(context.l10n.settingLanguageText, style: TextStyle(fontSize: 16)), subtitle: Text( '${activeLanguage.label}: ${activeLanguage.locale}', style: TextStyle(fontSize: 12), ), trailing: Icon(Icons.chevron_right, color: color), onTap: () { showModalBottomSheet( context: context, builder: (_) => LanguageSettingPage()); }, ); } } ================================================ FILE: modules/basic_system/app/lib/view/setting/setting_page.dart ================================================ import 'package:app/app.dart'; import 'package:fx_updater/fx_updater.dart'; import 'package:go_router/go_router.dart'; import 'package:l10n/l10n.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'language_setting.dart'; class SettingPage extends StatelessWidget { const SettingPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { const Widget divider = Divider(height: 1); return DragToMoveWrapper( child: Scaffold( appBar: AppBar(title: Text(context.l10n.appSettings)), body: ListView( children: [ Container(height: 15), ListTile( leading: Icon( Icons.style, color: Theme.of(context).primaryColor, ), title: Text(context.l10n.darkMode, style: TextStyle(fontSize: 16)), subtitle: BlocBuilder( builder: (_, state) { String info = switch (state.themeMode) { ThemeMode.system => context.l10n.followSystem, ThemeMode.light => context.l10n.lightMode, ThemeMode.dark => context.l10n.darkMode, }; return Text(info, style: const TextStyle(fontSize: 12, color: Colors.grey)); }, ), trailing: _nextIcon(context), onTap: () => context.push('/settings/dark_mode'), ), divider, ListTile( leading: Icon( Icons.palette, color: Theme.of(context).primaryColor, ), title: Text(context.l10n.themeColorSetting, style: TextStyle(fontSize: 16)), subtitle: BlocBuilder( builder: (_, state) => Text( state.themeColor.label(context), style: TextStyle(color: state.themeColor.color, fontSize: 12), ), ), trailing: _nextIcon(context), onTap: () => context.push('/settings/theme_color'), ), // divider, Container(height: 10), ListTile( leading: Icon( Icons.translate, color: Theme.of(context).primaryColor, ), title: Text(context.l10n.fontSetting, style: TextStyle(fontSize: 16)), subtitle: BlocBuilder( builder: (_, state) => Text( state.fontFamily, style: TextStyle(fontSize: 12), ), ), trailing: _nextIcon(context), onTap: () => context.push('/settings/font_setting'), ), divider, const LanguageSwitchTile(), divider, ListTile( leading: Icon( TolyIcon.icon_code, color: Theme.of(context).primaryColor, ), title: Text(context.l10n.codeHighlightStyle, style: TextStyle(fontSize: 16)), trailing: _nextIcon(context), onTap: () => context.push('/settings/code_style'), ), // divider, Container( height: 10, ), // _buildShowBg(context), divider, _buildShowOver(context), // divider, // _buildShowTool(context), divider, // Container( height: 10), VersionTiled(), ], ), ), ); } Widget _buildShowOver(BuildContext context) => BlocBuilder( builder: (_, state) => TolySwitchListTile( secondary: Icon( TolyIcon.icon_background, color: Theme.of(context).primaryColor, ), title: Text(context.l10n.displayPerformanceFloatingLayer, style: TextStyle(fontSize: 16)), value: state.showPerformanceOverlay, onChanged: (bool value) { BlocProvider.of(context).switchShowOver(value); }, )); Widget _nextIcon(BuildContext context) => Icon(Icons.chevron_right, color: Theme.of(context).primaryColor); } class VersionTiled extends StatelessWidget { const VersionTiled({super.key}); @override Widget build(BuildContext context) { Color themeColor = Theme.of(context).primaryColor; Widget title = Text(context.l10n.versionInformation, style: TextStyle(fontSize: 16)); UpdateState state = context.watch().state; if (state is ShouldUpdateState) { title = Wrap( spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [title, AppUpgradeTips(state: state)], ); } return ListTile( leading: Icon(Icons.info, color: themeColor), title: title, trailing: Icon(Icons.chevron_right, color: themeColor), onTap: () => context.push('/settings/version'), ); } } class AppUpgradeTips extends StatelessWidget { final ShouldUpdateState state; const AppUpgradeTips({super.key, required this.state}); @override Widget build(BuildContext context) { Color themeColor = Theme.of(context).primaryColor; bool downloading = state.isDownloading; String text = downloading ? state.progressDisplay : '新版本'; Color color = downloading ? themeColor : Colors.redAccent; return Container( decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(4)), padding: EdgeInsets.symmetric(horizontal: 4, vertical: 4), child: Text( text, style: TextStyle(color: Colors.white, fontSize: 10, height: 1), )); } } ================================================ FILE: modules/basic_system/app/lib/view/setting/theme_color_setting.dart ================================================ import 'package:app/app.dart'; import 'package:components/components.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:toly_ui/toly_ui.dart'; /// create by 张风捷特烈 on 2020-04-10 /// contact me by email 1981462002@qq.com /// 说明: class ThemeColorSettingPage extends StatelessWidget { const ThemeColorSettingPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: UnitColor.scaffoldBgLight, appBar: const UnitAppbar(title:'主题色设置'), body: BlocBuilder( builder: (_, state) => _buildCell( context, ThemeColor.values, state.themeColor)), ); } Widget _buildCell( BuildContext context, List themeColorSupport, ThemeColor color) { return GridView.count( padding: const EdgeInsets.only(top: 20, left: 10, right: 10), shrinkWrap: true, crossAxisCount: kIsDesk?4:2, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1.5, children: themeColorSupport .map((ThemeColor c) => FeedbackWidget( a: 0.95, duration: const Duration(milliseconds: 200), onPressed: () => BlocProvider.of(context).switchThemeColor(c), child: GridTile( header: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.only(topLeft: Radius.circular(10),topRight: Radius.circular(10)), color: color == c ? Colors.blue.withAlpha(88): Colors.grey.withAlpha(55), ), padding: const EdgeInsets.only(left: 10, right: 5), height: 30, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Spacer(), Text(colorString(c.color), style: const TextStyle( color: Colors.white, )), const Spacer(), if (color == c) const Padding( padding: EdgeInsets.only(right:8.0), child: Circle(color: Colors.white,radius: 7,), ) ], ), ), child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(10)), gradient: LinearGradient(colors: [ c.color.shade50, c.color.shade100, c.color.shade200, c.color.shade300, c.color.shade400, c.color.shade500, c.color.shade600, c.color.shade700, c.color.shade800, c.color.shade900, ])), alignment: const Alignment(0,0.35), child: Text( c.label(context), style: const TextStyle(fontSize: 18,color: Colors.white,fontWeight: FontWeight.bold), )), ))) .toList(), ); } String colorString(Color color) => "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; } ================================================ FILE: modules/basic_system/app/lib/view/setting/theme_model_setting.dart ================================================ import 'package:app/app.dart'; import 'package:l10n/l10n.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class ThemeModelSetting extends StatelessWidget { const ThemeModelSetting({Key? key}) : super(key: key); @override Widget build(BuildContext context) { ThemeMode mode = context .select((bloc) => bloc.state.themeMode); Color iconColor = Theme.of(context).primaryColor; String dark = context.l10n.darkMode; String light = context.l10n.lightMode; String followSystem = context.l10n.followSystem; String manualSetting = context.l10n.manualSetting; String info = context.l10n.afterOpeningWillFollowTheSystemToOpenOrCloseDarkMode; return Scaffold( appBar: AppBar(title: Text(dark)), body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 15, ), TolySwitchListTile( title: Text(followSystem, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), subtitle: Text( info, style: const TextStyle(fontSize: 12, color: Colors.grey), ), value: mode == ThemeMode.system, onChanged: (bool value) { ThemeMode newModel; if (value) { newModel = ThemeMode.system; } else { newModel = ThemeMode.light; } context.read().changeThemeMode(newModel); }, ), Padding( padding: const EdgeInsets.only(left: 10, top: 16, bottom: 6), child: Text(manualSetting), ), ListTile( title: Text(light), onTap: () { context.read().changeThemeMode(ThemeMode.light); }, trailing: mode == ThemeMode.light ? Icon(Icons.check, size: 20, color: iconColor) : null, ), const Divider(), ListTile( title: Text(dark), onTap: () { context.read().changeThemeMode(ThemeMode.dark); }, trailing: mode == ThemeMode.dark ? Icon(Icons.check, size: 20, color: iconColor) : null, ) ], ), ); ; } } ================================================ FILE: modules/basic_system/app/lib/view/unit_todo/attr_unit_page.dart ================================================ import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; class AttrUnitPage extends StatelessWidget { final String info = '【Flutter属性集录】是Unit项目计划的第二阶段的功能之一。' '会对所有Widget的所有属性进行收录整理到数据库,进行数据分析和组件关联。' '并且对一些重要属性,进行全面讲解。'; const AttrUnitPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('属性集录'), ), body: Stack( alignment: Alignment.center, children: [ Positioned( top: 50, child: Column( children: const [ CircleImage( image: AssetImage('assets/images/icon_head.webp'), size: 80, ), SizedBox(height: 10,), Text( 'Flutter Unit 2.0 计划', style: TextStyle( color: Colors.green, fontSize: 18, fontWeight: FontWeight.bold, ), ) ], ), ), Container( alignment: Alignment.center, padding: const EdgeInsets.all(20), child: ShaderMask( shaderCallback: (rect) => _buildShader(rect, Theme.of(context).primaryColor), child: TextTyper( text:info, textStyle: const TextStyle( shadows: [ Shadow( color: Colors.black, offset: Offset(1, 1), blurRadius: 1) ], color: Colors.white, // color: Theme.of(context).primaryColor, fontSize: 20, fontWeight: FontWeight.bold, ), ), ), ), buildPower() ], ), ); } Shader _buildShader(Rect bounds, Color color) => RadialGradient( center: Alignment.topLeft, radius: 1.0, tileMode: TileMode.mirror, colors: [color.withAlpha(88), color.withAlpha(136), color]) .createShader(bounds); Widget buildPower() { return const Positioned( bottom: 30, right: 30, child: Text("Power By 张风捷特烈", style: TextStyle( color: Colors.grey, shadows: [ Shadow( color: Colors.black, blurRadius: 1, offset: Offset(0.3, 0.3)) ], fontSize: 16)), ); } } ================================================ FILE: modules/basic_system/app/lib/view/unit_todo/layout_unit_page.dart ================================================ import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; class LayoutUnitPage extends StatelessWidget { const LayoutUnitPage({Key? key}) : super(key: key); final String info = '【Flutter布局集录】是Unit项目计划的第二阶段的功能之一。' '将收录大量的布局样板,一者,方便直接使用;二者,方便布局的学习。' '本集录将支持布局征集,愿开发者共同集录。'; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('布局集录'), ), body: Stack( alignment: Alignment.center, children: [ Positioned( top: 50, child: Column( children: const [ CircleImage( image: AssetImage('assets/images/icon_head.webp'), size: 80, ), SizedBox(height: 10,), Text( 'Flutter Unit 2.0 计划', style: TextStyle( color: Colors.green, fontSize: 18, fontWeight: FontWeight.bold, ), ) ], ), ), Container( alignment: Alignment.center, padding: const EdgeInsets.all(20), child: ShaderMask( shaderCallback: (rect) => _buildShader(rect, Theme.of(context).primaryColor), child: TextTyper( text:info, textStyle: const TextStyle( shadows: [ Shadow( color: Colors.black, offset: Offset(1, 1), blurRadius: 1) ], color: Colors.white, // color: Theme.of(context).primaryColor, fontSize: 20, fontWeight: FontWeight.bold, ), ), ), ), buildPlan(), buildPower() ], ), ); } Shader _buildShader(Rect bounds, Color color) => RadialGradient( center: Alignment.topLeft, radius: 1.0, tileMode: TileMode.mirror, colors: [color.withAlpha(88), color.withAlpha(136), color]) .createShader(bounds); Widget buildPlan() { return const Positioned( bottom: 80, child: Text("Flutter Unit 布局征集方案(待完成)", style: TextStyle( color: Colors.blue, decoration: TextDecoration.underline, shadows: [ Shadow( color: Colors.black, blurRadius: .5, offset: Offset(0.3, 0.3)) ], fontSize: 16)), ); } Widget buildPower() { return const Positioned( bottom: 30, right: 30, child: Text("Power By 张风捷特烈", style: TextStyle( color: Colors.grey, shadows: [ Shadow( color: Colors.black, blurRadius: 1, offset: Offset(0.3, 0.3)) ], fontSize: 16)), ); } } ================================================ FILE: modules/basic_system/app/lib/view/unit_todo/paint_unit_page.dart ================================================ import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; class PaintUnitPage extends StatelessWidget { final String info = '【Flutter绘制集录】是Unit项目计划的第二阶段的功能之一。' '将收录大量绘制作品,展现Flutter强大的绘制表现力,' '以供学习绘制技能。本集录将支持绘制征集,愿开发者共同集录。'; const PaintUnitPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('绘制集录'), ), body: Stack( alignment: Alignment.center, children: [ Positioned( top: 50, child: Column( children: const [ CircleImage( image: AssetImage('assets/images/icon_head.webp'), size: 80, ), SizedBox(height: 10,), Text( 'Flutter Unit 2.0 计划', style: TextStyle( color: Colors.green, fontSize: 18, fontWeight: FontWeight.bold, ), ) ], ), ), Container( alignment: Alignment.center, padding: const EdgeInsets.all(20), child: ShaderMask( shaderCallback: (rect) => _buildShader(rect, Theme.of(context).primaryColor), child: TextTyper( text:info, textStyle: const TextStyle( shadows: [ Shadow( color: Colors.black, offset: Offset(1, 1), blurRadius: 1) ], color: Colors.white, // color: Theme.of(context).primaryColor, fontSize: 20, fontWeight: FontWeight.bold, ), ), ), ), buildPlan(), buildPower() ], ), ); } Shader _buildShader(Rect bounds, Color color) => RadialGradient( center: Alignment.topLeft, radius: 1.0, tileMode: TileMode.mirror, colors: [color.withAlpha(88), color.withAlpha(136), color]) .createShader(bounds); Widget buildPlan() { return const Positioned( bottom: 80, child: Text("Flutter Unit 绘制征集方案(待完成)", style: TextStyle( color: Colors.blue, decoration: TextDecoration.underline, shadows: [ Shadow( color: Colors.black, blurRadius: .5, offset: Offset(0.3, 0.3)) ], fontSize: 16)), ); } Widget buildPower() { return const Positioned( bottom: 30, right: 30, child: Text("Power By 张风捷特烈", style: TextStyle( color: Colors.grey, shadows: [ Shadow( color: Colors.black, blurRadius: 1, offset: Offset(0.3, 0.3)) ], fontSize: 16)), ); } } ================================================ FILE: modules/basic_system/app/lib/view/unit_todo/point_unit_page.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; class BugUnitPage extends StatelessWidget { const BugUnitPage({Key? key}) : super(key: key); final String info = '【Flutter要点集录】是Unit项目计划的第二阶段的功能之一。' '将收录Flutter的常见异常及解决方案,也可以是Flutter中的特点或注意点,' '以供学习参考。本集录将支持异常/特色征集,愿开发者共同集录。'; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('要点集录'), ), body: Stack( alignment: Alignment.center, children: [ Positioned( top: 50, child: Column( children: [ FeedbackWidget( onPressed: (){ // Navigator.of(context).pushNamed(UnitRouter.issues_point); }, child: const CircleImage( image: AssetImage('assets/images/icon_head.webp'), size: 80, ), ), const SizedBox(height: 10,), const Text( 'Flutter Unit 2.0 计划', style: TextStyle( color: Colors.green, fontSize: 18, fontWeight: FontWeight.bold, ), ) ], ), ), Container( alignment: Alignment.center, padding: const EdgeInsets.all(20), child: ShaderMask( shaderCallback: (rect) => _buildShader(rect, Theme.of(context).primaryColor), child: TextTyper( text:info, textStyle: const TextStyle( shadows: [ Shadow( color: Colors.black, offset: Offset(1, 1), blurRadius: 1) ], color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), ), ), buildPlan(), buildPower() ], ), ); } Shader _buildShader(Rect bounds, Color color) => RadialGradient( center: Alignment.topLeft, radius: 1.0, tileMode: TileMode.mirror, colors: [color.withAlpha(88), color.withAlpha(136), color]) .createShader(bounds); Widget buildPlan() { return const Positioned( bottom: 80, child: Text("Flutter Unit 异常/特色 征集方案(待完成)", style: TextStyle( color: Colors.blue, decoration: TextDecoration.underline, shadows: [ Shadow( color: Colors.black, blurRadius: .5, offset: Offset(0.3, 0.3)) ], fontSize: 16)), ); } Widget buildPower() { return const Positioned( bottom: 30, right: 30, child: Text("Power By 张风捷特烈", style: TextStyle( color: Colors.grey, shadows: [ Shadow( color: Colors.black, blurRadius: 1, offset: Offset(0.3, 0.3)) ], fontSize: 16)), ); } } ================================================ FILE: modules/basic_system/app/lib/view/view.dart ================================================ export 'about/about_app_page.dart'; export 'about/about_me_page.dart'; export 'about/version_info.dart'; export 'account/desk/desk_account_page.dart'; export 'setting/setting_page.dart'; export 'setting/app_style_setting.dart'; export 'setting/font_setting.dart'; export 'setting/item_style_setting.dart'; export 'setting/language_setting.dart'; export 'setting/theme_color_setting.dart'; export 'setting/code_style_setting.dart'; export 'setting/theme_model_setting.dart'; export 'data_manage/data_manage_page.dart'; ================================================ FILE: modules/basic_system/app/lib/view/wrapper/overlay_tool_wrapper.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/10/21 /// contact me by email 1981462002@qq.com /// 说明: class OverlayToolWrapper extends StatefulWidget { final Widget child; const OverlayToolWrapper({Key? key,required this.child}) : super(key: key); @override OverlayToolWrapperState createState() => OverlayToolWrapperState(); static OverlayToolWrapperState of(BuildContext context, {bool nullOk = false}) { final OverlayToolWrapperState? result = context.findAncestorStateOfType(); if (result != null) return result; throw FlutterError.fromParts([ ErrorSummary( 'OverlayToolWrapper.of() called with a context that does not contain a OverlayToolWrapper.'), ]); } } class OverlayToolWrapperState extends State with SingleTickerProviderStateMixin { bool show = false; late Offset offset; late AnimationController _ctrl; final double width = 200; final double height = 30; final double outWidth = 35; final double boxHeight = 110; final double radius = 60; OverlayEntry? entry; double showWidth = 0; bool out = false; @override void initState() { super.initState(); _ctrl = AnimationController( duration: const Duration(milliseconds: 400), vsync: this, )..addListener(_listenAnimate); WidgetsBinding.instance.addPostFrameCallback((callback) { var px = MediaQuery.of(context).size.width - 100; var py = MediaQuery.of(context).size.height*0.05; offset = Offset(px, py); entry = OverlayEntry( builder: (context) => Stack( children: [ Positioned( left: offset.dx, top: offset.dy, child: _buildFloating(), ), ], )); }); } final double circleRadius = 80; final double menuSize = 36; Widget _buildFloating() { Color wrapColor = Colors.blue.withOpacity(0.6); bool left = offset.dx < 100; return Container( width: circleRadius * 2, height: circleRadius * 2, alignment: Alignment.center, // color: Colors.orangeAccent, child: IconTheme( data: const IconThemeData(color: Colors.white, size: 18), child: BurstMenu( startAngle: !left ? 90.0 + 15 : -90 - 15.0 + 180, swapAngle: !left ? 180.0 - 15 * 2 : -(180.0 - 15 * 2), center: _buildCenter(), burstMenuItemClick: _burstMenuItemClick, menus: _buildMenuItems(wrapColor)), ), ); } Widget _buildCenter() => GestureDetector( onPanEnd: _onPanEnd, onPanUpdate: _updatePosition, child: Opacity( opacity: 0.9, child: Container( width: menuSize, height: menuSize, padding: const EdgeInsets.all(2), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(menuSize / 2)), child: Container( decoration: BoxDecoration( color: Colors.blue, image: const DecorationImage( image: AssetImage('assets/images/icon_head.webp')), borderRadius: BorderRadius.circular(menuSize / 2)), ), ), ), ); // 构建 菜单 item List _buildMenuItems(Color wrapColor) => [ Circled(color: wrapColor, child: const Icon(Icons.close)), Circled(color: wrapColor, radius: 15, child: const Icon(TolyIcon.icon_bug)), Circled(color: wrapColor, radius: 15, child: const Icon(Icons.palette)), Circled(color: wrapColor, child: const Icon(Icons.widgets)), Circled(color: wrapColor, child: const Icon(Icons.settings)), ]; bool _burstMenuItemClick(int index) { print(index); switch (index) { case 0: _doClose(); return true; case 1: _toPoint(); break; case 2: _toGalley(); break; case 3: _toWidget(); break; case 4: _toSetting(); break; } return true; } // 处理 菜单 item 点击事件 void _toSetting() { // Navigator.of(context).pushNamed(UnitRouter.setting); } void _toWidget() {} void _toGalley() { } void _toPoint() { // BlocProvider.of(context).add(EventLoadPoint()); // Navigator.of(context).pushNamed(UnitRouter.point); } void _doClose() { if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); } } double endX=0; void _onPanEnd(details) { endX = offset.dx; _ctrl.reset(); _ctrl.forward(); } void _listenAnimate() { // var px = MediaQuery.of(context).size.width - (outWidth); // offset = Offset(px - (_ctrl.value), offset.dy); double px; // print(offset.dx); if (offset.dx > MediaQuery.of(context).size.width / 2 - circleRadius) { double begin = endX; double end = MediaQuery.of(context).size.width - menuSize / 2 - circleRadius; double t = _ctrl.value; px = begin + (end - begin) * t; // x = menuSize / 2 - circleRadius; } else { double begin = endX; double end = menuSize / 2 - circleRadius; double t = _ctrl.value; px = begin + (end - begin) * t; // x = menuSize / 2 - circleRadius; } offset = Offset(px, offset.dy); entry?.markNeedsBuild(); } void _updatePosition(DragUpdateDetails details) { double y = details.globalPosition.dy - circleRadius; double x = details.globalPosition.dx - circleRadius; if (x < menuSize / 2 - circleRadius) { x = menuSize / 2 - circleRadius; } if (y < menuSize / 2 - circleRadius) { y = menuSize / 2 - circleRadius; } if (x > MediaQuery.of(context).size.width - menuSize / 2 - circleRadius) { x = MediaQuery.of(context).size.width - menuSize / 2 - circleRadius; } if (y > MediaQuery.of(context).size.height - menuSize / 2 - circleRadius) { y = MediaQuery.of(context).size.height - menuSize / 2 - circleRadius; } offset = Offset(x, y); entry?.markNeedsBuild(); } void showFloating() { if (!show&&entry!=null) { Overlay.of(context)?.insert(entry!); show = true; } } void hideFloating() { if (show) { entry?.remove(); show = false; } } @override Widget build(BuildContext context) { return widget.child; } } ================================================ FILE: modules/basic_system/app/pubspec.yaml ================================================ name: app description: A new Flutter package project. version: 0.0.1 homepage: publish_to: none environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter fx_updater: path: ../../basic_system/fx_updater flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/basic_system/app/test/app_config_test.dart ================================================ // import 'package:flutter_test/flutter_test.dart'; // // import 'package:app_config/app_config.dart'; // // void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); // } ================================================ FILE: modules/basic_system/authentication/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .packages build/ ================================================ FILE: modules/basic_system/authentication/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 channel: stable project_type: package ================================================ FILE: modules/basic_system/authentication/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/basic_system/authentication/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/basic_system/authentication/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/basic_system/authentication/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/basic_system/authentication/lib/authentication.dart ================================================ library authentication; export 'repository/auth_repository.dart'; export 'repository/impl/http_auth_repository.dart'; export 'blocs/authentic/state.dart'; export 'blocs/authentic/bloc.dart'; export 'blocs/authentic/event.dart'; export 'blocs/register/event.dart'; export 'blocs/register/state.dart'; export 'blocs/register/bloc.dart'; export 'blocs/user/bloc.dart'; export 'blocs/user/state.dart'; export 'views/mobile/user/page_item.dart'; export 'views/mobile/user/unit_drawer_header.dart'; export 'views/mobile/user/user_page.dart'; export 'views/mobile/user/support_me.dart'; export 'views/mobile/login/login_page.dart'; export 'views/mobile/register/register_page.dart'; ================================================ FILE: modules/basic_system/authentication/lib/blocs/authentic/bloc.dart ================================================ import 'dart:async'; import 'dart:convert'; import 'package:app/app.dart'; import 'package:authentication/authentication.dart'; import 'package:authentication/models/user.dart'; import 'package:bloc/bloc.dart'; import 'package:jwt_decoder/jwt_decoder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:utils/utils.dart'; import 'event.dart'; import 'state.dart'; class AuthBloc extends Bloc { final AuthRepository repository; AuthBloc({required this.repository}) : super(const AuthInitial()){ on(_onAppStarted); on(_onAuthByPassword); on(_onAuthByRegister); on(_onLoggedOut); } void _onAppStarted(AuthEvent event, Emitter emit) async{ SharedPreferences sp = await SharedPreferences.getInstance(); if (event is AppStarted) { String? token = sp.getString(SpKey.tokenKey); String? userJson = sp.getString(SpKey.userKey); if (token != null && userJson != null) { bool disable = JwtDecoder.isExpired(token); if (!disable) { HttpUtil.instance.setToken(token); emit(AuthSuccess(User.fromJson(json.decode(userJson)))); }else{ // 说明 token 过期 await _removeToken(sp); await _removeUser(sp); } } } if (event is Logout) { } } // 持久化 token Future _persistToken(String token,SharedPreferences sp) async { await sp.setString(SpKey.tokenKey, token); } // 持久化 token Future _removeToken(SharedPreferences sp) async { await sp.remove(SpKey.tokenKey); } // 持久化 token Future _removeUser(SharedPreferences sp) async { await sp.remove(SpKey.userKey); } // 持久化 user Future _persistUser(User user,SharedPreferences sp) async { await sp.setString(SpKey.userKey, json.encode(user)); } FutureOr _onAuthByPassword(AuthByPassword event, Emitter emit) async{ emit (AuthLoading()); await Future.delayed(const Duration(milliseconds: 500)); TaskResult result = await repository.login(username: event.username, password: event.password); if (result.success&& result.data!=null) { // 登录成功 SharedPreferences sp = await SharedPreferences.getInstance(); HttpUtil.instance.setToken(result.msg); await _persistToken(result.msg,sp); await _persistUser(result.data!,sp); emit (AuthSuccess(result.data!)); } else { emit (const AuthFailure('用户名和密码不匹配')); } } FutureOr _onAuthByRegister(AuthByRegister event, Emitter emit) async{ emit(AuthLoading()); TaskResult result = await repository.register(email: event.email, code: event.code); // if(result.data == null){ // emit(const RegisterError('注册失败')); // }else{ // if (result.data!=null&&result.data!) { // // 注册成功 // emit( RegisterSuccess(event.email)); // }else{ // emit( RegisterError(result.msg)); // } // } } FutureOr _onLoggedOut(Logout event, Emitter emit) async{ SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.remove(SpKey.tokenKey); emit(const AuthInitial()); } } ================================================ FILE: modules/basic_system/authentication/lib/blocs/authentic/event.dart ================================================ import 'package:equatable/equatable.dart'; ///********************************验证行为******************************** abstract class AuthEvent extends Equatable { const AuthEvent(); @override List get props => []; } class AppStarted extends AuthEvent { const AppStarted(); } // 发送 邮箱验证 class AuthByPassword extends AuthEvent { final String username; final String password; const AuthByPassword({required this.username,required this.password}); @override List get props => [username,password]; } // 用户注册也是认证的一部分 class AuthByRegister extends AuthEvent{ final String email; final String code; const AuthByRegister(this.email, this.code); } class Logout extends AuthEvent { final bool clearUser; final bool tokenDisable; const Logout({this.clearUser=true,this.tokenDisable=false}); } class TokenDisabled extends AuthEvent { const TokenDisabled(); } ================================================ FILE: modules/basic_system/authentication/lib/blocs/authentic/state.dart ================================================ import 'package:authentication/models/user.dart'; import 'package:equatable/equatable.dart'; enum AuthType{ login, register, visitor } ///********************************校验状态******************************** abstract class AuthState extends Equatable { const AuthState(); @override List get props => []; } class AuthInitial extends AuthState { const AuthInitial(); } class AuthFailure extends AuthState { final String error; const AuthFailure(this.error); @override List get props => [error]; @override String toString() { return 'AuthFailure{message: $error}'; } } class LogOuted extends AuthState {} class AuthSuccess extends AuthState { final User user; const AuthSuccess(this.user); @override String toString() { return 'AuthSuccess{loginResult: $user}'; } } class AuthLoading extends AuthState { } ================================================ FILE: modules/basic_system/authentication/lib/blocs/register/bloc.dart ================================================ // import 'package:authentication/authentication.dart'; // import 'package:flutter_bloc/flutter_bloc.dart'; // import 'package:utils/algorithm.dart'; // // import 'event.dart'; // import 'state.dart'; // // /// create by 张风捷特烈 on 2021/1/17 // /// contact me by email 1981462002@qq.com // /// 说明: // // class RegisterBloc extends Bloc { // final AuthRepository repository; // // RegisterBloc({required this.repository}) : super(RegisterNone()){ // on(_onRegisterEvent); // } // // void _onRegisterEvent(RegisterEvent event,Emitter emit) async { // if (event is DoRegister) { // emit(RegisterLoading()); // TaskResult result = // await repository.register(email: event.email, code: event.code); // // if(result.data == null){ // emit(const RegisterError('注册失败')); // }else{ // if (result.data!=null&&result.data!) { // // 注册成功 // emit( RegisterSuccess(event.email)); // }else{ // emit( RegisterError(result.msg)); // } // } // } // } // } ================================================ FILE: modules/basic_system/authentication/lib/blocs/register/event.dart ================================================ // import 'package:equatable/equatable.dart'; // // /// create by 张风捷特烈 on 2020-03-03 // /// contact me by email 1981462002@qq.com // /// 说明: // // abstract class RegisterEvent extends Equatable { // const RegisterEvent(); // // @override // List get props => []; // } // // // 发送 邮箱验证 // class DoRegister extends RegisterEvent { // final String email; // final String code; // // const DoRegister(this.email, this.code); // } ================================================ FILE: modules/basic_system/authentication/lib/blocs/register/state.dart ================================================ // import 'package:equatable/equatable.dart'; // // /// create by 张风捷特烈 on 2020-03-03 // /// contact me by email 1981462002@qq.com // /// 说明: 主页 Widget 列表 状态类 // // abstract class RegisterState extends Equatable { // const RegisterState(); // // @override // List get props => []; // } // // class RegisterLoading extends RegisterState { // @override // List get props => []; // } // // class RegisterNone extends RegisterState { // @override // List get props => []; // } // // // class RegisterError extends RegisterState { // final String message; // // const RegisterError(this.message); // // @override // List get props => [message]; // // @override // String toString() { // return 'RegisterError{message: $message}'; // } // } // // class RegisterSuccess extends RegisterState { // final String username; // // const RegisterSuccess(this.username); // // @override // List get props => [username]; // } ================================================ FILE: modules/basic_system/authentication/lib/blocs/user/bloc.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import 'state.dart'; class UserBloc extends Cubit { UserBloc() : super(UserPerformance.fromJson({})); } ================================================ FILE: modules/basic_system/authentication/lib/blocs/user/state.dart ================================================ class UserPerformance { final String username; final int userId; final String phone; final String email; final int emailVerified; final String weiChat; final String avatar; String get showEmail { String prefix = email.split('@')[0]; String tail = email.split('@')[1]; String result = ''; if (prefix.length > 8) { result = "${prefix.substring(0, 3)}***${prefix.substring(prefix.length - 2)}"; } else { result = prefix; } return '$result@$tail'; } String get showPhone => "${phone.substring(0, 3)}***${phone.substring(phone.length - 4)}"; UserPerformance({ required this.username, required this.userId, required this.email, required this.emailVerified, required this.weiChat, required this.phone, required this.avatar, }); UserPerformance copyWith({ String? username, int? userId, String? phone, String? avatar, String? email, int? emailVerified, String? weiChat, bool? hasPwd, int? pollenCoin, int? honeyCoin, }) => UserPerformance( weiChat: weiChat ?? this.weiChat, username: username ?? this.username, userId: userId ?? this.userId, phone: phone ?? this.phone, avatar: avatar ?? this.avatar, email: email ?? this.email, emailVerified: emailVerified ?? this.emailVerified, ); static UserPerformance fromJson(Map map) => UserPerformance( username: map["username"] ?? '', userId: map["userId"] ?? 0, phone: map["phone"] ?? '', avatar: map["avatar"] ?? '', email: map["email"] ?? '', weiChat: map["weiChat"] ?? '', emailVerified: map["emailVerified"] ?? 0, ); Map toJson() => { "username": username, "userId": userId, "phone": phone, "avatar": avatar, "weiChat": weiChat, "email": email, "emailVerified": emailVerified, }; } ================================================ FILE: modules/basic_system/authentication/lib/models/user.dart ================================================ import 'package:equatable/equatable.dart'; /// create by 张风捷特烈 on 2021/1/17 /// contact me by email 1981462002@qq.com /// 说明: // "userId": 1302422300380954625, // "username": "toly", // "email": "1981462001@qq.com", // "activeCode": 1, // "roles": "admin", // "createAt": "2020-09-06T01:44:09.000+00:00", // "updateAt": "2020-12-12T06:35:22.000+00:00" class User extends Equatable { final String username; final String userAvatar; final String email; final String roles; final int userId; const User({ required this.username, required this.email, required this.roles, required this.userId, required this.userAvatar, }); factory User.fromJson(Map map) { return User( username: map['username'], email: map['email'], roles: map['roles'], userId: map['userId'], userAvatar: map['userAvatar'], ); } bool get isHonour => roles.contains('honour'); @override List get props => [username, email, roles, userId, userAvatar]; Map toJson() => { "username": username, "email": email, "roles": roles, "userId": userId, "userAvatar": userAvatar, }; } ================================================ FILE: modules/basic_system/authentication/lib/repository/auth_repository.dart ================================================ import 'package:utils/utils.dart'; import '../models/user.dart'; abstract class AuthRepository { // 用户登录接口 Future> login({ required String username, required String password, }); // 用户注册接口 // 邮箱注册 Future> register({ required String email, required String code, }); // 发送邮箱验证信息 Future> sendEmail({ required String email, }); } ================================================ FILE: modules/basic_system/authentication/lib/repository/impl/http_auth_repository.dart ================================================ import 'package:authentication/models/user.dart'; import 'package:utils/src/http_utils/task_result.dart'; import 'package:utils/utils.dart'; import '../auth_repository.dart'; const String kSendEmail = '/sendEmail/'; const String kLogin = '/login'; const String kRegister = '/register'; class HttpAuthRepository implements AuthRepository { @override Future> login({ required String username, required String password, }) async { String errorMsg = ""; try { var result = await HttpUtil.instance.client.post( kLogin, data: { "username": username, "password": password, }, ); if (result.data != null) { if (result.data['status']) { return TaskResult( msg: result.data['msg'], data: User.fromJson(result.data['data']), success: result.data['status'], ); } else { return TaskResult( msg: result.data['msg'], data: null, success: false, ); } } } catch (e) { errorMsg = e.toString(); } return TaskResult.error(msg: '请求错误: $errorMsg'); } @override Future> register({ required String email, required String code, }) async { String errorMsg = ""; try { var result = await HttpUtil.instance.client .post(kRegister, data: {"email": email, "activeCode": code}); if (result.data != null) { return TaskResult.success(data: result.data); } } catch (e) { errorMsg = e.toString(); } return TaskResult.error(msg: '请求错误: $errorMsg'); } @override Future> sendEmail({required String email}) async { try { var result = await HttpUtil.instance.client.post(kSendEmail + email); if (result.data != null) { if(result.data['status']){ return TaskResult.success(data: result.data); }else{ return TaskResult.error(msg: result.data['msg']); } } } catch (e) { print(e); } return const TaskResult.error(msg: '请求错误'); } } ================================================ FILE: modules/basic_system/authentication/lib/views/authentic_widget.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../authentication.dart'; /// create by 张风捷特烈 on 2021/2/24 /// contact me by email 1981462002@qq.com /// 说明: class AuthenticWidget extends StatelessWidget { final Widget authentic; final Widget noAuthentic; const AuthenticWidget( {Key? key, required this.authentic, required this.noAuthentic}) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder( builder: (_, state) { return state is AuthSuccess ? authentic : noAuthentic; }); } factory AuthenticWidget.just(Widget authentic){ return AuthenticWidget( authentic: authentic, noAuthentic: const SizedBox(), ); } } ================================================ FILE: modules/basic_system/authentication/lib/views/mobile/login/login_form.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:utils/utils.dart'; import '../../../authentication.dart'; class LoginFrom extends StatefulWidget { const LoginFrom({Key? key}) : super(key: key); @override _LoginFromState createState() => _LoginFromState(); } class _LoginFromState extends State { final _usernameController = TextEditingController(text: '张风捷特烈'); final _passwordController = TextEditingController(text: '111111'); bool _showPwd = false; @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ const Text( "Flutter Unit 登录", style: TextStyle(fontSize: 25), ), const SizedBox( height: 5, ), const Text( "登录账号,更多精彩,更多体验 ~", style: TextStyle(color: Colors.grey), ), const SizedBox( height: 20, ), buildUsernameInput(), Stack( alignment: const Alignment(.8, 0), children: [ buildPasswordInput(), FeedbackWidget( onPressed: () => setState(() => _showPwd = !_showPwd), child: Icon(_showPwd ? TolyIcon.icon_show : TolyIcon.icon_hide)) ], ), // Row( // children: [ // Checkbox(value: true, onChanged: (e) => {}), // const Text( // "自动登录", // style: TextStyle(color: Color(0xff444444), fontSize: 14), // ), // const Spacer(), // // ], // ), BlocConsumer( listener: _listenLoginState, builder: _buildBtnByState, ), GestureDetector( onTap: () { // Navigator.of(context).pushReplacementNamed(UnitRouter.register); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: const Text( "没有账号,立即注册", style: TextStyle( color: Colors.blue, fontSize: 14, decoration: TextDecoration.underline,decorationColor: Colors.blue), ), ), ), // buildOtherLogin(), const Spacer(flex: 4), ], ); } void _doLogIn() { print('---用户名:${_usernameController.text}------密码:${_passwordController.text}---'); String username = _usernameController.text; String password = _passwordController.text; if (!_preValidate(username, password)) return; BlocProvider.of(context).add( AuthByPassword(username: username, password: password), ); } Widget buildUsernameInput() { return Column( children: [ Container( decoration: BoxDecoration( border: Border.all( color: Colors.grey.withOpacity(0.5), width: 1.0, ), borderRadius: BorderRadius.circular(15.0), ), margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), child: Row( children: [ const Padding( padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), child: Icon( Icons.person_outline, color: Colors.grey, ), ), Container( height: 20.0, width: 1.0, color: Colors.grey.withOpacity(0.5), margin: const EdgeInsets.only(left: 00.0, right: 10.0), ), Expanded( child: TextField( controller: _usernameController, decoration: const InputDecoration( border: InputBorder.none, hintText: '请输入用户名...', hintStyle: TextStyle(color: Colors.grey), ), ), ) ], ), ) ], ); } Widget buildPasswordInput() { return Column( children: [ Container( decoration: BoxDecoration( border: Border.all( color: Colors.grey.withOpacity(0.5), width: 1.0, ), borderRadius: BorderRadius.circular(15.0), ), margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), child: Row( children: [ const Padding( padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), child: Icon( Icons.lock_outline, color: Colors.grey, ), ), Container( height: 30.0, width: 1.0, color: Colors.grey.withOpacity(0.5), margin: const EdgeInsets.only(left: 00.0, right: 10.0), ), Expanded( child: TextField( obscureText: !_showPwd, controller: _passwordController, decoration: const InputDecoration( border: InputBorder.none, hintText: '请输入密码...', hintStyle: TextStyle(color: Colors.grey), ), ), ) ], ), ) ], ); } Widget buildOtherLogin() { return Wrap( alignment: WrapAlignment.center, children: [ Padding( padding: const EdgeInsets.only(top: 30.0), child: Row( children: const [ Expanded( child: Divider( height: 20, )), Padding( padding: EdgeInsets.all(8.0), child: Text( '第三方登录', style: TextStyle(color: Colors.grey), ), ), Expanded( child: Divider( height: 20, )), ], ), ), const Icon( TolyIcon.icon_github, color: Colors.black, size: 30, ) ], ); } Widget _buildBtnByState(BuildContext context, AuthState state) { if (state is AuthLoading) { return Container( margin: const EdgeInsets.only(top: 10, bottom: 0), height: 40, width: 40, child: ElevatedButton( style: ElevatedButton.styleFrom( elevation: 0, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20))), backgroundColor: Colors.blue.withOpacity(0.4), ), onPressed: _doLogIn, child: const CupertinoActivityIndicator(), )); } return Container( margin: const EdgeInsets.only(top: 10, left: 10, right: 10, bottom: 0), height: 40, width: MediaQuery.of(context).size.width, child: ElevatedButton( style: ElevatedButton.styleFrom( elevation: 0, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20))), backgroundColor: Colors.blue, ), onPressed: _doLogIn, child: const Text("进入 Unit 世界", style: TextStyle(color: Colors.white, fontSize: 14,fontWeight: FontWeight.bold)), )); } void _listenLoginState(BuildContext context, AuthState state) { if (state is AuthSuccess) { Navigator.of(context).pop(); } if (state is AuthFailure) { Toast.toast(context, '登录失败 : ${state.error}!', color: Colors.red, duration: const Duration(seconds: 2)); } } bool _preValidate(String username, String password) { if (username.isEmpty || password.isEmpty) { Toast.toast(context, '登录失败 : 用户名和密码不能为空!', color: Colors.orange, duration: const Duration(seconds: 2)); return false; } return true; } } ================================================ FILE: modules/basic_system/authentication/lib/views/mobile/login/login_page.dart ================================================ import 'package:authentication/authentication.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../register/arc_clipper.dart'; import 'login_form.dart'; /// create by 张风捷特烈 on 2020/4/24 /// contact me by email 1981462002@qq.com /// 说明: // class AuthenticScope extends StatelessWidget { // const AuthenticScope({Key? key}) : super(key: key); // // @override // Widget build(BuildContext context) { // final AuthRepository repository = context.read().repository; // return MultiBlocProvider(providers: [ // BlocProvider(create: (_) => RegisterBloc(repository: repository)), // ], child: const LoginPage()); // } // } class LoginPage extends StatelessWidget { const LoginPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { Size winSize = MediaQuery.of(context).size; return Scaffold( body: SingleChildScrollView( child: Wrap(children: [ Stack(children: [ UnitArcBackground(height: winSize.height * 0.3), const Positioned(top: 24, child: BackButton(color: Colors.white)), ]), Container( // color: Colors.green, height: winSize.height * 0.68, width: MediaQuery.of(context).size.width, padding: const EdgeInsets.only(left: 20.0, right: 20, top: 10), child: Stack( alignment: Alignment.center, children: const [ LoginFrom(), ], )) ]), )); } } ================================================ FILE: modules/basic_system/authentication/lib/views/mobile/register/arc_clipper.dart ================================================ import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/4/27 /// contact me by email 1981462002@qq.com /// 说明: class ArcClipper extends CustomClipper { final double factor; ArcClipper({this.factor = 0.618}); @override Path getClip(Size size) => Path() ..moveTo(0, 0) ..relativeLineTo(size.width, 0) ..relativeLineTo(0, 0.8 * size.height) ..arcToPoint( Offset(0.0, size.height * 0.618), radius: const Radius.elliptical(50.0, 10.0), rotation: 0.0, ) ..close(); @override bool shouldReclip(ArcClipper oldClipper) => factor != oldClipper.factor; } class ArcBackground extends StatelessWidget { final Widget? child; final ImageProvider image; const ArcBackground({Key? key, this.child, required this.image}) : super(key: key); @override Widget build(BuildContext context) { return ClipPath( clipper: ArcClipper(), child: Container( decoration: BoxDecoration( image: DecorationImage( image: image, fit: BoxFit.cover, ), ), alignment: Alignment.center, child: child, ), ); } } class UnitArcBackground extends StatelessWidget { final double height; const UnitArcBackground({Key? key,required this.height}) : super(key: key); @override Widget build(BuildContext context) { return SizedBox( height: height, child: ArcBackground( image: const AssetImage("assets/images/caver.webp"), child: Container( padding: const EdgeInsets.all(30), decoration: BoxDecoration( color: Colors.blue.withAlpha(88), shape: BoxShape.circle), child: const CircleImage( size: 100, roundColor: Colors.blue, image: AssetImage( 'assets/images/sabar.webp', )), ), ), ); } } ================================================ FILE: modules/basic_system/authentication/lib/views/mobile/register/register_page.dart ================================================ import 'package:authentication/authentication.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:utils/utils.dart'; import 'arc_clipper.dart'; import 'send_code.dart'; /// create by 张风捷特烈 on 2020/4/24 /// contact me by email 1981462002@qq.com /// 说明: class RegisterPage extends StatefulWidget { const RegisterPage({Key? key}) : super(key: key); @override _RegisterPageState createState() => _RegisterPageState(); } class _RegisterPageState extends State { final _emailCtrl = TextEditingController(text: '1981462002@qq.com'); final _codeCtrl = TextEditingController(text: ''); final ValueNotifier _enableRegister = ValueNotifier(false); bool get enable => _emailCtrl.text.isNotEmpty && _codeCtrl.text.isNotEmpty; @override void initState() { super.initState(); _emailCtrl.addListener(() { _enableRegister.value = enable; }); _codeCtrl.addListener(() { _enableRegister.value = enable; }); } @override Widget build(BuildContext context) { Size winSize = MediaQuery.of(context).size; return Scaffold( body: SingleChildScrollView( child: Wrap(children: [ Stack(children: [ UnitArcBackground(height: winSize.height * 0.30), const Positioned(top: 20, child: BackButton(color: Colors.white)), ]), Container( width: winSize.width, height: winSize.height * 0.68, padding: const EdgeInsets.only(left: 20.0, right: 20, top: 10), child: Column( children: [ const Text( "Flutter Unit 注册", style: TextStyle(fontSize: 25), ), const SizedBox( height: 5, ), const Text( "登录账号,更多精彩,更多体验 ~", style: TextStyle(color: Colors.grey), ), const Spacer( flex: 1, ), IconInput( icon: Icons.person_outline, textFiled: TextField( controller: _emailCtrl, decoration: const InputDecoration( border: InputBorder.none, hintText: '请输入邮箱', hintStyle: TextStyle(color: Colors.grey), ), ), ), const SizedBox(height: 10), buildInputWithSend(), const Spacer(flex: 1), _buildBtn(), const Spacer(flex: 6), ], )) ]), )); } Stack buildInputWithSend() { return Stack( alignment: const Alignment(.8, 0), children: [ IconInput( icon: Icons.code_outlined, textFiled: TextField( controller: _codeCtrl, decoration: const InputDecoration( border: InputBorder.none, hintText: '请输入验证码', hintStyle: TextStyle(color: Colors.grey), ), ), ), CountDownWidget( onPress: _sendEmail, ) ], ); } _sendEmail(BuildContext context) async { if (!_checkEmail(_emailCtrl.text)) { Toast.toast(context, '邮箱格式校验错误,请重试!', color: Colors.orange, duration: const Duration(seconds: 2)); return; } AuthRepository repository = context.read().repository; TaskResult result = await repository.sendEmail(email: _emailCtrl.text); print(result); if (result.success) { Toast.toast(context, '验证码发送成功,请注意邮箱查收!', duration: const Duration(seconds: 2)); } else { Toast.toast(context, '验证码发送失败: ${result.msg}!', color: Colors.red, duration: const Duration(seconds: 2)); } } bool _checkEmail(String email) { RegExp exp = RegExp(r'^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$'); return exp.hasMatch(email); } Widget _buildBtn() => Container( margin: const EdgeInsets.only(top: 10, left: 10, right: 10, bottom: 0), height: 40, width: MediaQuery.of(context).size.width, child: BlocConsumer( builder: _build, listener: _listener, ), ); void _doRegister() { BlocProvider.of(context) .add(AuthByRegister(_emailCtrl.text, _codeCtrl.text)); } Widget _build(BuildContext context, state) { bool enable = state is AuthLoading || state is AuthSuccess; String info = enable ? '注册中...' : '开启 Unit 新世界'; return BlocListener( listener: _listenerLogin, child: ValueListenableBuilder( valueListenable: _enableRegister, builder: (ctx, bool value, child) { return ElevatedButton( style: ElevatedButton.styleFrom( elevation: 0, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20))), backgroundColor: Colors.blue, disabledBackgroundColor: Colors.blue.withOpacity(0.6), ), onPressed: (enable || !value) ? null : _doRegister, child: Text(info, style: TextStyle(color: Colors.white, fontSize: 14,fontWeight: FontWeight.bold)), ); }, )); } void _listener(BuildContext context, AuthState state) { if (state is AuthFailure) { Toast.toast(context, '注册失败 : ${state.error}!', color: Colors.red, duration: const Duration(seconds: 2)); } if (state is AuthSuccess) { // BlocProvider.of(context).add(AuthByPassword( // username: _emailCtrl.text, // password: _codeCtrl.text, // )); } } void _listenerLogin(BuildContext context, AuthState state) { if (state is AuthSuccess) { Navigator.pop(context); } } } ================================================ FILE: modules/basic_system/authentication/lib/views/mobile/register/send_code.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2021/1/17 /// contact me by email 1981462002@qq.com /// 说明: class CountDownWidget extends StatefulWidget { final Function(BuildContext context)? onPress; const CountDownWidget({Key? key, this.onPress}) : super(key: key); @override _CountDownWidgetState createState() => _CountDownWidgetState(); } class _CountDownWidgetState extends State { Timer? timer; int count = 60; bool startTimer = false; @override void initState() { super.initState(); } @override void dispose() { timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return TextButton( onPressed: startTimer ? null : () { timer = Timer.periodic(const Duration(seconds: 1), _update); setState(() { startTimer = true; }); widget.onPress?.call(context); }, child: Text(startTimer ? '$count 秒后重试' : '获取验证码')); } void _update(Timer timer) { count--; if (count == 0) { timer.cancel(); startTimer = false; count = 60; } setState(() {}); } } ================================================ FILE: modules/basic_system/authentication/lib/views/mobile/user/page_item.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:fx_updater/views/update_red_point.dart'; import 'package:go_router/go_router.dart'; import 'package:l10n/l10n.dart'; /// create by 张风捷特烈 on 2020-03-26 /// contact me by email 1981462002@qq.com /// 说明: class MePageItem extends StatelessWidget { final Color color; const MePageItem({Key? key, this.color = Colors.white}) : super(key: key); @override Widget build(BuildContext context) { return _buildChild(context); } Widget get divider { return const Divider(); } Widget _buildChild(BuildContext context) { return ScrollConfiguration( behavior: NoScrollBehavior(), child: ListView( padding: EdgeInsets.zero, children: [ const SizedBox(height: 10), Gap.sfl10, _buildItem(context, TolyIcon.icon_them, context.l10n.appSettings, AppRoute.settings.url), divider, _buildItem(context, TolyIcon.icon_layout, context.l10n.dataManagement, AppRoute.dataManage.url), divider, _buildItem( context, TolyIcon.icon_collect, context.l10n.userCollection, AppRoute.collection.url, ), divider, _buildItem( context, Icons.note_alt, context.l10n.messageBoard, AppRoute.note.url, ), Gap.sfl10, Stack( children: [ _buildItem( context, Icons.update, context.l10n.versionInformation, AppRoute.version.url, ), const Positioned(left: 40, top: 10, child: UpdateRedPoint()) ], ), divider, _buildItem(context, Icons.info, context.l10n.aboutApplications, AppRoute.aboutApp.url), Gap.sfl10, _buildItem(context, TolyIcon.icon_kafei, context.l10n.contactThisKing, AppRoute.aboutMe.url), divider, _buildItem(context, Icons.sanitizer, context.l10n.homeAccountSupport, AppRoute.supportMe.url), ], ), ); } Widget _buildItem( BuildContext context, IconData icon, String title, String linkTo, {VoidCallback? onTap}) => ListTile( leading: Icon( icon, color: Theme.of(context).primaryColor, ), title: Text(title, style: const TextStyle(fontSize: 16)), trailing: Icon(Icons.chevron_right, color: Theme.of(context).primaryColor), onTap: () { if (linkTo.isNotEmpty) { context.push(linkTo); if (onTap != null) onTap(); } }, ); } ================================================ FILE: modules/basic_system/authentication/lib/views/mobile/user/support_me.dart ================================================ import 'package:flutter/material.dart'; import 'package:l10n/l10n.dart'; class SupportMe extends StatelessWidget { const SupportMe({super.key}); @override Widget build(BuildContext context) { return DefaultTabController( length: 3, child: Scaffold( appBar: AppBar( title: Column( children: [ Text(context.l10n.homeAccountSupport), Text('开源不易, 请我喝咖啡~',style: TextStyle(fontSize: 12,fontWeight: FontWeight.normal),), ], ), bottom: TabBar( tabs: [ Tab( text: '支付宝', ), Tab( text: '微信1', ), Tab( text: '微信2', ), ], ), ), body: TabBarView( children: [ Image.asset( 'assets/images/coffee_zfb.webp', ), Image.asset('assets/images/coffee_wx.webp'), Image.asset('assets/images/coffee_wx_ac.webp'), ], ), ), ); } } ================================================ FILE: modules/basic_system/authentication/lib/views/mobile/user/unit_drawer_header.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020-04-22 /// contact me by email 1981462002@qq.com /// 说明: class UnitDrawerHeader extends StatelessWidget { final Color color; const UnitDrawerHeader({Key? key, required this.color}) : super(key: key); @override Widget build(BuildContext context) { return DrawerHeader( padding: const EdgeInsets.only(top: 10, left: 15), decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/login_bg.png'), fit: BoxFit.cover), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Wrap( spacing: 10, crossAxisAlignment: WrapCrossAlignment.center, children: const [ FlutterLogo( // colors: Colors.orange, size: 35, ), Text( 'Flutter Unit', style: TextStyle(fontSize: 24, color: Colors.white, shadows: [ Shadow( color: Colors.black, offset: Offset(1, 1), blurRadius: 3) ]), ), ], ), const SizedBox( height: 15, ), Text( 'The Unity Of Flutter, The Unity Of Coder.', style: TextStyle(fontSize: 15, color: Colors.white, shadows: [ Shadow(color: color, offset: const Offset(.5, .5), blurRadius: 1) ]), ), const SizedBox( height: 5, ), Text( 'Flutter的联合,编程者的联合。', style: TextStyle(fontSize: 15, color: Colors.white, shadows: [ Shadow(color: color, offset: const Offset(.5, .5), blurRadius: 1) ]), ), const SizedBox( height: 10, ), Row( children: const [ Spacer( flex: 5, ), Text( '—— 张风捷特烈', style: TextStyle(fontSize: 15, color: Colors.white, shadows: [ Shadow( color: Colors.orangeAccent, offset: Offset(.5, .5), blurRadius: 1) ]), ), Spacer( flex: 1, ), ], ), ], ), ); } } ================================================ FILE: modules/basic_system/authentication/lib/views/mobile/user/user_account.dart ================================================ import 'dart:io'; import 'dart:ui'; import 'package:authentication/blocs/authentic/bloc.dart'; import 'package:authentication/blocs/user/bloc.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../blocs/authentic/event.dart'; import '../../../blocs/user/state.dart'; class UserAccountPage extends StatelessWidget { const UserAccountPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { Icon trailing = const Icon( Icons.navigate_next, color: Color(0xffD9D9D9), ); Color? color = Theme.of(context).listTileTheme.tileColor; Color? sbgColor = Theme.of(context).appBarTheme.backgroundColor; Color? bgColor = Theme.of(context).scaffoldBackgroundColor; bool isDark = Theme.of(context).brightness == Brightness.dark; UserPerformance performance = context.select( (bloc) => bloc.state, ); return Scaffold( backgroundColor: isDark ? null : bgColor, appBar: AppBar( backgroundColor: isDark ? null : sbgColor, title: const Text( '账号资料', ), ), body: Column( children: [ const SizedBox( height: 10, ), GestureDetector( onTap: () => _showPicker(context), child: Container( color: color, height: 64, child: Row( children: [ Container( padding: const EdgeInsets.only(left: 15), width: 120, child: const Text('头像')), const Spacer(), // AuthUserAvatar( // size: 50, // borderSize: 2, // ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: trailing, ) ], ), ), ), Divider( height: 1 / window.devicePixelRatio, thickness: 1 / window.devicePixelRatio, ), GestureDetector( onTap: () { // LoggingUploader.onEvent(kSetNameAction, kMyInfoPageName); // Navigator.push( // context, // Right2LeftRouter( // duration: Duration(milliseconds: 200), // child: UserChangeNamePage( // name: performance.username, // ))); }, child: Container( color: color, height: 54, child: Row( children: [ Container( padding: const EdgeInsets.only(left: 15), width: 120, child: const Text('昵称')), const Spacer(), Text( performance.username ?? '', style: const TextStyle(color: Colors.grey), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: trailing, ) ], ), ), ), Divider( height: 1 / window.devicePixelRatio, thickness: 1 / window.devicePixelRatio, ), Container( color: color, height: 54, child: Row( children: [ Container( padding: const EdgeInsets.only(left: 15), width: 120, child: const Text('箴言')), const Spacer(), const Text( // '${performance.userId}', '海的彼岸,有我未曾见证的风采。', style: TextStyle(color: Colors.grey,fontSize: 12), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 12), child: Icon( Icons.arrow_forward_ios_sharp, size: 20, color: Colors.transparent, ), ) ], ), ), // const SizedBox( // height: 10, // ), // Container( // color: color, // height: 54, // child: Row( // children: [ // Container( // padding: EdgeInsets.only(left: 15), // width: 120, // child: Text('免费蜂蜜/日')), // Spacer(), // Text( // '${ 0}', // style: TextStyle(color: Colors.grey), // ), // Padding( // padding: const EdgeInsets.symmetric(horizontal: 12), // child: Icon( // Icons.arrow_forward_ios_sharp, // size: 20, // color: Colors.transparent, // ), // ) // ], // ), // ), // Divider( // height: 1 / window.devicePixelRatio, // thickness: 1 / window.devicePixelRatio, // ), // Container( // color: color, // height: 54, // child: Row( // children: [ // Container( // padding: EdgeInsets.only(left: 15), // width: 120, // child: Text('蜂蜜')), // Spacer(), // Text( // '${0}', // style: TextStyle(color: Colors.grey), // ), // Padding( // padding: const EdgeInsets.symmetric(horizontal: 12), // child: Icon( // Icons.arrow_forward_ios_sharp, // size: 20, // color: Colors.transparent, // ), // ) // ], // ), // ), const SizedBox( height: 10, ), Container( color: color, height: 54, child: Row( children: [ Container( padding: const EdgeInsets.only(left: 15), width: 120, child: const Text('账号')), const Spacer(), const Text( // '${performance.userId}', '******', style: TextStyle(color: Colors.grey,fontSize: 12), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 12), child: Icon( Icons.arrow_forward_ios_sharp, size: 20, color: Colors.transparent, ), ) ], ), ), const SizedBox( height: 10, ), ListTile( title: const Center( child: Text( '退出登录', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), )), // trailing: _nextIcon(context), onTap: () { showDialog( context: context, builder: (ctx) => AlertConformDialog( title: "登出提示", content: "退出后将无法使用用户相关的功能,确定退出登录吗?", conformText: '确定', onConform: () async { context.read().add(const Logout()); // Navigator.of(context).pushAndRemoveUntil( // NoAnimRouter(AuthRelation( // pushLogin: false, // child: MoAILoginPage( // model: LoginModel( // appName: "蜜蜂AI智能助手", // appNameEn: // "Bee Chat AI Intelligence Assistant", // appIcon: Icons.widgets_outlined, // loginBgAssets: "assets/images/login_bg.png"), // ), // )), // ModalRoute.withName('/'), // ); return true; })); }, ), Divider( height: 1 / window.devicePixelRatio, thickness: 1 / window.devicePixelRatio, ), // const SizedBox( // height: 10, // ), // if(false) ListTile( title: const Center( child: Text( '删除账号', style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: Colors.redAccent), )), // trailing: _nextIcon(context), onTap: () { String msg = "确定删除账号吗?删除后你将无法再访问蜜蜂 ai 提供的智能服务,并清空你的所有账号资料,点击确定删除。"; showDialog( context: context, builder: (ctx) => Dialog( child: MobileMessagePanel( title: '清空提示', conformText: '确定', msg: msg, task: (_) async { // await Future.delayed(Duration(seconds: 3)); // await context.read().repo.unregister(); // context.read().add(const Logout()); // Navigator.of(context).pushAndRemoveUntil( // NoAnimRouter(AuthRelation( // pushLogin: false, // child: MoAILoginPage( // model: LoginModel( // appName: "蜜蜂AI智能助手", // appNameEn: // "Bee Chat AI Intelligence Assistant", // appIcon: Icons.widgets_outlined, // loginBgAssets: // "assets/images/login_bg.png"), // ), // )), // ModalRoute.withName('/'), // ); }, ), )); }, ), ], ), ); } void _showPicker(BuildContext context) async { // FilePickerResult? result = // await FilePicker.platform.pickFiles(type: FileType.image); // if (result != null) { // String? p = result.files.single.path; // if (p != null) { // // File file = File(p); // // Share.shareXFiles([XFile(file.path)], text: 'Great picture'); // Navigator.push( // context, // NoAnimRouter(ClipImagePage( // image: FileImage(File(p)), // ))); // } // } // // showCupertinoModalPopup( // // context: context, // // builder: (ctx) => ClipRRect( // // borderRadius: BorderRadius.only( // // topLeft: Radius.circular(10), // // topRight: Radius.circular(10), // // ), // // child: SizedBox( // // width: 500, // // child: AsyncPopPicker( // // title: Text( // // '更换头像', // // style: TextStyle(color: Colors.grey), // // ), // // tasks: [ // // AsyncPopItem( // // task: () async { // // try { // // FilePickerResult? result = await FilePicker.platform // // .pickFiles(type: FileType.image); // // if (result != null) { // // String? p = result.files.single.path; // // if (p != null) { // // // File file = File(p); // // // Share.shareXFiles([XFile(file.path)], text: 'Great picture'); // // Navigator.push( // // context, // // NoAnimRouter(ClipImagePage( // // image: FileImage(File(p)), // // ))); // // } // // } // // } catch (e) { // // Toast.warning("当前应用没有文件读写权限,请先在权限管理中允许!"); // // } // // }, // // info: '从相册选取', // // ), // // ], // // ), // // ), // // )); } } class UserItemPanel extends StatelessWidget { final String label; final String value; final Color? color; const UserItemPanel( {Key? key, required this.label, required this.value, required this.color}) : super(key: key); @override Widget build(BuildContext context) { return Container( color: color, height: 54, child: Row( children: [ Expanded( child: Container( padding: const EdgeInsets.only(left: 15), child: Text(label)), ), Text( value, style: const TextStyle(color: Colors.grey), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 12), child: Icon( Icons.arrow_forward_ios_sharp, size: 20, color: Colors.transparent, ), ) ], ), ); } } ================================================ FILE: modules/basic_system/authentication/lib/views/mobile/user/user_page.dart ================================================ import 'package:app/app.dart'; import 'package:authentication/views/mobile/user/user_account.dart'; import 'package:components/project_ui/project_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:toly_ui/toly_ui.dart'; import '../../../authentication.dart'; import 'page_item.dart'; /// create by 张风捷特烈 on 2020/4/26 /// contact me by email 1981462002@qq.com /// 说明: class UserPage extends StatelessWidget { const UserPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; String image = isDark?'anim_draw.webp':'base_draw.webp'; return Scaffold( body: AnnotatedRegion( value:Theme.of(context).appBarTheme.systemOverlayStyle!, child: Column( children: [ Stack( children: [ Container( height: 180, width: MediaQuery.of(context).size.width, margin: const EdgeInsets.only(bottom: 40), child: Image.asset( 'assets/images/$image', fit: BoxFit.cover, ), ), Positioned( top: 50, right: 20, child: Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), borderRadius: BorderRadius.circular(4)), child: const Icon( Icons.color_lens_outlined, color: Colors.white, size: 20, )), ), Positioned( bottom: 0, left: 40, child: BlocBuilder( builder: _buildAvatarByState, ), ), Positioned( bottom: 5, right: 30, child: BlocBuilder( builder: _buildByState, )) ], ), const Expanded(child: MePageItem()) ], ), )); } Widget _buildByState(BuildContext context, AuthState state) { if (state is AuthSuccess) { if (state.user.isHonour) { return HonourWrapper(username: state.user.username); } return Text( state.user.username, style: TextStyle(fontSize: 18, color: Theme.of(context).primaryColor), ); } return Text( '张风捷特烈', style: TextStyle( fontSize: 18, color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold), ); } Widget _buildAvatarByState(BuildContext context, AuthState state) { if (state is AuthSuccess) { return FeedbackWidget( onEnd: () { Navigator.of(context).push(SlidePageRoute(child: UserAccountPage())); }, child: CircleImage( size: 80, shadowColor: Theme.of(context).primaryColor.withAlpha(33), // image: NetworkImage(state.user.userAvatar), image: const AssetImage("assets/images/icon_head.webp"), ), ); } return FeedbackWidget( onEnd: () { // Navigator.of(context).pushNamed(UnitRouter.login); }, child: CircleImage( size: 80, shadowColor: Theme.of(context).primaryColor.withAlpha(33), image: const AssetImage("assets/images/icon_head.webp"), ), ); } } ================================================ FILE: modules/basic_system/authentication/pubspec.yaml ================================================ name: authentication description: A new Flutter package project. version: 0.0.1 homepage: environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter flutter_bloc: ^8.1.6 # 状态管理 equatable: ^2.0.5 # 相等辅助 shared_preferences: ^2.2.1 # xml 固化 go_router: ^14.2.0 utils: path: ../utils fx_updater: path: ../fx_updater app: path: ../app l10n: path: ../l10n components: path: ../components toly_ui: path: ../toly_ui # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/basic_system/authentication/test/authentication_test.dart ================================================ // import 'package:flutter_test/flutter_test.dart'; // // import 'package:authentication/authentication.dart'; // // void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); // } ================================================ FILE: modules/basic_system/components/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .packages build/ ================================================ FILE: modules/basic_system/components/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 channel: stable project_type: package ================================================ FILE: modules/basic_system/components/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/basic_system/components/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/basic_system/components/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/basic_system/components/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/basic_system/components/lib/components.dart ================================================ library components; export 'flutter_ui/flutter_ui.dart'; export 'project_ui/project_ui.dart'; ================================================ FILE: modules/basic_system/components/lib/flutter_ui/diy_flexible_space_bar.dart ================================================ // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math' as math; import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; /// The part of a Material Design [AppBar] that expands, collapses, and /// stretches. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=mSc7qFzxHDw} /// /// Most commonly used in the [SliverAppBar.flexibleSpace] field, a flexible /// space bar expands and contracts as the app scrolls so that the [AppBar] /// reaches from the top of the app to the top of the scrolling contents of the /// app. When using [SliverAppBar.flexibleSpace], the [SliverAppBar.expandedHeight] /// must be large enough to accommodate the [SliverAppBar.flexibleSpace] widget. /// /// Furthermore is included functionality for stretch behavior. When /// [SliverAppBar.stretch] is true, and your [ScrollPhysics] allow for /// overscroll, this space will stretch with the overscroll. /// /// The widget that sizes the [AppBar] must wrap it in the widget returned by /// [DiyFlexibleSpaceBar.createSettings], to convey sizing information down to the /// [DiyFlexibleSpaceBar]. /// /// {@tool dartpad} /// This sample application demonstrates the different features of the /// [DiyFlexibleSpaceBar] when used in a [SliverAppBar]. This app bar is configured /// to stretch into the overscroll space, and uses the /// [DiyFlexibleSpaceBar.stretchModes] to apply `fadeTitle`, `blurBackground` and /// `zoomBackground`. The app bar also makes use of [CollapseMode.parallax] by /// default. /// /// ** See code in examples/api/lib/material/flexible_space_bar/flexible_space_bar.0.dart ** /// {@end-tool} /// /// See also: /// /// * [SliverAppBar], which implements the expanding and contracting. /// * [AppBar], which is used by [SliverAppBar]. /// * typedef FractionalBuilder = Widget Function(double t); class DiyFlexibleSpaceBar extends StatefulWidget { /// Creates a flexible space bar. /// /// Most commonly used in the [AppBar.flexibleSpace] field. const DiyFlexibleSpaceBar({ super.key, this.title, this.fixedSubtitle, this.titleIconBuilder, this.background, this.centerTitle, this.titlePadding, this.collapseMode = CollapseMode.parallax, this.stretchModes = const [StretchMode.zoomBackground], this.expandedTitleScale = 1.5, }) : assert(collapseMode != null), assert(expandedTitleScale >= 1); /// The primary contents of the flexible space bar when expanded. /// /// Typically a [Text] widget. final Widget? title; final Widget? fixedSubtitle; final FractionalBuilder? titleIconBuilder; /// Shown behind the [title] when expanded. /// /// Typically an [Image] widget with [Image.fit] set to [BoxFit.cover]. final Widget? background; /// Whether the title should be centered. /// /// By default this property is true if the current target platform /// is [TargetPlatform.iOS] or [TargetPlatform.macOS], false otherwise. final bool? centerTitle; /// Collapse effect while scrolling. /// /// Defaults to [CollapseMode.parallax]. final CollapseMode collapseMode; /// Stretch effect while over-scrolling. /// /// Defaults to include [StretchMode.zoomBackground]. final List stretchModes; /// Defines how far the [title] is inset from either the widget's /// bottom-left or its center. /// /// Typically this property is used to adjust how far the title is /// is inset from the bottom-left and it is specified along with /// [centerTitle] false. /// /// By default the value of this property is /// `EdgeInsetsDirectional.only(start: 72, bottom: 16)` if the title is /// not centered, `EdgeInsetsDirectional.only(start: 0, bottom: 16)` otherwise. final EdgeInsetsGeometry? titlePadding; /// Defines how much the title is scaled when the FlexibleSpaceBar is expanded /// due to the user scrolling downwards. The title is scaled uniformly on the /// x and y axes while maintaining its bottom-left position (bottom-center if /// [centerTitle] is true). /// /// Defaults to 1.5 and must be greater than 1. final double expandedTitleScale; @override State createState() => _DiyFlexibleSpaceBarState(); } class _DiyFlexibleSpaceBarState extends State { bool _getEffectiveCenterTitle(ThemeData theme) { if (widget.centerTitle != null) { return widget.centerTitle!; } assert(theme.platform != null); switch (theme.platform) { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: return false; case TargetPlatform.iOS: case TargetPlatform.macOS: return true; } } Alignment _getTitleAlignment(bool effectiveCenterTitle) { if (effectiveCenterTitle) { return Alignment.bottomCenter; } final TextDirection textDirection = Directionality.of(context); assert(textDirection != null); switch (textDirection) { case TextDirection.rtl: return Alignment.bottomRight; case TextDirection.ltr: return Alignment.bottomLeft; } } double _getCollapsePadding(double t, FlexibleSpaceBarSettings settings) { switch (widget.collapseMode) { case CollapseMode.pin: return -(settings.maxExtent - settings.currentExtent); case CollapseMode.none: return 0.0; case CollapseMode.parallax: final double deltaExtent = settings.maxExtent - settings.minExtent; return -Tween(begin: 0.0, end: deltaExtent / 4.0).transform(t); } } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final FlexibleSpaceBarSettings settings = context.dependOnInheritedWidgetOfExactType()!; assert( settings != null, 'A FlexibleSpaceBar must be wrapped in the widget returned by FlexibleSpaceBar.createSettings().', ); final List children = []; final double deltaExtent = settings.maxExtent - settings.minExtent; // 0.0 -> Expanded // 1.0 -> Collapsed to toolbar final double t = clampDouble(1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent, 0.0, 1.0); // print("=======build=======$t========"); // background if (widget.background != null) { final double fadeStart = math.max(0.0, 1.0 - kToolbarHeight / deltaExtent); const double fadeEnd = 1.0; assert(fadeStart <= fadeEnd); // If the min and max extent are the same, the app bar cannot collapse // and the content should be visible, so opacity = 1. final double opacity = settings.maxExtent == settings.minExtent ? 1.0 : 1.0 - Interval(fadeStart, fadeEnd).transform(t); double height = settings.maxExtent; // StretchMode.zoomBackground if (widget.stretchModes.contains(StretchMode.zoomBackground) && constraints.maxHeight > height) { height = constraints.maxHeight; } children.add(Positioned( top: _getCollapsePadding(t, settings), left: 0.0, right: 0.0, height: height, child: Opacity( // IOS is relying on this semantics node to correctly traverse // through the app bar when it is collapsed. alwaysIncludeSemantics: true, opacity: opacity, child: widget.background, ), )); // StretchMode.blurBackground if (widget.stretchModes.contains(StretchMode.blurBackground) && constraints.maxHeight > settings.maxExtent) { final double blurAmount = (constraints.maxHeight - settings.maxExtent) / 10; children.add(Positioned.fill( child: BackdropFilter( filter: ui.ImageFilter.blur( sigmaX: blurAmount, sigmaY: blurAmount, ), child: Container( color: Colors.transparent, ), ), )); } } // title if (widget.title != null) { final ThemeData theme = Theme.of(context); Widget? title; switch (theme.platform) { case TargetPlatform.iOS: case TargetPlatform.macOS: title = widget.title; break; case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: title = Semantics( namesRoute: true, child: widget.title, ); break; } // StretchMode.fadeTitle if (widget.stretchModes.contains(StretchMode.fadeTitle) && constraints.maxHeight > settings.maxExtent) { final double stretchOpacity = 1 - clampDouble( (constraints.maxHeight - settings.maxExtent) / 100, 0.0, 1.0); title = Opacity( opacity: stretchOpacity, child: title, ); } final double opacity = settings.toolbarOpacity; if (opacity > 0.0) { TextStyle titleStyle = theme.primaryTextTheme.headlineSmall!; titleStyle = titleStyle.copyWith( color: titleStyle.color!.withOpacity(opacity), ); final bool effectiveCenterTitle = _getEffectiveCenterTitle(theme); final EdgeInsetsGeometry padding = widget.titlePadding ?? EdgeInsetsDirectional.only( start: effectiveCenterTitle ? 0.0 : 72.0, bottom: 16.0, ); final double scaleValue = Tween(begin: widget.expandedTitleScale, end: 1.0).transform(t); final Matrix4 scaleTransform = Matrix4.identity() ..scale(scaleValue, scaleValue, 1.0)..translate(t*30); final Matrix4 translateTransform = Matrix4.identity() ..translate(t*30); final Alignment titleAlignment = _getTitleAlignment(effectiveCenterTitle); children.add(Container( padding: padding, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, children: [ Transform( alignment: titleAlignment, transform: scaleTransform, child: Align( alignment: titleAlignment, child: DefaultTextStyle( style: titleStyle, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Container( width: constraints.maxWidth / scaleValue, alignment: titleAlignment, child: Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: 5, children: [ title!, if(widget.titleIconBuilder!=null) widget.titleIconBuilder!(t) ], ), ); }, ), ), ), ), if(widget.fixedSubtitle!=null) Transform( alignment: titleAlignment, transform: translateTransform, child: widget.fixedSubtitle!) ], ), ),); } } return ClipRect(child: Stack(children: children)); }, ); } } ================================================ FILE: modules/basic_system/components/lib/flutter_ui/flutter_ui.dart ================================================ export 'diy_flexible_space_bar.dart'; export 'no_div_expansion_tile.dart'; export 'toly_date_picker.dart'; ================================================ FILE: modules/basic_system/components/lib/flutter_ui/no_div_expansion_tile.dart ================================================ // Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; const Duration _kExpand = Duration(milliseconds: 200); /// A single-line [ListTile] with a trailing button that expands or collapses /// the tile to reveal or hide the [children]. /// /// This widget is typically used with [ListView] to create an /// "expand / collapse" list entry. When used with scrolling widgets liked_widget_bloc /// [ListView], a unique [PageStorageKey] must be specified to enable the /// [NoBorderExpansionTile] to save and restore its expanded state when it is scrolled /// in and out of category_view. /// /// See also: /// /// * [ListTile], useful for creating expansion tile [children] when the /// expansion tile represents a sublist. /// * The "Expand/collapse" section of /// . class NoBorderExpansionTile extends StatefulWidget { /// Creates a single-line [ListTile] with a trailing button that expands or collapses /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must /// be non-null. const NoBorderExpansionTile({ Key? key, this.leading, required this.title, this.subtitle, this.backgroundColor, this.onExpansionChanged, this.children = const [], this.trailing, this.initiallyExpanded = false, }) : super(key: key); /// A widget to display before the title. /// /// Typically a [CircleAvatar] widget. final Widget? leading; /// The primary content of the list item. /// /// Typically a [Text] widget. final Widget title; /// Additional content displayed below the title. /// /// Typically a [Text] widget. final Widget? subtitle; /// Called when the tile expands or collapses. /// /// When the tile starts expanding, this function is called with the value /// true. When the tile starts collapsing, this function is called with /// the value false. final ValueChanged? onExpansionChanged; /// The widgets that are displayed when the tile expands. /// /// Typically [ListTile] widgets. final List children; /// The color to display behind the sublist when expanded. final Color? backgroundColor; /// A widget to display instead of a rotating arrow icon. final Widget? trailing; /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default). final bool initiallyExpanded; @override _NoBorderExpansionTileState createState() => _NoBorderExpansionTileState(); } class _NoBorderExpansionTileState extends State with SingleTickerProviderStateMixin { static final Animatable _easeOutTween = CurveTween(curve: Curves.easeOut); static final Animatable _easeInTween = CurveTween(curve: Curves.easeIn); static final Animatable _halfTween = Tween(begin: 0.0, end: 0.5); final ColorTween _borderColorTween = ColorTween(); final ColorTween _headerColorTween = ColorTween(); final ColorTween _iconColorTween = ColorTween(); final ColorTween _backgroundColorTween = ColorTween(); late AnimationController _controller; late Animation _iconTurns; late Animation _heightFactor; late Animation _headerColor; late Animation _iconColor; late Animation _backgroundColor; bool _isExpanded = false; @override void initState() { super.initState(); _controller = AnimationController(duration: _kExpand, vsync: this); _heightFactor = _controller.drive(_easeInTween); _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween)); _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween)); _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween)); _isExpanded = PageStorage.of(context).readState(context) ?? widget.initiallyExpanded; if (_isExpanded) { _controller.value = 1.0; } } @override void dispose() { _controller.dispose(); super.dispose(); } void _handleTap() { setState(() { _isExpanded = !_isExpanded; if (_isExpanded) { _controller.forward(); } else { _controller.reverse().then((void value) { if (!mounted) { return; } setState(() { // Rebuild without widget.children. }); }); } PageStorage.of(context).writeState(context, _isExpanded); }); widget.onExpansionChanged?.call(_isExpanded); } Widget _buildChildren(BuildContext context, Widget? child) { return Container( color: _backgroundColor.value ?? Colors.transparent, child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTileTheme.merge( iconColor: _iconColor.value, textColor: _headerColor.value, child: ListTile( onTap: _handleTap, leading: widget.leading, title: widget.title, subtitle: widget.subtitle, trailing: widget.trailing ?? RotationTransition( turns: _iconTurns, child: const Icon(Icons.expand_more), ), ), ), ClipRect( child: Align( // alignment: Alignment.topCenter, heightFactor: _heightFactor.value, child: child, ), ), ], ), ); } @override void didChangeDependencies() { final ThemeData theme = Theme.of(context); _borderColorTween .end = theme.dividerColor; _headerColorTween ..begin = theme.textTheme.bodyMedium?.color ..end = theme.primaryColor; _iconColorTween ..begin = theme.unselectedWidgetColor ..end = theme.primaryColor; _backgroundColorTween .end = widget.backgroundColor; super.didChangeDependencies(); } @override Widget build(BuildContext context) { final bool closed = !_isExpanded && _controller.isDismissed; return AnimatedBuilder( animation: _controller.view, builder: _buildChildren, child: closed ? null : Column(children: widget.children), ); } } ================================================ FILE: modules/basic_system/components/lib/flutter_ui/toly_date_picker.dart ================================================ // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math' as math; import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; const Size _calendarPortraitDialogSize = Size(330.0, 518.0); const Size _calendarLandscapeDialogSize = Size(496.0, 346.0); const Size _inputPortraitDialogSize = Size(330.0, 270.0); const Size _inputLandscapeDialogSize = Size(496, 160.0); const Size _inputRangeLandscapeDialogSize = Size(496, 164.0); const Duration _dialogSizeAnimationDuration = Duration(milliseconds: 200); const double _inputFormPortraitHeight = 98.0; const double _inputFormLandscapeHeight = 108.0; /// Shows a dialog containing a Material Design date picker. /// /// The returned [Future] resolves to the date selected by the user when the /// user confirms the dialog. If the user cancels the dialog, null is returned. /// /// When the date picker is first displayed, it will show the month of /// [initialDate], with [initialDate] selected. /// /// The [firstDate] is the earliest allowable date. The [lastDate] is the latest /// allowable date. [initialDate] must either fall between these dates, /// or be equal to one of them. For each of these [DateTime] parameters, only /// their dates are considered. Their time fields are ignored. They must all /// be non-null. /// /// The [currentDate] represents the current day (i.e. today). This /// date will be highlighted in the day grid. If null, the date of /// `DateTime.now()` will be used. /// /// An optional [initialEntryMode] argument can be used to display the date /// picker in the [DatePickerEntryMode.calendar] (a calendar month grid) /// or [DatePickerEntryMode.input] (a text input field) mode. /// It defaults to [DatePickerEntryMode.calendar] and must be non-null. /// /// An optional [selectableDayPredicate] function can be passed in to only allow /// certain days for selection. If provided, only the days that /// [selectableDayPredicate] returns true for will be selectable. For example, /// this can be used to only allow weekdays for selection. If provided, it must /// return true for [initialDate]. /// /// The following optional string parameters allow you to override the default /// text used for various parts of the dialog: /// /// * [helpText], label displayed at the top of the dialog. /// * [cancelText], label on the cancel button. /// * [confirmText], label on the ok button. /// * [errorFormatText], message used when the input text isn't in a proper date format. /// * [errorInvalidText], message used when the input text isn't a selectable date. /// * [fieldHintText], text used to prompt the user when no text has been entered in the field. /// * [fieldLabelText], label for the date text input field. /// /// An optional [locale] argument can be used to set the locale for the date /// picker. It defaults to the ambient locale provided by [Localizations]. /// /// An optional [textDirection] argument can be used to set the text direction /// ([TextDirection.ltr] or [TextDirection.rtl]) for the date picker. It /// defaults to the ambient text direction provided by [Directionality]. If both /// [locale] and [textDirection] are non-null, [textDirection] overrides the /// direction chosen for the [locale]. /// /// The [context], [useRootNavigator] and [routeSettings] arguments are passed to /// [showDialog], the documentation for which discusses how it is used. [context] /// and [useRootNavigator] must be non-null. /// /// The [builder] parameter can be used to wrap the dialog widget /// to add inherited widgets like [Theme]. /// /// An optional [initialDatePickerMode] argument can be used to have the /// calendar date picker initially appear in the [DatePickerMode.year] or /// [DatePickerMode.day] mode. It defaults to [DatePickerMode.day], and /// must be non-null. /// /// {@macro flutter.widgets.RawDialogRoute} /// /// ### State Restoration /// /// Using this method will not enable state restoration for the date picker. /// In order to enable state restoration for a date picker, use /// [Navigator.restorablePush] or [Navigator.restorablePushNamed] with /// [DatePickerDialog]. /// /// For more information about state restoration, see [RestorationManager]. /// /// {@macro flutter.widgets.RestorationManager} /// /// {@tool dartpad} /// This sample demonstrates how to create a restorable Material date picker. /// This is accomplished by enabling state restoration by specifying /// [MaterialApp.restorationScopeId] and using [Navigator.restorablePush] to /// push [DatePickerDialog] when the button is tapped. /// /// ** See code in examples/api/lib/material/date_picker/show_date_picker.0.dart ** /// {@end-tool} /// /// See also: /// /// * [showDateRangePicker], which shows a Material Design date range picker /// used to select a range of dates. /// * [CalendarDatePicker], which provides the calendar grid used by the date picker dialog. /// * [InputDatePickerFormField], which provides a text input field for entering dates. /// * [DisplayFeatureSubScreen], which documents the specifics of how /// [DisplayFeature]s can split the screen into sub-screens. /// * [showTimePicker], which shows a dialog that contains a Material Design time picker. /// Future showDatePicker({ required BuildContext context, required DateTime initialDate, required DateTime firstDate, required DateTime lastDate, DateTime? currentDate, DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar, SelectableDayPredicate? selectableDayPredicate, String? helpText, String? cancelText, String? confirmText, Locale? locale, bool useRootNavigator = true, RouteSettings? routeSettings, TextDirection? textDirection, TransitionBuilder? builder, DatePickerMode initialDatePickerMode = DatePickerMode.day, String? errorFormatText, String? errorInvalidText, String? fieldHintText, String? fieldLabelText, TextInputType? keyboardType, Offset? anchorPoint, }) async { assert(context != null); assert(initialDate != null); assert(firstDate != null); assert(lastDate != null); initialDate = DateUtils.dateOnly(initialDate); firstDate = DateUtils.dateOnly(firstDate); lastDate = DateUtils.dateOnly(lastDate); assert( !lastDate.isBefore(firstDate), 'lastDate $lastDate must be on or after firstDate $firstDate.', ); assert( !initialDate.isBefore(firstDate), 'initialDate $initialDate must be on or after firstDate $firstDate.', ); assert( !initialDate.isAfter(lastDate), 'initialDate $initialDate must be on or before lastDate $lastDate.', ); assert( selectableDayPredicate == null || selectableDayPredicate(initialDate), 'Provided initialDate $initialDate must satisfy provided selectableDayPredicate.', ); assert(initialEntryMode != null); assert(useRootNavigator != null); assert(initialDatePickerMode != null); assert(debugCheckHasMaterialLocalizations(context)); Widget dialog = DatePickerDialog( initialDate: initialDate, firstDate: firstDate, lastDate: lastDate, currentDate: currentDate, initialEntryMode: initialEntryMode, selectableDayPredicate: selectableDayPredicate, helpText: helpText, cancelText: cancelText, confirmText: confirmText, initialCalendarMode: initialDatePickerMode, errorFormatText: errorFormatText, errorInvalidText: errorInvalidText, fieldHintText: fieldHintText, fieldLabelText: fieldLabelText, keyboardType: keyboardType, ); if (textDirection != null) { dialog = Directionality( textDirection: textDirection, child: dialog, ); } if (locale != null) { dialog = Localizations.override( context: context, locale: locale, child: dialog, ); } return showDialog( context: context, useRootNavigator: useRootNavigator, routeSettings: routeSettings, builder: (BuildContext context) { return builder == null ? dialog : builder(context, dialog); }, anchorPoint: anchorPoint, ); } /// A Material-style date picker dialog. /// /// It is used internally by [showDatePicker] or can be directly pushed /// onto the [Navigator] stack to enable state restoration. See /// [showDatePicker] for a state restoration app example. /// /// See also: /// /// * [showDatePicker], which is a way to display the date picker. class DatePickerDialog extends StatefulWidget { /// A Material-style date picker dialog. DatePickerDialog({ super.key, required DateTime initialDate, required DateTime firstDate, required DateTime lastDate, DateTime? currentDate, this.initialEntryMode = DatePickerEntryMode.calendar, this.selectableDayPredicate, this.cancelText, this.confirmText, this.helpText, this.initialCalendarMode = DatePickerMode.day, this.errorFormatText, this.errorInvalidText, this.fieldHintText, this.fieldLabelText, this.keyboardType, this.restorationId, }) : assert(initialDate != null), assert(firstDate != null), assert(lastDate != null), initialDate = DateUtils.dateOnly(initialDate), firstDate = DateUtils.dateOnly(firstDate), lastDate = DateUtils.dateOnly(lastDate), currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()), assert(initialEntryMode != null), assert(initialCalendarMode != null) { assert( !this.lastDate.isBefore(this.firstDate), 'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.', ); assert( !this.initialDate.isBefore(this.firstDate), 'initialDate ${this.initialDate} must be on or after firstDate ${this.firstDate}.', ); assert( !this.initialDate.isAfter(this.lastDate), 'initialDate ${this.initialDate} must be on or before lastDate ${this.lastDate}.', ); assert( selectableDayPredicate == null || selectableDayPredicate!(this.initialDate), 'Provided initialDate ${this.initialDate} must satisfy provided selectableDayPredicate', ); } /// The initially selected [DateTime] that the picker should display. final DateTime initialDate; /// The earliest allowable [DateTime] that the user can select. final DateTime firstDate; /// The latest allowable [DateTime] that the user can select. final DateTime lastDate; /// The [DateTime] representing today. It will be highlighted in the day grid. final DateTime currentDate; /// The initial mode of date entry method for the date picker dialog. /// /// See [DatePickerEntryMode] for more details on the different data entry /// modes available. final DatePickerEntryMode initialEntryMode; /// Function to provide full control over which [DateTime] can be selected. final SelectableDayPredicate? selectableDayPredicate; /// The text that is displayed on the cancel button. final String? cancelText; /// The text that is displayed on the confirm button. final String? confirmText; /// The text that is displayed at the top of the header. /// /// This is used to indicate to the user what they are selecting a date for. final String? helpText; /// The initial display of the calendar picker. final DatePickerMode initialCalendarMode; /// The error text displayed if the entered date is not in the correct format. final String? errorFormatText; /// The error text displayed if the date is not valid. /// /// A date is not valid if it is earlier than [firstDate], later than /// [lastDate], or doesn't pass the [selectableDayPredicate]. final String? errorInvalidText; /// The hint text displayed in the [TextField]. /// /// If this is null, it will default to the date format string. For example, /// 'mm/dd/yyyy' for en_US. final String? fieldHintText; /// The label text displayed in the [TextField]. /// /// If this is null, it will default to the words representing the date format /// string. For example, 'Month, Day, Year' for en_US. final String? fieldLabelText; /// The keyboard type of the [TextField]. /// /// If this is null, it will default to [TextInputType.datetime] final TextInputType? keyboardType; /// Restoration ID to save and restore the state of the [DatePickerDialog]. /// /// If it is non-null, the date picker will persist and restore the /// date selected on the dialog. /// /// The state of this widget is persisted in a [RestorationBucket] claimed /// from the surrounding [RestorationScope] using the provided restoration ID. /// /// See also: /// /// * [RestorationManager], which explains how state restoration works in /// Flutter. final String? restorationId; @override State createState() => _DatePickerDialogState(); } class _DatePickerDialogState extends State with RestorationMixin { late final RestorableDateTime _selectedDate = RestorableDateTime(widget.initialDate); late final _RestorableDatePickerEntryMode _entryMode = _RestorableDatePickerEntryMode(widget.initialEntryMode); final _RestorableAutovalidateMode _autovalidateMode = _RestorableAutovalidateMode(AutovalidateMode.disabled); @override String? get restorationId => widget.restorationId; @override void restoreState(RestorationBucket? oldBucket, bool initialRestore) { registerForRestoration(_selectedDate, 'selected_date'); registerForRestoration(_autovalidateMode, 'autovalidateMode'); registerForRestoration(_entryMode, 'calendar_entry_mode'); } final GlobalKey _calendarPickerKey = GlobalKey(); final GlobalKey _formKey = GlobalKey(); void _handleOk() { if (_entryMode.value == DatePickerEntryMode.input || _entryMode.value == DatePickerEntryMode.inputOnly) { final FormState form = _formKey.currentState!; if (!form.validate()) { setState(() => _autovalidateMode.value = AutovalidateMode.always); return; } form.save(); } Navigator.pop(context, _selectedDate.value); } void _handleCancel() { Navigator.pop(context); } void _handleEntryModeToggle() { setState(() { switch (_entryMode.value) { case DatePickerEntryMode.calendar: _autovalidateMode.value = AutovalidateMode.disabled; _entryMode.value = DatePickerEntryMode.input; break; case DatePickerEntryMode.input: _formKey.currentState!.save(); _entryMode.value = DatePickerEntryMode.calendar; break; case DatePickerEntryMode.calendarOnly: case DatePickerEntryMode.inputOnly: assert(false, 'Can not change entry mode from _entryMode'); break; } }); } void _handleDateChanged(DateTime date) { setState(() { _selectedDate.value = date; }); } Size _dialogSize(BuildContext context) { final Orientation orientation = MediaQuery.of(context).orientation; switch (_entryMode.value) { case DatePickerEntryMode.calendar: case DatePickerEntryMode.calendarOnly: switch (orientation) { case Orientation.portrait: return _calendarPortraitDialogSize; case Orientation.landscape: return _calendarLandscapeDialogSize; } case DatePickerEntryMode.input: case DatePickerEntryMode.inputOnly: switch (orientation) { case Orientation.portrait: return _inputPortraitDialogSize; case Orientation.landscape: return _inputLandscapeDialogSize; } } } static const Map _formShortcutMap = { // Pressing enter on the field will move focus to the next field or control. SingleActivator(LogicalKeyboardKey.enter): NextFocusIntent(), }; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; final MaterialLocalizations localizations = MaterialLocalizations.of(context); final Orientation orientation = MediaQuery.of(context).orientation; final TextTheme textTheme = theme.textTheme; // Constrain the textScaleFactor to the largest supported value to prevent // layout issues. final double textScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 1.3); final String dateText = localizations.formatMediumDate(_selectedDate.value); final Color onPrimarySurface = colorScheme.brightness == Brightness.light ? colorScheme.onPrimary : colorScheme.onSurface; final TextStyle? dateStyle = orientation == Orientation.landscape ? textTheme.headlineMedium?.copyWith(color: onPrimarySurface) : textTheme.headlineMedium?.copyWith(color: onPrimarySurface); final Widget actions = Container( alignment: AlignmentDirectional.centerEnd, constraints: const BoxConstraints(minHeight: 52.0), padding: const EdgeInsets.symmetric(horizontal: 8), child: OverflowBar( spacing: 8, children: [ TextButton( onPressed: _handleCancel, child: Text(widget.cancelText ?? localizations.cancelButtonLabel), ), TextButton( onPressed: _handleOk, child: Text(widget.confirmText ?? localizations.okButtonLabel), ), ], ), ); CalendarDatePicker calendarDatePicker() { return CalendarDatePicker( key: _calendarPickerKey, initialDate: _selectedDate.value, firstDate: widget.firstDate, lastDate: widget.lastDate, currentDate: widget.currentDate, onDateChanged: _handleDateChanged, selectableDayPredicate: widget.selectableDayPredicate, initialCalendarMode: widget.initialCalendarMode, ); } Form inputDatePicker() { return Form( key: _formKey, autovalidateMode: _autovalidateMode.value, child: Container( padding: const EdgeInsets.symmetric(horizontal: 24), height: orientation == Orientation.portrait ? _inputFormPortraitHeight : _inputFormLandscapeHeight, child: Shortcuts( shortcuts: _formShortcutMap, child: Column( children: [ const Spacer(), InputDatePickerFormField( initialDate: _selectedDate.value, firstDate: widget.firstDate, lastDate: widget.lastDate, onDateSubmitted: _handleDateChanged, onDateSaved: _handleDateChanged, selectableDayPredicate: widget.selectableDayPredicate, errorFormatText: widget.errorFormatText, errorInvalidText: widget.errorInvalidText, fieldHintText: widget.fieldHintText, fieldLabelText: widget.fieldLabelText, keyboardType: widget.keyboardType, autofocus: true, ), const Spacer(), ], ), ), ), ); } final Widget picker; final Widget? entryModeButton; switch (_entryMode.value) { case DatePickerEntryMode.calendar: picker = calendarDatePicker(); entryModeButton = IconButton( icon: const Icon(Icons.edit), color: onPrimarySurface, tooltip: localizations.inputDateModeButtonLabel, onPressed: _handleEntryModeToggle, ); break; case DatePickerEntryMode.calendarOnly: picker = calendarDatePicker(); entryModeButton = null; break; case DatePickerEntryMode.input: picker = inputDatePicker(); entryModeButton = IconButton( icon: const Icon(Icons.calendar_today), color: onPrimarySurface, tooltip: localizations.calendarModeButtonLabel, onPressed: _handleEntryModeToggle, ); break; case DatePickerEntryMode.inputOnly: picker = inputDatePicker(); entryModeButton = null; break; } final Widget header = _DatePickerHeader( helpText: widget.helpText ?? localizations.datePickerHelpText, titleText: dateText, titleStyle: dateStyle, orientation: orientation, isShort: orientation == Orientation.landscape, entryModeButton: entryModeButton, ); final Size dialogSize = _dialogSize(context) * textScaleFactor; return Dialog( insetPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), clipBehavior: Clip.antiAlias, child: AnimatedContainer( width: dialogSize.width, height: dialogSize.height, duration: _dialogSizeAnimationDuration, curve: Curves.easeIn, child: MediaQuery( data: MediaQuery.of(context).copyWith( textScaleFactor: textScaleFactor, ), child: Builder(builder: (BuildContext context) { switch (orientation) { case Orientation.portrait: return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ header, Expanded(child: picker), actions, ], ); case Orientation.landscape: return Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ header, Flexible( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded(child: picker), actions, ], ), ), ], ); } }), ), ), ); } } // A restorable [DatePickerEntryMode] value. // // This serializes each entry as a unique `int` value. class _RestorableDatePickerEntryMode extends RestorableValue { _RestorableDatePickerEntryMode( DatePickerEntryMode defaultValue, ) : _defaultValue = defaultValue; final DatePickerEntryMode _defaultValue; @override DatePickerEntryMode createDefaultValue() => _defaultValue; @override void didUpdateValue(DatePickerEntryMode? oldValue) { assert(debugIsSerializableForRestoration(value.index)); notifyListeners(); } @override DatePickerEntryMode fromPrimitives(Object? data) => DatePickerEntryMode.values[data! as int]; @override Object? toPrimitives() => value.index; } // A restorable [AutovalidateMode] value. // // This serializes each entry as a unique `int` value. class _RestorableAutovalidateMode extends RestorableValue { _RestorableAutovalidateMode( AutovalidateMode defaultValue, ) : _defaultValue = defaultValue; final AutovalidateMode _defaultValue; @override AutovalidateMode createDefaultValue() => _defaultValue; @override void didUpdateValue(AutovalidateMode? oldValue) { assert(debugIsSerializableForRestoration(value.index)); notifyListeners(); } @override AutovalidateMode fromPrimitives(Object? data) => AutovalidateMode.values[data! as int]; @override Object? toPrimitives() => value.index; } /// Re-usable widget that displays the selected date (in large font) and the /// help text above it. /// /// These types include: /// /// * Single Date picker with calendar mode. /// * Single Date picker with text input mode. /// * Date Range picker with text input mode. /// /// [helpText], [orientation], [icon], [onIconPressed] are required and must be /// non-null. class _DatePickerHeader extends StatelessWidget { /// Creates a header for use in a date picker dialog. const _DatePickerHeader({ required this.helpText, required this.titleText, this.titleSemanticsLabel, required this.titleStyle, required this.orientation, this.isShort = false, this.entryModeButton, }) : assert(helpText != null), assert(orientation != null), assert(isShort != null); static const double _datePickerHeaderLandscapeWidth = 152.0; static const double _datePickerHeaderPortraitHeight = 120.0; static const double _headerPaddingLandscape = 16.0; /// The text that is displayed at the top of the header. /// /// This is used to indicate to the user what they are selecting a date for. final String helpText; /// The text that is displayed at the center of the header. final String titleText; /// The semantic label associated with the [titleText]. final String? titleSemanticsLabel; /// The [TextStyle] that the title text is displayed with. final TextStyle? titleStyle; /// The orientation is used to decide how to layout its children. final Orientation orientation; /// Indicates the header is being displayed in a shorter/narrower context. /// /// This will be used to tighten up the space between the help text and date /// text if `true`. Additionally, it will use a smaller typography style if /// `true`. /// /// This is necessary for displaying the manual input mode in /// landscape orientation, in order to account for the keyboard height. final bool isShort; final Widget? entryModeButton; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; final TextTheme textTheme = theme.textTheme; // The header should use the primary color in light themes and surface color in dark final bool isDark = colorScheme.brightness == Brightness.dark; final Color primarySurfaceColor = isDark ? colorScheme.surface : colorScheme.primary; final Color onPrimarySurfaceColor = isDark ? colorScheme.onSurface : colorScheme.onPrimary; final TextStyle? helpStyle = textTheme.headlineMedium?.copyWith( color: onPrimarySurfaceColor, ); final Text help = Text( helpText, style: helpStyle, maxLines: 1, overflow: TextOverflow.ellipsis, ); final Text title = Text( titleText, semanticsLabel: titleSemanticsLabel ?? titleText, style: titleStyle, maxLines: orientation == Orientation.portrait ? 1 : 2, overflow: TextOverflow.ellipsis, ); switch (orientation) { case Orientation.portrait: return SizedBox( height: _datePickerHeaderPortraitHeight, child: Material( color: primarySurfaceColor, child: Padding( padding: const EdgeInsetsDirectional.only( start: 24, end: 12, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), help, const Flexible(child: SizedBox(height: 38)), Row( children: [ Expanded(child: title), if (entryModeButton != null) entryModeButton!, ], ), ], ), ), ), ); case Orientation.landscape: return SizedBox( width: _datePickerHeaderLandscapeWidth, child: Material( color: primarySurfaceColor, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric( horizontal: _headerPaddingLandscape, ), child: help, ), SizedBox(height: isShort ? 16 : 56), Expanded( child: Padding( padding: const EdgeInsets.symmetric( horizontal: _headerPaddingLandscape, ), child: title, ), ), if (entryModeButton != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: entryModeButton, ), ], ), ), ); } } } /// Shows a full screen modal dialog containing a Material Design date range /// picker. /// /// The returned [Future] resolves to the [DateTimeRange] selected by the user /// when the user saves their selection. If the user cancels the dialog, null is /// returned. /// /// If [initialDateRange] is non-null, then it will be used as the initially /// selected date range. If it is provided, `initialDateRange.start` must be /// before or on `initialDateRange.end`. /// /// The [firstDate] is the earliest allowable date. The [lastDate] is the latest /// allowable date. Both must be non-null. /// /// If an initial date range is provided, `initialDateRange.start` /// and `initialDateRange.end` must both fall between or on [firstDate] and /// [lastDate]. For all of these [DateTime] values, only their dates are /// considered. Their time fields are ignored. /// /// The [currentDate] represents the current day (i.e. today). This /// date will be highlighted in the day grid. If null, the date of /// `DateTime.now()` will be used. /// /// An optional [initialEntryMode] argument can be used to display the date /// picker in the [DatePickerEntryMode.calendar] (a scrollable calendar month /// grid) or [DatePickerEntryMode.input] (two text input fields) mode. /// It defaults to [DatePickerEntryMode.calendar] and must be non-null. /// /// The following optional string parameters allow you to override the default /// text used for various parts of the dialog: /// /// * [helpText], the label displayed at the top of the dialog. /// * [cancelText], the label on the cancel button for the text input mode. /// * [confirmText],the label on the ok button for the text input mode. /// * [saveText], the label on the save button for the fullscreen calendar /// mode. /// * [errorFormatText], the message used when an input text isn't in a proper /// date format. /// * [errorInvalidText], the message used when an input text isn't a /// selectable date. /// * [errorInvalidRangeText], the message used when the date range is /// invalid (e.g. start date is after end date). /// * [fieldStartHintText], the text used to prompt the user when no text has /// been entered in the start field. /// * [fieldEndHintText], the text used to prompt the user when no text has /// been entered in the end field. /// * [fieldStartLabelText], the label for the start date text input field. /// * [fieldEndLabelText], the label for the end date text input field. /// /// An optional [locale] argument can be used to set the locale for the date /// picker. It defaults to the ambient locale provided by [Localizations]. /// /// An optional [textDirection] argument can be used to set the text direction /// ([TextDirection.ltr] or [TextDirection.rtl]) for the date picker. It /// defaults to the ambient text direction provided by [Directionality]. If both /// [locale] and [textDirection] are non-null, [textDirection] overrides the /// direction chosen for the [locale]. /// /// The [context], [useRootNavigator] and [routeSettings] arguments are passed /// to [showDialog], the documentation for which discusses how it is used. /// [context] and [useRootNavigator] must be non-null. /// /// The [builder] parameter can be used to wrap the dialog widget /// to add inherited widgets like [Theme]. /// /// {@macro flutter.widgets.RawDialogRoute} /// /// ### State Restoration /// /// Using this method will not enable state restoration for the date range picker. /// In order to enable state restoration for a date range picker, use /// [Navigator.restorablePush] or [Navigator.restorablePushNamed] with /// [DateRangePickerDialog]. /// /// For more information about state restoration, see [RestorationManager]. /// /// {@macro flutter.widgets.RestorationManager} /// /// {@tool sample} /// This sample demonstrates how to create a restorable Material date range picker. /// This is accomplished by enabling state restoration by specifying /// [MaterialApp.restorationScopeId] and using [Navigator.restorablePush] to /// push [DateRangePickerDialog] when the button is tapped. /// /// ** See code in examples/api/lib/material/date_picker/show_date_range_picker.0.dart ** /// {@end-tool} /// /// See also: /// /// * [showDatePicker], which shows a Material Design date picker used to /// select a single date. /// * [DateTimeRange], which is used to describe a date range. /// * [DisplayFeatureSubScreen], which documents the specifics of how /// [DisplayFeature]s can split the screen into sub-screens. Future showDateRangePicker({ required BuildContext context, DateTimeRange? initialDateRange, required DateTime firstDate, required DateTime lastDate, DateTime? currentDate, DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar, String? helpText, String? cancelText, String? confirmText, String? saveText, String? errorFormatText, String? errorInvalidText, String? errorInvalidRangeText, String? fieldStartHintText, String? fieldEndHintText, String? fieldStartLabelText, String? fieldEndLabelText, Locale? locale, bool useRootNavigator = true, RouteSettings? routeSettings, TextDirection? textDirection, TransitionBuilder? builder, Offset? anchorPoint, }) async { assert(context != null); assert( initialDateRange == null || (initialDateRange.start != null && initialDateRange.end != null), 'initialDateRange must be null or have non-null start and end dates.', ); assert( initialDateRange == null || !initialDateRange.start.isAfter(initialDateRange.end), "initialDateRange's start date must not be after it's end date.", ); initialDateRange = initialDateRange == null ? null : DateUtils.datesOnly(initialDateRange); assert(firstDate != null); firstDate = DateUtils.dateOnly(firstDate); assert(lastDate != null); lastDate = DateUtils.dateOnly(lastDate); assert( !lastDate.isBefore(firstDate), 'lastDate $lastDate must be on or after firstDate $firstDate.', ); assert( initialDateRange == null || !initialDateRange.start.isBefore(firstDate), "initialDateRange's start date must be on or after firstDate $firstDate.", ); assert( initialDateRange == null || !initialDateRange.end.isBefore(firstDate), "initialDateRange's end date must be on or after firstDate $firstDate.", ); assert( initialDateRange == null || !initialDateRange.start.isAfter(lastDate), "initialDateRange's start date must be on or before lastDate $lastDate.", ); assert( initialDateRange == null || !initialDateRange.end.isAfter(lastDate), "initialDateRange's end date must be on or before lastDate $lastDate.", ); currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()); assert(initialEntryMode != null); assert(useRootNavigator != null); assert(debugCheckHasMaterialLocalizations(context)); Widget dialog = DateRangePickerDialog( initialDateRange: initialDateRange, firstDate: firstDate, lastDate: lastDate, currentDate: currentDate, initialEntryMode: initialEntryMode, helpText: helpText, cancelText: cancelText, confirmText: confirmText, saveText: saveText, errorFormatText: errorFormatText, errorInvalidText: errorInvalidText, errorInvalidRangeText: errorInvalidRangeText, fieldStartHintText: fieldStartHintText, fieldEndHintText: fieldEndHintText, fieldStartLabelText: fieldStartLabelText, fieldEndLabelText: fieldEndLabelText, ); if (textDirection != null) { dialog = Directionality( textDirection: textDirection, child: dialog, ); } if (locale != null) { dialog = Localizations.override( context: context, locale: locale, child: dialog, ); } return showDialog( context: context, useRootNavigator: useRootNavigator, routeSettings: routeSettings, useSafeArea: false, builder: (BuildContext context) { return builder == null ? dialog : builder(context, dialog); }, anchorPoint: anchorPoint, ); } /// Returns a locale-appropriate string to describe the start of a date range. /// /// If `startDate` is null, then it defaults to 'Start Date', otherwise if it /// is in the same year as the `endDate` then it will use the short month /// day format (i.e. 'Jan 21'). Otherwise it will return the short date format /// (i.e. 'Jan 21, 2020'). String _formatRangeStartDate(MaterialLocalizations localizations, DateTime? startDate, DateTime? endDate) { return startDate == null ? localizations.dateRangeStartLabel : (endDate == null || startDate.year == endDate.year) ? localizations.formatShortMonthDay(startDate) : localizations.formatShortDate(startDate); } /// Returns an locale-appropriate string to describe the end of a date range. /// /// If `endDate` is null, then it defaults to 'End Date', otherwise if it /// is in the same year as the `startDate` and the `currentDate` then it will /// just use the short month day format (i.e. 'Jan 21'), otherwise it will /// include the year (i.e. 'Jan 21, 2020'). String _formatRangeEndDate(MaterialLocalizations localizations, DateTime? startDate, DateTime? endDate, DateTime currentDate) { return endDate == null ? localizations.dateRangeEndLabel : (startDate != null && startDate.year == endDate.year && startDate.year == currentDate.year) ? localizations.formatShortMonthDay(endDate) : localizations.formatShortDate(endDate); } /// A Material-style date range picker dialog. /// /// It is used internally by [showDateRangePicker] or can be directly pushed /// onto the [Navigator] stack to enable state restoration. See /// [showDateRangePicker] for a state restoration app example. /// /// See also: /// /// * [showDateRangePicker], which is a way to display the date picker. class DateRangePickerDialog extends StatefulWidget { /// A Material-style date range picker dialog. const DateRangePickerDialog({ super.key, this.initialDateRange, required this.firstDate, required this.lastDate, this.currentDate, this.initialEntryMode = DatePickerEntryMode.calendar, this.helpText, this.cancelText, this.confirmText, this.saveText, this.errorInvalidRangeText, this.errorFormatText, this.errorInvalidText, this.fieldStartHintText, this.fieldEndHintText, this.fieldStartLabelText, this.fieldEndLabelText, this.restorationId, }); /// The date range that the date range picker starts with when it opens. /// /// If an initial date range is provided, `initialDateRange.start` /// and `initialDateRange.end` must both fall between or on [firstDate] and /// [lastDate]. For all of these [DateTime] values, only their dates are /// considered. Their time fields are ignored. /// /// If [initialDateRange] is non-null, then it will be used as the initially /// selected date range. If it is provided, `initialDateRange.start` must be /// before or on `initialDateRange.end`. final DateTimeRange? initialDateRange; /// The earliest allowable date on the date range. final DateTime firstDate; /// The latest allowable date on the date range. final DateTime lastDate; /// The [currentDate] represents the current day (i.e. today). /// /// This date will be highlighted in the day grid. /// /// If `null`, the date of `DateTime.now()` will be used. final DateTime? currentDate; /// The initial date range picker entry mode. /// /// The date range has two main modes: [DatePickerEntryMode.calendar] (a /// scrollable calendar month grid) or [DatePickerEntryMode.input] (two text /// input fields) mode. /// /// It defaults to [DatePickerEntryMode.calendar] and must be non-null. final DatePickerEntryMode initialEntryMode; /// The label on the cancel button for the text input mode. /// /// If null, the localized value of /// [MaterialLocalizations.cancelButtonLabel] is used. final String? cancelText; /// The label on the "OK" button for the text input mode. /// /// If null, the localized value of /// [MaterialLocalizations.okButtonLabel] is used. final String? confirmText; /// The label on the save button for the fullscreen calendar mode. /// /// If null, the localized value of /// [MaterialLocalizations.saveButtonLabel] is used. final String? saveText; /// The label displayed at the top of the dialog. /// /// If null, the localized value of /// [MaterialLocalizations.dateRangePickerHelpText] is used. final String? helpText; /// The message used when the date range is invalid (e.g. start date is after /// end date). /// /// If null, the localized value of /// [MaterialLocalizations.invalidDateRangeLabel] is used. final String? errorInvalidRangeText; /// The message used when an input text isn't in a proper date format. /// /// If null, the localized value of /// [MaterialLocalizations.invalidDateFormatLabel] is used. final String? errorFormatText; /// The message used when an input text isn't a selectable date. /// /// If null, the localized value of /// [MaterialLocalizations.dateOutOfRangeLabel] is used. final String? errorInvalidText; /// The text used to prompt the user when no text has been entered in the /// start field. /// /// If null, the localized value of /// [MaterialLocalizations.dateHelpText] is used. final String? fieldStartHintText; /// The text used to prompt the user when no text has been entered in the /// end field. /// /// If null, the localized value of [MaterialLocalizations.dateHelpText] is /// used. final String? fieldEndHintText; /// The label for the start date text input field. /// /// If null, the localized value of [MaterialLocalizations.dateRangeStartLabel] /// is used. final String? fieldStartLabelText; /// The label for the end date text input field. /// /// If null, the localized value of [MaterialLocalizations.dateRangeEndLabel] /// is used. final String? fieldEndLabelText; /// Restoration ID to save and restore the state of the [DateRangePickerDialog]. /// /// If it is non-null, the date range picker will persist and restore the /// date range selected on the dialog. /// /// The state of this widget is persisted in a [RestorationBucket] claimed /// from the surrounding [RestorationScope] using the provided restoration ID. /// /// See also: /// /// * [RestorationManager], which explains how state restoration works in /// Flutter. final String? restorationId; @override State createState() => _DateRangePickerDialogState(); } class _DateRangePickerDialogState extends State with RestorationMixin { late final _RestorableDatePickerEntryMode _entryMode = _RestorableDatePickerEntryMode(widget.initialEntryMode); late final RestorableDateTimeN _selectedStart = RestorableDateTimeN(widget.initialDateRange?.start); late final RestorableDateTimeN _selectedEnd = RestorableDateTimeN(widget.initialDateRange?.end); final RestorableBool _autoValidate = RestorableBool(false); final GlobalKey _calendarPickerKey = GlobalKey(); final GlobalKey<_InputDateRangePickerState> _inputPickerKey = GlobalKey<_InputDateRangePickerState>(); @override String? get restorationId => widget.restorationId; @override void restoreState(RestorationBucket? oldBucket, bool initialRestore) { registerForRestoration(_entryMode, 'entry_mode'); registerForRestoration(_selectedStart, 'selected_start'); registerForRestoration(_selectedEnd, 'selected_end'); registerForRestoration(_autoValidate, 'autovalidate'); } void _handleOk() { if (_entryMode.value == DatePickerEntryMode.input || _entryMode.value == DatePickerEntryMode.inputOnly) { final _InputDateRangePickerState picker = _inputPickerKey.currentState!; if (!picker.validate()) { setState(() { _autoValidate.value = true; }); return; } } final DateTimeRange? selectedRange = _hasSelectedDateRange ? DateTimeRange(start: _selectedStart.value!, end: _selectedEnd.value!) : null; Navigator.pop(context, selectedRange); } void _handleCancel() { Navigator.pop(context); } void _handleEntryModeToggle() { setState(() { switch (_entryMode.value) { case DatePickerEntryMode.calendar: _autoValidate.value = false; _entryMode.value = DatePickerEntryMode.input; break; case DatePickerEntryMode.input: // Validate the range dates if (_selectedStart.value != null && (_selectedStart.value!.isBefore(widget.firstDate) || _selectedStart.value!.isAfter(widget.lastDate))) { _selectedStart.value = null; // With no valid start date, having an end date makes no sense for the UI. _selectedEnd.value = null; } if (_selectedEnd.value != null && (_selectedEnd.value!.isBefore(widget.firstDate) || _selectedEnd.value!.isAfter(widget.lastDate))) { _selectedEnd.value = null; } // If invalid range (start after end), then just use the start date if (_selectedStart.value != null && _selectedEnd.value != null && _selectedStart.value!.isAfter(_selectedEnd.value!)) { _selectedEnd.value = null; } _entryMode.value = DatePickerEntryMode.calendar; break; case DatePickerEntryMode.calendarOnly: case DatePickerEntryMode.inputOnly: assert(false, 'Can not change entry mode from $_entryMode'); break; } }); } void _handleStartDateChanged(DateTime? date) { setState(() => _selectedStart.value = date); } void _handleEndDateChanged(DateTime? date) { setState(() => _selectedEnd.value = date); } bool get _hasSelectedDateRange => _selectedStart.value != null && _selectedEnd.value != null; @override Widget build(BuildContext context) { final MediaQueryData mediaQuery = MediaQuery.of(context); final Orientation orientation = mediaQuery.orientation; final double textScaleFactor = math.min(mediaQuery.textScaleFactor, 1.3); final MaterialLocalizations localizations = MaterialLocalizations.of(context); final ColorScheme colors = Theme.of(context).colorScheme; final Color onPrimarySurface = colors.brightness == Brightness.light ? colors.onPrimary : colors.onSurface; final Widget contents; final Size size; ShapeBorder? shape; final double elevation; final EdgeInsets insetPadding; final bool showEntryModeButton = _entryMode.value == DatePickerEntryMode.calendar || _entryMode.value == DatePickerEntryMode.input; switch (_entryMode.value) { case DatePickerEntryMode.calendar: case DatePickerEntryMode.calendarOnly: contents = _CalendarRangePickerDialog( key: _calendarPickerKey, selectedStartDate: _selectedStart.value, selectedEndDate: _selectedEnd.value, firstDate: widget.firstDate, lastDate: widget.lastDate, currentDate: widget.currentDate, onStartDateChanged: _handleStartDateChanged, onEndDateChanged: _handleEndDateChanged, onConfirm: _hasSelectedDateRange ? _handleOk : null, onCancel: _handleCancel, entryModeButton: showEntryModeButton ? IconButton( icon: const Icon(Icons.edit), padding: EdgeInsets.zero, color: onPrimarySurface, tooltip: localizations.inputDateModeButtonLabel, onPressed: _handleEntryModeToggle, ) : null, confirmText: widget.saveText ?? localizations.saveButtonLabel, helpText: widget.helpText ?? localizations.dateRangePickerHelpText, ); size = mediaQuery.size; insetPadding = EdgeInsets.zero; shape = const RoundedRectangleBorder(); elevation = 0; break; case DatePickerEntryMode.input: case DatePickerEntryMode.inputOnly: contents = _InputDateRangePickerDialog( selectedStartDate: _selectedStart.value, selectedEndDate: _selectedEnd.value, currentDate: widget.currentDate, picker: Container( padding: const EdgeInsets.symmetric(horizontal: 24), height: orientation == Orientation.portrait ? _inputFormPortraitHeight : _inputFormLandscapeHeight, child: Column( children: [ const Spacer(), _InputDateRangePicker( key: _inputPickerKey, initialStartDate: _selectedStart.value, initialEndDate: _selectedEnd.value, firstDate: widget.firstDate, lastDate: widget.lastDate, onStartDateChanged: _handleStartDateChanged, onEndDateChanged: _handleEndDateChanged, autofocus: true, autovalidate: _autoValidate.value, helpText: widget.helpText, errorInvalidRangeText: widget.errorInvalidRangeText, errorFormatText: widget.errorFormatText, errorInvalidText: widget.errorInvalidText, fieldStartHintText: widget.fieldStartHintText, fieldEndHintText: widget.fieldEndHintText, fieldStartLabelText: widget.fieldStartLabelText, fieldEndLabelText: widget.fieldEndLabelText, ), const Spacer(), ], ), ), onConfirm: _handleOk, onCancel: _handleCancel, entryModeButton: showEntryModeButton ? IconButton( icon: const Icon(Icons.calendar_today), padding: EdgeInsets.zero, color: onPrimarySurface, tooltip: localizations.calendarModeButtonLabel, onPressed: _handleEntryModeToggle, ) : null, confirmText: widget.confirmText ?? localizations.okButtonLabel, cancelText: widget.cancelText ?? localizations.cancelButtonLabel, helpText: widget.helpText ?? localizations.dateRangePickerHelpText, ); final DialogThemeData dialogTheme = Theme.of(context).dialogTheme; size = orientation == Orientation.portrait ? _inputPortraitDialogSize : _inputRangeLandscapeDialogSize; insetPadding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0); shape = dialogTheme.shape; elevation = dialogTheme.elevation ?? 24; break; } return Dialog( insetPadding: insetPadding, shape: shape, elevation: elevation, clipBehavior: Clip.antiAlias, child: AnimatedContainer( width: size.width, height: size.height, duration: _dialogSizeAnimationDuration, curve: Curves.easeIn, child: MediaQuery( data: MediaQuery.of(context).copyWith( textScaleFactor: textScaleFactor, ), child: Builder(builder: (BuildContext context) { return contents; }), ), ), ); } } class _CalendarRangePickerDialog extends StatelessWidget { const _CalendarRangePickerDialog({ super.key, required this.selectedStartDate, required this.selectedEndDate, required this.firstDate, required this.lastDate, required this.currentDate, required this.onStartDateChanged, required this.onEndDateChanged, required this.onConfirm, required this.onCancel, required this.confirmText, required this.helpText, this.entryModeButton, }); final DateTime? selectedStartDate; final DateTime? selectedEndDate; final DateTime firstDate; final DateTime lastDate; final DateTime? currentDate; final ValueChanged onStartDateChanged; final ValueChanged onEndDateChanged; final VoidCallback? onConfirm; final VoidCallback? onCancel; final String confirmText; final String helpText; final Widget? entryModeButton; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; final MaterialLocalizations localizations = MaterialLocalizations.of(context); final Orientation orientation = MediaQuery.of(context).orientation; final TextTheme textTheme = theme.textTheme; final Color headerForeground = colorScheme.brightness == Brightness.light ? colorScheme.onPrimary : colorScheme.onSurface; final Color headerDisabledForeground = headerForeground.withOpacity(0.38); final String startDateText = _formatRangeStartDate( localizations, selectedStartDate, selectedEndDate); final String endDateText = _formatRangeEndDate( localizations, selectedStartDate, selectedEndDate, DateTime.now()); final TextStyle? headlineStyle = textTheme.headlineMedium; final TextStyle? startDateStyle = headlineStyle?.apply( color: selectedStartDate != null ? headerForeground : headerDisabledForeground, ); final TextStyle? endDateStyle = headlineStyle?.apply( color: selectedEndDate != null ? headerForeground : headerDisabledForeground, ); final TextStyle saveButtonStyle = textTheme.headlineMedium!.apply( color: onConfirm != null ? headerForeground : headerDisabledForeground, ); return SafeArea( top: false, left: false, right: false, child: Scaffold( appBar: AppBar( leading: CloseButton( onPressed: onCancel, ), actions: [ if (orientation == Orientation.landscape && entryModeButton != null) entryModeButton!, TextButton( onPressed: onConfirm, child: Text(confirmText, style: saveButtonStyle), ), const SizedBox(width: 8), ], bottom: PreferredSize( preferredSize: const Size(double.infinity, 64), child: Row(children: [ SizedBox( width: MediaQuery.of(context).size.width < 360 ? 42 : 72), Expanded( child: Semantics( label: '$helpText $startDateText to $endDateText', excludeSemantics: true, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( helpText, style: textTheme.headlineMedium!.apply( color: headerForeground, ), ), const SizedBox(height: 8), Row( children: [ Text( startDateText, style: startDateStyle, maxLines: 1, overflow: TextOverflow.ellipsis, ), Text( ' – ', style: startDateStyle, ), Flexible( child: Text( endDateText, style: endDateStyle, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 16), ], ), ), ), if (orientation == Orientation.portrait && entryModeButton != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: entryModeButton, ), ]), ), ), body: _CalendarDateRangePicker( initialStartDate: selectedStartDate, initialEndDate: selectedEndDate, firstDate: firstDate, lastDate: lastDate, currentDate: currentDate, onStartDateChanged: onStartDateChanged, onEndDateChanged: onEndDateChanged, ), ), ); } } const Duration _monthScrollDuration = Duration(milliseconds: 200); const double _monthItemHeaderHeight = 58.0; const double _monthItemFooterHeight = 12.0; const double _monthItemRowHeight = 42.0; const double _monthItemSpaceBetweenRows = 8.0; const double _horizontalPadding = 8.0; const double _maxCalendarWidthLandscape = 384.0; const double _maxCalendarWidthPortrait = 480.0; /// Displays a scrollable calendar grid that allows a user to select a range /// of dates. class _CalendarDateRangePicker extends StatefulWidget { /// Creates a scrollable calendar grid for picking date ranges. _CalendarDateRangePicker({ DateTime? initialStartDate, DateTime? initialEndDate, required DateTime firstDate, required DateTime lastDate, DateTime? currentDate, required this.onStartDateChanged, required this.onEndDateChanged, }) : initialStartDate = initialStartDate != null ? DateUtils.dateOnly(initialStartDate) : null, initialEndDate = initialEndDate != null ? DateUtils.dateOnly(initialEndDate) : null, assert(firstDate != null), assert(lastDate != null), firstDate = DateUtils.dateOnly(firstDate), lastDate = DateUtils.dateOnly(lastDate), currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()) { assert( this.initialStartDate == null || this.initialEndDate == null || !this.initialStartDate!.isAfter(initialEndDate!), 'initialStartDate must be on or before initialEndDate.', ); assert( !this.lastDate.isBefore(this.firstDate), 'firstDate must be on or before lastDate.', ); } /// The [DateTime] that represents the start of the initial date range selection. final DateTime? initialStartDate; /// The [DateTime] that represents the end of the initial date range selection. final DateTime? initialEndDate; /// The earliest allowable [DateTime] that the user can select. final DateTime firstDate; /// The latest allowable [DateTime] that the user can select. final DateTime lastDate; /// The [DateTime] representing today. It will be highlighted in the day grid. final DateTime currentDate; /// Called when the user changes the start date of the selected range. final ValueChanged? onStartDateChanged; /// Called when the user changes the end date of the selected range. final ValueChanged? onEndDateChanged; @override _CalendarDateRangePickerState createState() => _CalendarDateRangePickerState(); } class _CalendarDateRangePickerState extends State<_CalendarDateRangePicker> { final GlobalKey _scrollViewKey = GlobalKey(); DateTime? _startDate; DateTime? _endDate; int _initialMonthIndex = 0; late ScrollController _controller; late bool _showWeekBottomDivider; @override void initState() { super.initState(); _controller = ScrollController(); _controller.addListener(_scrollListener); _startDate = widget.initialStartDate; _endDate = widget.initialEndDate; // Calculate the index for the initially displayed month. This is needed to // divide the list of months into two `SliverList`s. final DateTime initialDate = widget.initialStartDate ?? widget.currentDate; if (!initialDate.isBefore(widget.firstDate) && !initialDate.isAfter(widget.lastDate)) { _initialMonthIndex = DateUtils.monthDelta(widget.firstDate, initialDate); } _showWeekBottomDivider = _initialMonthIndex != 0; } @override void dispose() { _controller.dispose(); super.dispose(); } void _scrollListener() { if (_controller.offset <= _controller.position.minScrollExtent) { setState(() { _showWeekBottomDivider = false; }); } else if (!_showWeekBottomDivider) { setState(() { _showWeekBottomDivider = true; }); } } int get _numberOfMonths => DateUtils.monthDelta(widget.firstDate, widget.lastDate) + 1; void _vibrate() { switch (Theme.of(context).platform) { case TargetPlatform.android: case TargetPlatform.fuchsia: HapticFeedback.vibrate(); break; case TargetPlatform.iOS: case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: break; } } // This updates the selected date range using this logic: // // * From the unselected state, selecting one date creates the start date. // * If the next selection is before the start date, reset date range and // set the start date to that selection. // * If the next selection is on or after the start date, set the end date // to that selection. // * After both start and end dates are selected, any subsequent selection // resets the date range and sets start date to that selection. void _updateSelection(DateTime date) { _vibrate(); setState(() { if (_startDate != null && _endDate == null && !date.isBefore(_startDate!)) { _endDate = date; widget.onEndDateChanged?.call(_endDate); } else { _startDate = date; widget.onStartDateChanged?.call(_startDate!); if (_endDate != null) { _endDate = null; widget.onEndDateChanged?.call(_endDate); } } }); } Widget _buildMonthItem( BuildContext context, int index, bool beforeInitialMonth) { final int monthIndex = beforeInitialMonth ? _initialMonthIndex - index - 1 : _initialMonthIndex + index; final DateTime month = DateUtils.addMonthsToMonthDate(widget.firstDate, monthIndex); return Stack( alignment: Alignment.center, children: [ Text( "${month.month}", style: TextStyle(fontSize: 200, color: Colors.grey.withOpacity(0.1)), ), _MonthItem( selectedDateStart: _startDate, selectedDateEnd: _endDate, currentDate: widget.currentDate, firstDate: widget.firstDate, lastDate: widget.lastDate, displayedMonth: month, onChanged: _updateSelection, ), ], ); } @override Widget build(BuildContext context) { const Key sliverAfterKey = Key('sliverAfterKey'); return Column( children: [ const _DayHeaders(), if (_showWeekBottomDivider) const Divider(height: 0), Expanded( child: _CalendarKeyboardNavigator( firstDate: widget.firstDate, lastDate: widget.lastDate, initialFocusedDay: _startDate ?? widget.initialStartDate ?? widget.currentDate, // In order to prevent performance issues when displaying the // correct initial month, 2 `SliverList`s are used to split the // months. The first item in the second SliverList is the initial // month to be displayed. child: CustomScrollView( key: _scrollViewKey, controller: _controller, center: sliverAfterKey, slivers: [ SliverList( delegate: SliverChildBuilderDelegate( (BuildContext context, int index) => _buildMonthItem(context, index, true), childCount: _initialMonthIndex, ), ), SliverList( key: sliverAfterKey, delegate: SliverChildBuilderDelegate( (BuildContext context, int index) => _buildMonthItem(context, index, false), childCount: _numberOfMonths - _initialMonthIndex, ), ), ], ), ), ), ], ); } } class _CalendarKeyboardNavigator extends StatefulWidget { const _CalendarKeyboardNavigator({ required this.child, required this.firstDate, required this.lastDate, required this.initialFocusedDay, }); final Widget child; final DateTime firstDate; final DateTime lastDate; final DateTime initialFocusedDay; @override _CalendarKeyboardNavigatorState createState() => _CalendarKeyboardNavigatorState(); } class _CalendarKeyboardNavigatorState extends State<_CalendarKeyboardNavigator> { final Map _shortcutMap = const { SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left), SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent(TraversalDirection.right), SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down), SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up), }; late Map> _actionMap; late FocusNode _dayGridFocus; TraversalDirection? _dayTraversalDirection; DateTime? _focusedDay; @override void initState() { super.initState(); _actionMap = >{ NextFocusIntent: CallbackAction(onInvoke: _handleGridNextFocus), PreviousFocusIntent: CallbackAction( onInvoke: _handleGridPreviousFocus), DirectionalFocusIntent: CallbackAction( onInvoke: _handleDirectionFocus), }; _dayGridFocus = FocusNode(debugLabel: 'Day Grid'); } @override void dispose() { _dayGridFocus.dispose(); super.dispose(); } void _handleGridFocusChange(bool focused) { setState(() { if (focused) { _focusedDay ??= widget.initialFocusedDay; } }); } /// Move focus to the next element after the day grid. void _handleGridNextFocus(NextFocusIntent intent) { _dayGridFocus.requestFocus(); _dayGridFocus.nextFocus(); } /// Move focus to the previous element before the day grid. void _handleGridPreviousFocus(PreviousFocusIntent intent) { _dayGridFocus.requestFocus(); _dayGridFocus.previousFocus(); } /// Move the internal focus date in the direction of the given intent. /// /// This will attempt to move the focused day to the next selectable day in /// the given direction. If the new date is not in the current month, then /// the page view will be scrolled to show the new date's month. /// /// For horizontal directions, it will move forward or backward a day (depending /// on the current [TextDirection]). For vertical directions it will move up and /// down a week at a time. void _handleDirectionFocus(DirectionalFocusIntent intent) { assert(_focusedDay != null); setState(() { final DateTime? nextDate = _nextDateInDirection(_focusedDay!, intent.direction); if (nextDate != null) { _focusedDay = nextDate; _dayTraversalDirection = intent.direction; } }); } static const Map _directionOffset = { TraversalDirection.up: -DateTime.daysPerWeek, TraversalDirection.right: 1, TraversalDirection.down: DateTime.daysPerWeek, TraversalDirection.left: -1, }; int _dayDirectionOffset( TraversalDirection traversalDirection, TextDirection textDirection) { // Swap left and right if the text direction if RTL if (textDirection == TextDirection.rtl) { if (traversalDirection == TraversalDirection.left) { traversalDirection = TraversalDirection.right; } else if (traversalDirection == TraversalDirection.right) { traversalDirection = TraversalDirection.left; } } return _directionOffset[traversalDirection]!; } DateTime? _nextDateInDirection(DateTime date, TraversalDirection direction) { final TextDirection textDirection = Directionality.of(context); final DateTime nextDate = DateUtils.addDaysToDate( date, _dayDirectionOffset(direction, textDirection)); if (!nextDate.isBefore(widget.firstDate) && !nextDate.isAfter(widget.lastDate)) { return nextDate; } return null; } @override Widget build(BuildContext context) { return FocusableActionDetector( shortcuts: _shortcutMap, actions: _actionMap, focusNode: _dayGridFocus, onFocusChange: _handleGridFocusChange, child: _FocusedDate( date: _dayGridFocus.hasFocus ? _focusedDay : null, scrollDirection: _dayGridFocus.hasFocus ? _dayTraversalDirection : null, child: widget.child, ), ); } } /// InheritedWidget indicating what the current focused date is for its children. /// /// This is used by the [_MonthPicker] to let its children [_DayPicker]s know /// what the currently focused date (if any) should be. class _FocusedDate extends InheritedWidget { const _FocusedDate({ required super.child, this.date, this.scrollDirection, }); final DateTime? date; final TraversalDirection? scrollDirection; @override bool updateShouldNotify(_FocusedDate oldWidget) { return !DateUtils.isSameDay(date, oldWidget.date) || scrollDirection != oldWidget.scrollDirection; } static _FocusedDate? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<_FocusedDate>(); } } class _DayHeaders extends StatelessWidget { const _DayHeaders(); /// Builds widgets showing abbreviated days of week. The first widget in the /// returned list corresponds to the first day of week for the current locale. /// /// Examples: /// /// ``` /// ┌ Sunday is the first day of week in the US (en_US) /// | /// S M T W T F S <-- the returned list contains these widgets /// _ _ _ _ _ 1 2 /// 3 4 5 6 7 8 9 /// /// ┌ But it's Monday in the UK (en_GB) /// | /// M T W T F S S <-- the returned list contains these widgets /// _ _ _ _ 1 2 3 /// 4 5 6 7 8 9 10 /// ``` List _getDayHeaders( TextStyle headerStyle, MaterialLocalizations localizations) { final List result = []; for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) { final String weekday = localizations.narrowWeekdays[i]; result.add(ExcludeSemantics( child: Center(child: Text(weekday, style: headerStyle)), )); if (i == (localizations.firstDayOfWeekIndex - 1) % 7) { break; } } return result; } @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); final ColorScheme colorScheme = themeData.colorScheme; final TextStyle textStyle = themeData.textTheme.headlineMedium!.apply(color: colorScheme.onSurface); final MaterialLocalizations localizations = MaterialLocalizations.of(context); final List labels = _getDayHeaders(textStyle, localizations); // Add leading and trailing containers for edges of the custom grid layout. labels.insert(0, Container()); labels.add(Container()); return Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).orientation == Orientation.landscape ? _maxCalendarWidthLandscape : _maxCalendarWidthPortrait, maxHeight: _monthItemRowHeight, ), child: GridView.custom( shrinkWrap: true, gridDelegate: _monthItemGridDelegate, childrenDelegate: SliverChildListDelegate( labels, addRepaintBoundaries: false, ), ), ); } } class _MonthItemGridDelegate extends SliverGridDelegate { const _MonthItemGridDelegate(); @override SliverGridLayout getLayout(SliverConstraints constraints) { final double tileWidth = (constraints.crossAxisExtent - 2 * _horizontalPadding) / DateTime.daysPerWeek; return _MonthSliverGridLayout( crossAxisCount: DateTime.daysPerWeek + 2, dayChildWidth: tileWidth, edgeChildWidth: _horizontalPadding, reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), ); } @override bool shouldRelayout(_MonthItemGridDelegate oldDelegate) => false; } const _MonthItemGridDelegate _monthItemGridDelegate = _MonthItemGridDelegate(); class _MonthSliverGridLayout extends SliverGridLayout { /// Creates a layout that uses equally sized and spaced tiles for each day of /// the week and an additional edge tile for padding at the start and end of /// each row. /// /// This is necessary to facilitate the painting of the range highlight /// correctly. const _MonthSliverGridLayout({ required this.crossAxisCount, required this.dayChildWidth, required this.edgeChildWidth, required this.reverseCrossAxis, }) : assert(crossAxisCount != null && crossAxisCount > 0), assert(dayChildWidth != null && dayChildWidth >= 0), assert(edgeChildWidth != null && edgeChildWidth >= 0), assert(reverseCrossAxis != null); /// The number of children in the cross axis. final int crossAxisCount; /// The width in logical pixels of the day child widgets. final double dayChildWidth; /// The width in logical pixels of the edge child widgets. final double edgeChildWidth; /// Whether the children should be placed in the opposite order of increasing /// coordinates in the cross axis. /// /// For example, if the cross axis is horizontal, the children are placed from /// left to right when [reverseCrossAxis] is false and from right to left when /// [reverseCrossAxis] is true. /// /// Typically set to the return value of [axisDirectionIsReversed] applied to /// the [SliverConstraints.crossAxisDirection]. final bool reverseCrossAxis; /// The number of logical pixels from the leading edge of one row to the /// leading edge of the next row. double get _rowHeight { return _monthItemRowHeight + _monthItemSpaceBetweenRows; } /// The height in logical pixels of the children widgets. double get _childHeight { return _monthItemRowHeight; } @override int getMinChildIndexForScrollOffset(double scrollOffset) { return crossAxisCount * (scrollOffset ~/ _rowHeight); } @override int getMaxChildIndexForScrollOffset(double scrollOffset) { final int mainAxisCount = (scrollOffset / _rowHeight).ceil(); return math.max(0, crossAxisCount * mainAxisCount - 1); } double _getCrossAxisOffset(double crossAxisStart, bool isPadding) { if (reverseCrossAxis) { return ((crossAxisCount - 2) * dayChildWidth + 2 * edgeChildWidth) - crossAxisStart - (isPadding ? edgeChildWidth : dayChildWidth); } return crossAxisStart; } @override SliverGridGeometry getGeometryForChildIndex(int index) { final int adjustedIndex = index % crossAxisCount; final bool isEdge = adjustedIndex == 0 || adjustedIndex == crossAxisCount - 1; final double crossAxisStart = math.max(0, (adjustedIndex - 1) * dayChildWidth + edgeChildWidth); return SliverGridGeometry( scrollOffset: (index ~/ crossAxisCount) * _rowHeight, crossAxisOffset: _getCrossAxisOffset(crossAxisStart, isEdge), mainAxisExtent: _childHeight, crossAxisExtent: isEdge ? edgeChildWidth : dayChildWidth, ); } @override double computeMaxScrollOffset(int childCount) { assert(childCount >= 0); final int mainAxisCount = ((childCount - 1) ~/ crossAxisCount) + 1; final double mainAxisSpacing = _rowHeight - _childHeight; return _rowHeight * mainAxisCount - mainAxisSpacing; } } /// Displays the days of a given month and allows choosing a date range. /// /// The days are arranged in a rectangular grid with one column for each day of /// the week. class _MonthItem extends StatefulWidget { /// Creates a month item. _MonthItem({ required this.selectedDateStart, required this.selectedDateEnd, required this.currentDate, required this.onChanged, required this.firstDate, required this.lastDate, required this.displayedMonth, this.dragStartBehavior = DragStartBehavior.start, }) : assert(firstDate != null), assert(lastDate != null), assert(!firstDate.isAfter(lastDate)), assert(selectedDateStart == null || !selectedDateStart.isBefore(firstDate)), assert(selectedDateEnd == null || !selectedDateEnd.isBefore(firstDate)), assert( selectedDateStart == null || !selectedDateStart.isAfter(lastDate)), assert(selectedDateEnd == null || !selectedDateEnd.isAfter(lastDate)), assert(selectedDateStart == null || selectedDateEnd == null || !selectedDateStart.isAfter(selectedDateEnd)), assert(currentDate != null), assert(onChanged != null), assert(displayedMonth != null), assert(dragStartBehavior != null); /// The currently selected start date. /// /// This date is highlighted in the picker. final DateTime? selectedDateStart; /// The currently selected end date. /// /// This date is highlighted in the picker. final DateTime? selectedDateEnd; /// The current date at the time the picker is displayed. final DateTime currentDate; /// Called when the user picks a day. final ValueChanged onChanged; /// The earliest date the user is permitted to pick. final DateTime firstDate; /// The latest date the user is permitted to pick. final DateTime lastDate; /// The month whose days are displayed by this picker. final DateTime displayedMonth; /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], the drag gesture used to scroll a /// date picker wheel will begin at the position where the drag gesture won /// the arena. If set to [DragStartBehavior.down] it will begin at the position /// where a down event is first detected. /// /// In general, setting this to [DragStartBehavior.start] will make drag /// animation smoother and setting it to [DragStartBehavior.down] will make /// drag behavior feel slightly more reactive. /// /// By default, the drag start behavior is [DragStartBehavior.start]. /// /// See also: /// /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for /// the different behaviors. final DragStartBehavior dragStartBehavior; @override _MonthItemState createState() => _MonthItemState(); } class _MonthItemState extends State<_MonthItem> { /// List of [FocusNode]s, one for each day of the month. late List _dayFocusNodes; @override void initState() { super.initState(); final int daysInMonth = DateUtils.getDaysInMonth( widget.displayedMonth.year, widget.displayedMonth.month); _dayFocusNodes = List.generate( daysInMonth, (int index) => FocusNode(skipTraversal: true, debugLabel: 'Day ${index + 1}'), ); } @override void didChangeDependencies() { super.didChangeDependencies(); // Check to see if the focused date is in this month, if so focus it. final DateTime? focusedDate = _FocusedDate.of(context)?.date; if (focusedDate != null && DateUtils.isSameMonth(widget.displayedMonth, focusedDate)) { _dayFocusNodes[focusedDate.day - 1].requestFocus(); } } @override void dispose() { for (final FocusNode node in _dayFocusNodes) { node.dispose(); } super.dispose(); } Color _highlightColor(BuildContext context) { return Theme.of(context).colorScheme.primary.withOpacity(0.12); } void _dayFocusChanged(bool focused) { if (focused) { final TraversalDirection? focusDirection = _FocusedDate.of(context)?.scrollDirection; if (focusDirection != null) { ScrollPositionAlignmentPolicy policy = ScrollPositionAlignmentPolicy.explicit; switch (focusDirection) { case TraversalDirection.up: case TraversalDirection.left: policy = ScrollPositionAlignmentPolicy.keepVisibleAtStart; break; case TraversalDirection.right: case TraversalDirection.down: policy = ScrollPositionAlignmentPolicy.keepVisibleAtEnd; break; } Scrollable.ensureVisible( primaryFocus!.context!, duration: _monthScrollDuration, alignmentPolicy: policy, ); } } } Widget _buildDayItem(BuildContext context, DateTime dayToBuild, int firstDayOffset, int daysInMonth) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; final TextTheme textTheme = theme.textTheme; final MaterialLocalizations localizations = MaterialLocalizations.of(context); final TextDirection textDirection = Directionality.of(context); final Color highlightColor = _highlightColor(context); final int day = dayToBuild.day; final bool isDisabled = dayToBuild.isAfter(widget.lastDate) || dayToBuild.isBefore(widget.firstDate); BoxDecoration? decoration; TextStyle? itemStyle = textTheme.headlineMedium; final bool isRangeSelected = widget.selectedDateStart != null && widget.selectedDateEnd != null; final bool isSelectedDayStart = widget.selectedDateStart != null && dayToBuild.isAtSameMomentAs(widget.selectedDateStart!); final bool isSelectedDayEnd = widget.selectedDateEnd != null && dayToBuild.isAtSameMomentAs(widget.selectedDateEnd!); final bool isInRange = isRangeSelected && dayToBuild.isAfter(widget.selectedDateStart!) && dayToBuild.isBefore(widget.selectedDateEnd!); _HighlightPainter? highlightPainter; if (isSelectedDayStart || isSelectedDayEnd) { // The selected start and end dates gets a circle background // highlight, and a contrasting text color. itemStyle = textTheme.headlineMedium?.apply(color: colorScheme.onPrimary); decoration = BoxDecoration( color: colorScheme.primary, shape: BoxShape.circle, ); if (isRangeSelected && widget.selectedDateStart != widget.selectedDateEnd) { final _HighlightPainterStyle style = isSelectedDayStart ? _HighlightPainterStyle.highlightTrailing : _HighlightPainterStyle.highlightLeading; highlightPainter = _HighlightPainter( color: highlightColor, style: style, textDirection: textDirection, ); } } else if (isInRange) { // The days within the range get a light background highlight. highlightPainter = _HighlightPainter( color: highlightColor, style: _HighlightPainterStyle.highlightAll, textDirection: textDirection, ); } else if (isDisabled) { itemStyle = textTheme.headlineMedium ?.apply(color: colorScheme.onSurface.withOpacity(0.38)); } else if (DateUtils.isSameDay(widget.currentDate, dayToBuild)) { // The current day gets a different text color and a circle stroke // border. itemStyle = textTheme.headlineMedium?.apply(color: colorScheme.primary); decoration = BoxDecoration( border: Border.all(color: colorScheme.primary), shape: BoxShape.circle, ); } // We want the day of month to be spoken first irrespective of the // locale-specific preferences or TextDirection. This is because // an accessibility user is more likely to be interested in the // day of month before the rest of the date, as they are looking // for the day of month. To do that we prepend day of month to the // formatted full date. String semanticLabel = '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}'; if (isSelectedDayStart) { semanticLabel = localizations.dateRangeStartDateSemanticLabel(semanticLabel); } else if (isSelectedDayEnd) { semanticLabel = localizations.dateRangeEndDateSemanticLabel(semanticLabel); } Widget dayWidget = Container( decoration: decoration, child: Center( child: Semantics( label: semanticLabel, selected: isSelectedDayStart || isSelectedDayEnd, child: ExcludeSemantics( child: Text(localizations.formatDecimal(day), style: itemStyle), ), ), ), ); if (highlightPainter != null) { dayWidget = CustomPaint( painter: highlightPainter, child: dayWidget, ); } if (!isDisabled) { dayWidget = InkResponse( focusNode: _dayFocusNodes[day - 1], onTap: () => widget.onChanged(dayToBuild), radius: _monthItemRowHeight / 2 + 4, splashColor: colorScheme.primary.withOpacity(0.38), onFocusChange: _dayFocusChanged, child: dayWidget, ); } return dayWidget; } Widget _buildEdgeContainer(BuildContext context, bool isHighlighted) { return Container(color: isHighlighted ? _highlightColor(context) : null); } @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); final TextTheme textTheme = themeData.textTheme; final MaterialLocalizations localizations = MaterialLocalizations.of(context); final int year = widget.displayedMonth.year; final int month = widget.displayedMonth.month; final int daysInMonth = DateUtils.getDaysInMonth(year, month); final int dayOffset = DateUtils.firstDayOffset(year, month, localizations); final int weeks = ((daysInMonth + dayOffset) / DateTime.daysPerWeek).ceil(); final double gridHeight = weeks * _monthItemRowHeight + (weeks - 1) * _monthItemSpaceBetweenRows; final List dayItems = []; for (int i = 0; true; i += 1) { // 1-based day of month, e.g. 1-31 for January, and 1-29 for February on // a leap year. final int day = i - dayOffset + 1; if (day > daysInMonth) { break; } if (day < 1) { dayItems.add(Container()); } else { final DateTime dayToBuild = DateTime(year, month, day); final Widget dayItem = _buildDayItem( context, dayToBuild, dayOffset, daysInMonth, ); dayItems.add(dayItem); } } // Add the leading/trailing edge containers to each week in order to // correctly extend the range highlight. final List paddedDayItems = []; for (int i = 0; i < weeks; i++) { final int start = i * DateTime.daysPerWeek; final int end = math.min( start + DateTime.daysPerWeek, dayItems.length, ); final List weekList = dayItems.sublist(start, end); final DateTime dateAfterLeadingPadding = DateTime(year, month, start - dayOffset + 1); // Only color the edge container if it is after the start date and // on/before the end date. final bool isLeadingInRange = !(dayOffset > 0 && i == 0) && widget.selectedDateStart != null && widget.selectedDateEnd != null && dateAfterLeadingPadding.isAfter(widget.selectedDateStart!) && !dateAfterLeadingPadding.isAfter(widget.selectedDateEnd!); weekList.insert(0, _buildEdgeContainer(context, isLeadingInRange)); // Only add a trailing edge container if it is for a full week and not a // partial week. if (end < dayItems.length || (end == dayItems.length && dayItems.length % DateTime.daysPerWeek == 0)) { final DateTime dateBeforeTrailingPadding = DateTime(year, month, end - dayOffset); // Only color the edge container if it is on/after the start date and // before the end date. final bool isTrailingInRange = widget.selectedDateStart != null && widget.selectedDateEnd != null && !dateBeforeTrailingPadding.isBefore(widget.selectedDateStart!) && dateBeforeTrailingPadding.isBefore(widget.selectedDateEnd!); weekList.add(_buildEdgeContainer(context, isTrailingInRange)); } paddedDayItems.addAll(weekList); } final double maxWidth = MediaQuery.of(context).orientation == Orientation.landscape ? _maxCalendarWidthLandscape : _maxCalendarWidthPortrait; return Column( children: [ Container( constraints: BoxConstraints(maxWidth: maxWidth), height: _monthItemHeaderHeight, padding: const EdgeInsets.symmetric(horizontal: 16), alignment: AlignmentDirectional.centerStart, child: ExcludeSemantics( child: Text( localizations.formatMonthYear(widget.displayedMonth), style: textTheme.headlineMedium! .apply(color: themeData.colorScheme.onSurface), ), ), ), Container( constraints: BoxConstraints( maxWidth: maxWidth, maxHeight: gridHeight, ), child: GridView.custom( physics: const NeverScrollableScrollPhysics(), gridDelegate: _monthItemGridDelegate, childrenDelegate: SliverChildListDelegate( paddedDayItems, addRepaintBoundaries: false, ), ), ), const SizedBox(height: _monthItemFooterHeight), ], ); } } /// Determines which style to use to paint the highlight. enum _HighlightPainterStyle { /// Paints nothing. none, /// Paints a rectangle that occupies the leading half of the space. highlightLeading, /// Paints a rectangle that occupies the trailing half of the space. highlightTrailing, /// Paints a rectangle that occupies all available space. highlightAll, } /// This custom painter will add a background highlight to its child. /// /// This highlight will be drawn depending on the [style], [color], and /// [textDirection] supplied. It will either paint a rectangle on the /// left/right, a full rectangle, or nothing at all. This logic is determined by /// a combination of the [style] and [textDirection]. class _HighlightPainter extends CustomPainter { _HighlightPainter({ required this.color, this.style = _HighlightPainterStyle.none, this.textDirection, }); final Color color; final _HighlightPainterStyle style; final TextDirection? textDirection; @override void paint(Canvas canvas, Size size) { if (style == _HighlightPainterStyle.none) { return; } final Paint paint = Paint() ..color = color ..style = PaintingStyle.fill; final Rect rectLeft = Rect.fromLTWH(0, 0, size.width / 2, size.height); final Rect rectRight = Rect.fromLTWH(size.width / 2, 0, size.width / 2, size.height); switch (style) { case _HighlightPainterStyle.highlightTrailing: canvas.drawRect( textDirection == TextDirection.ltr ? rectRight : rectLeft, paint, ); break; case _HighlightPainterStyle.highlightLeading: canvas.drawRect( textDirection == TextDirection.ltr ? rectLeft : rectRight, paint, ); break; case _HighlightPainterStyle.highlightAll: canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), paint, ); break; case _HighlightPainterStyle.none: break; } } @override bool shouldRepaint(CustomPainter oldDelegate) => false; } class _InputDateRangePickerDialog extends StatelessWidget { const _InputDateRangePickerDialog({ required this.selectedStartDate, required this.selectedEndDate, required this.currentDate, required this.picker, required this.onConfirm, required this.onCancel, required this.confirmText, required this.cancelText, required this.helpText, required this.entryModeButton, }); final DateTime? selectedStartDate; final DateTime? selectedEndDate; final DateTime? currentDate; final Widget picker; final VoidCallback onConfirm; final VoidCallback onCancel; final String? confirmText; final String? cancelText; final String? helpText; final Widget? entryModeButton; String _formatDateRange( BuildContext context, DateTime? start, DateTime? end, DateTime now) { final MaterialLocalizations localizations = MaterialLocalizations.of(context); final String startText = _formatRangeStartDate(localizations, start, end); final String endText = _formatRangeEndDate(localizations, start, end, now); if (start == null || end == null) { return localizations.unspecifiedDateRange; } if (Directionality.of(context) == TextDirection.ltr) { return '$startText – $endText'; } else { return '$endText – $startText'; } } @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; final MaterialLocalizations localizations = MaterialLocalizations.of(context); final Orientation orientation = MediaQuery.of(context).orientation; final TextTheme textTheme = theme.textTheme; final Color onPrimarySurfaceColor = colorScheme.brightness == Brightness.light ? colorScheme.onPrimary : colorScheme.onSurface; final TextStyle? dateStyle = orientation == Orientation.landscape ? textTheme.headlineMedium?.apply(color: onPrimarySurfaceColor) : textTheme.headlineMedium?.apply(color: onPrimarySurfaceColor); final String dateText = _formatDateRange( context, selectedStartDate, selectedEndDate, currentDate!); final String semanticDateText = selectedStartDate != null && selectedEndDate != null ? '${localizations.formatMediumDate(selectedStartDate!)} – ${localizations.formatMediumDate(selectedEndDate!)}' : ''; final Widget header = _DatePickerHeader( helpText: helpText ?? localizations.dateRangePickerHelpText, titleText: dateText, titleSemanticsLabel: semanticDateText, titleStyle: dateStyle, orientation: orientation, isShort: orientation == Orientation.landscape, entryModeButton: entryModeButton, ); final Widget actions = Container( alignment: AlignmentDirectional.centerEnd, constraints: const BoxConstraints(minHeight: 52.0), padding: const EdgeInsets.symmetric(horizontal: 8), child: OverflowBar( spacing: 8, children: [ TextButton( onPressed: onCancel, child: Text(cancelText ?? localizations.cancelButtonLabel), ), TextButton( onPressed: onConfirm, child: Text(confirmText ?? localizations.okButtonLabel), ), ], ), ); switch (orientation) { case Orientation.portrait: return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ header, Expanded(child: picker), actions, ], ); case Orientation.landscape: return Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ header, Flexible( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded(child: picker), actions, ], ), ), ], ); } } } /// Provides a pair of text fields that allow the user to enter the start and /// end dates that represent a range of dates. class _InputDateRangePicker extends StatefulWidget { /// Creates a row with two text fields configured to accept the start and end dates /// of a date range. _InputDateRangePicker({ super.key, DateTime? initialStartDate, DateTime? initialEndDate, required DateTime firstDate, required DateTime lastDate, required this.onStartDateChanged, required this.onEndDateChanged, this.helpText, this.errorFormatText, this.errorInvalidText, this.errorInvalidRangeText, this.fieldStartHintText, this.fieldEndHintText, this.fieldStartLabelText, this.fieldEndLabelText, this.autofocus = false, this.autovalidate = false, }) : initialStartDate = initialStartDate == null ? null : DateUtils.dateOnly(initialStartDate), initialEndDate = initialEndDate == null ? null : DateUtils.dateOnly(initialEndDate), assert(firstDate != null), firstDate = DateUtils.dateOnly(firstDate), assert(lastDate != null), lastDate = DateUtils.dateOnly(lastDate), assert(firstDate != null), assert(lastDate != null), assert(autofocus != null), assert(autovalidate != null); /// The [DateTime] that represents the start of the initial date range selection. final DateTime? initialStartDate; /// The [DateTime] that represents the end of the initial date range selection. final DateTime? initialEndDate; /// The earliest allowable [DateTime] that the user can select. final DateTime firstDate; /// The latest allowable [DateTime] that the user can select. final DateTime lastDate; /// Called when the user changes the start date of the selected range. final ValueChanged? onStartDateChanged; /// Called when the user changes the end date of the selected range. final ValueChanged? onEndDateChanged; /// The text that is displayed at the top of the header. /// /// This is used to indicate to the user what they are selecting a date for. final String? helpText; /// Error text used to indicate the text in a field is not a valid date. final String? errorFormatText; /// Error text used to indicate the date in a field is not in the valid range /// of [firstDate] - [lastDate]. final String? errorInvalidText; /// Error text used to indicate the dates given don't form a valid date /// range (i.e. the start date is after the end date). final String? errorInvalidRangeText; /// Hint text shown when the start date field is empty. final String? fieldStartHintText; /// Hint text shown when the end date field is empty. final String? fieldEndHintText; /// Label used for the start date field. final String? fieldStartLabelText; /// Label used for the end date field. final String? fieldEndLabelText; /// {@macro flutter.widgets.editableText.autofocus} final bool autofocus; /// If true, this the date fields will validate and update their error text /// immediately after every change. Otherwise, you must call /// [_InputDateRangePickerState.validate] to validate. final bool autovalidate; @override _InputDateRangePickerState createState() => _InputDateRangePickerState(); } /// The current state of an [_InputDateRangePicker]. Can be used to /// [validate] the date field entries. class _InputDateRangePickerState extends State<_InputDateRangePicker> { late String _startInputText; late String _endInputText; DateTime? _startDate; DateTime? _endDate; late TextEditingController _startController; late TextEditingController _endController; String? _startErrorText; String? _endErrorText; bool _autoSelected = false; @override void initState() { super.initState(); _startDate = widget.initialStartDate; _startController = TextEditingController(); _endDate = widget.initialEndDate; _endController = TextEditingController(); } @override void dispose() { _startController.dispose(); _endController.dispose(); super.dispose(); } @override void didChangeDependencies() { super.didChangeDependencies(); final MaterialLocalizations localizations = MaterialLocalizations.of(context); if (_startDate != null) { _startInputText = localizations.formatCompactDate(_startDate!); final bool selectText = widget.autofocus && !_autoSelected; _updateController(_startController, _startInputText, selectText); _autoSelected = selectText; } if (_endDate != null) { _endInputText = localizations.formatCompactDate(_endDate!); _updateController(_endController, _endInputText, false); } } /// Validates that the text in the start and end fields represent a valid /// date range. /// /// Will return true if the range is valid. If not, it will /// return false and display an appropriate error message under one of the /// text fields. bool validate() { String? startError = _validateDate(_startDate); final String? endError = _validateDate(_endDate); if (startError == null && endError == null) { if (_startDate!.isAfter(_endDate!)) { startError = widget.errorInvalidRangeText ?? MaterialLocalizations.of(context).invalidDateRangeLabel; } } setState(() { _startErrorText = startError; _endErrorText = endError; }); return startError == null && endError == null; } DateTime? _parseDate(String? text) { final MaterialLocalizations localizations = MaterialLocalizations.of(context); return localizations.parseCompactDate(text); } String? _validateDate(DateTime? date) { if (date == null) { return widget.errorFormatText ?? MaterialLocalizations.of(context).invalidDateFormatLabel; } else if (date.isBefore(widget.firstDate) || date.isAfter(widget.lastDate)) { return widget.errorInvalidText ?? MaterialLocalizations.of(context).dateOutOfRangeLabel; } return null; } void _updateController( TextEditingController controller, String text, bool selectText) { TextEditingValue textEditingValue = controller.value.copyWith(text: text); if (selectText) { textEditingValue = textEditingValue.copyWith( selection: TextSelection( baseOffset: 0, extentOffset: text.length, )); } controller.value = textEditingValue; } void _handleStartChanged(String text) { setState(() { _startInputText = text; _startDate = _parseDate(text); widget.onStartDateChanged?.call(_startDate); }); if (widget.autovalidate) { validate(); } } void _handleEndChanged(String text) { setState(() { _endInputText = text; _endDate = _parseDate(text); widget.onEndDateChanged?.call(_endDate); }); if (widget.autovalidate) { validate(); } } @override Widget build(BuildContext context) { final MaterialLocalizations localizations = MaterialLocalizations.of(context); final InputDecorationThemeData inputTheme = Theme.of(context).inputDecorationTheme; return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: TextField( controller: _startController, decoration: InputDecoration( border: inputTheme.border ?? const UnderlineInputBorder(), filled: inputTheme.filled, hintText: widget.fieldStartHintText ?? localizations.dateHelpText, labelText: widget.fieldStartLabelText ?? localizations.dateRangeStartLabel, errorText: _startErrorText, ), keyboardType: TextInputType.datetime, onChanged: _handleStartChanged, autofocus: widget.autofocus, ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _endController, decoration: InputDecoration( border: inputTheme.border ?? const UnderlineInputBorder(), filled: inputTheme.filled, hintText: widget.fieldEndHintText ?? localizations.dateHelpText, labelText: widget.fieldEndLabelText ?? localizations.dateRangeEndLabel, errorText: _endErrorText, ), keyboardType: TextInputType.datetime, onChanged: _handleEndChanged, ), ), ], ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/default/empty_search_page.dart ================================================ import 'package:flutter/material.dart'; class NotSearchPage extends StatelessWidget { final String tips; const NotSearchPage({Key? key, required this.tips}):super(key: key); @override Widget build(BuildContext context) { final Color color = Theme.of(context).primaryColor; return Container( height: 300, alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon(Icons.search, color:color, size: 120.0), Container( padding: const EdgeInsets.only(top: 16.0), child: Text( tips, style: TextStyle( fontSize: 20, color: color, ), ), ) ], ), ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/default/empty_shower.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/11/17 /// contact me by email 1981462002@qq.com /// 说明: 默认 数据为空视图 class EmptyShower extends StatelessWidget { final String message; const EmptyShower({Key? key, this.message = "数据为空"}) : super(key: key); @override Widget build(BuildContext context) { final Color color = Theme.of(context).primaryColor; return Center( child: Container( alignment: FractionalOffset.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon(Icons.style, color: color, size: 120.0), Container( padding: const EdgeInsets.only(top: 10.0), child: Text( message, textAlign: TextAlign.center, style: TextStyle( color: color, fontSize: 18, ), ), ) ], ), ), ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/default/error_page.dart ================================================ import 'package:flutter/material.dart'; class ErrorPage extends StatelessWidget { const ErrorPage({Key? key}):super(key: key); @override Widget build(BuildContext context) { return Container( height: 300, alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon(Icons.error_outline, color: Colors.red[300], size: 120.0), Container( padding: const EdgeInsets.only(top: 16.0), child: Text( "好像有些小错误,ε=(#>д<)ノ", style: TextStyle( fontSize: 20, color: Colors.red[300], ), ), ) ], ), ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/default/error_shower.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/11/17 /// contact me by email 1981462002@qq.com /// 说明: 默认 错误视图 class ErrorShower extends StatelessWidget { final String error; const ErrorShower({Key? key, this.error = "出现异常"}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Wrap( spacing: 10, direction: Axis.vertical, alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const SizedBox( width: 80, height: 80, child: Icon( Icons.error, size: 80, color: Colors.red, )), Text( error, style:const TextStyle( color: Colors.red, fontSize: 18, ), textAlign: TextAlign.center, ) ], ), ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/default/loading_shower.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; /// create by 张风捷特烈 on 2020/10/24 /// contact me by email 1981462002@qq.com /// 说明: 默认 加载视图 class LoadingShower extends StatelessWidget { const LoadingShower({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Wrap( spacing: 10, direction: Axis.vertical, alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ SizedBox( width: 80, height: 80, child: SpinKitFadingCube(color: Theme.of(context).primaryColor)), const Text("loading ...",style: TextStyle( color: Color(0xff939EA7), fontSize: 13),) ], ), ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/default/no_more_widget.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/11/25 /// contact me by email 1981462002@qq.com /// 说明: class NoMoreWidget extends StatelessWidget { const NoMoreWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { double bottom = MediaQuery.of(context).padding.bottom; return SizedBox(height: bottom); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/project_ui.dart ================================================ export 'default/empty_search_page.dart'; export 'default/empty_shower.dart'; export 'default/error_shower.dart'; export 'default/loading_shower.dart'; export 'default/error_page.dart'; export 'default/no_more_widget.dart'; export 'wrapper/honour_wrapper.dart'; export 'unit_app_bar.dart'; export 'top_bar/desk_simple_top_bar.dart'; export 'top_bar/desk_tab_top_bar.dart'; export 'top_bar/desk_knowledge_top_bar.dart'; export 'time_line/flutter_unit_time_line.dart'; export 'refresh/refresh.dart'; ================================================ FILE: modules/basic_system/components/lib/project_ui/refresh/refresh.dart ================================================ export 'refresh_config_wrapper.dart'; export 'package:tolyui_refresh/tolyui_refresh.dart' show TolyRefresh,RefreshController; ================================================ FILE: modules/basic_system/components/lib/project_ui/refresh/refresh_config_wrapper.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tolyui_refresh/tolyui_refresh.dart'; import 'toly_refresh_indicator.dart'; class RefreshConfigWrapper extends StatelessWidget { final Widget child; const RefreshConfigWrapper({Key? key, required this.child}) : super(key: key); @override Widget build(BuildContext context) { Color themeColor = Theme.of(context).primaryColor; return RefreshConfigScope( headerTriggerDistance: 60, topHitBoundary: 20, child: child, springDescription: SpringDescription.withDampingRatio( mass: 0.5, stiffness: 100.0, ratio: 1.1, ), headerBuilder: () => const TolyRefreshIndicator(), footerBuilder: () => CustomFooter( builder: (BuildContext context, LoadStatus? mode) { Widget body; if (mode == LoadStatus.idle) { body = Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ Icon(Icons.arrow_upward, color: themeColor), Text("上拉加载", style: TextStyle(color: themeColor, height: 1)), ], ); } else if (mode == LoadStatus.loading) { body = CupertinoActivityIndicator(); } else if (mode == LoadStatus.failed) { body = Text("加载失败!点击重试!"); } else if (mode == LoadStatus.canLoading) { body = Text("松手,加载更多!", style: TextStyle(color: themeColor, height: 1)); } else { body = Text("没有更多数据了!", style: TextStyle( color: Colors.grey, )); } return Container( height: 55.0, child: Center(child: body), ); }, ), ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/refresh/toly_refresh_indicator.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart' hide RefreshIndicator, RefreshIndicatorState; import 'package:flutter/services.dart'; import 'package:tolyui_refresh/tolyui_refresh.dart'; class TolyRefreshIndicator extends RefreshIndicator { const TolyRefreshIndicator({super.key}); @override State createState() => _TolyRefreshIndicatorState(); } class _TolyRefreshIndicatorState extends RefreshIndicatorState with TickerProviderStateMixin { late AnimationController _iconRotateCtrl; late Animation rotateAnima; @override void initState() { super.initState(); _iconRotateCtrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 250)); rotateAnima = Tween(begin: 0.0, end: -0.5).animate(_iconRotateCtrl); } @override void onModeChange(RefreshStatus? mode) { if (mode == RefreshStatus.canRefresh) { HapticFeedback.lightImpact(); _iconRotateCtrl.forward(); } if (mode == RefreshStatus.idle) { if (_iconRotateCtrl.isCompleted) { _iconRotateCtrl.reverse(); } } } @override Future readyToRefresh() { // TODO: implement readyToRefresh // final Simulation simulation = SpringSimulation( // SpringDescription( // mass: 3.4, // stiffness: 10000.5, // damping: 6, // ), // _beizerBounceCtl.value, // 0, // 1000); // _beizerBounceCtl.animateWith(simulation); // if (widget.readyRefresh != null) { // return widget.readyRefresh!(); // } return super.readyToRefresh(); } @override Widget buildContent(BuildContext context, RefreshStatus mode) { Widget child = switch (mode) { RefreshStatus.refreshing => const CupertinoActivityIndicator( radius: 10, ), RefreshStatus.completed => const Icon( Icons.check, color: Colors.green, ), RefreshStatus.idle || RefreshStatus.canRefresh => RotationTransition( turns: rotateAnima, child: Icon( CupertinoIcons.arrow_down, color: Color(0xff666668), ), ), _ => SizedBox() }; return Container( // color: Colors.blue, child: Center( child: Padding( padding: const EdgeInsets.all(16.0), child: child, )), ); } @override void onOffsetChange(double offset) { // print(offset); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/time_line/flutter_unit_time_line.dart ================================================ import 'package:app/app.dart'; import 'package:dash_painter/dash_painter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:wrapper/wrapper.dart'; import 'model/time_node.dart'; class FlutterUnitTimeLine extends StatelessWidget { const FlutterUnitTimeLine({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, appBar: AppBar( backgroundColor: Colors.black, title: const Text('FlutterUnit 时光轴'), ), body: Container( margin: const EdgeInsets.only(top: 0), child: ListView( padding: const EdgeInsets.only(top: 0, bottom: 20), children: [ TimeLineNode( timeNode: TimeNode( title: 'FlutterUnit 正式开源', year: '2020', content: 'V1.0 版本: 核心功能是组件集录,此时收录 213 组件的基本使用方式。', imageUrl: 'https://gitee.com/toly1994/res/raw/master/img3.webp', time: '2020-04-15', ), ), TimeLineNode( timeNode: TimeNode( title: '收藏夹功能完成', content: '通过收藏夹功能,使用者可以自由创建收藏夹,对组件进行自己的分类。', time: '2020-04-23', ), ), TimeLineNode( timeNode: TimeNode( title: 'FlutterUnit 支持 MacOS', content: '优化相关界面布局,在新分支中支持 MacOS 系统运行 FlutterUnit 程序。', imageUrl: 'https://gitee.com/toly1994/toly_blog_pic/raw/master/node4.webp', time: '2020-05-05', ), ), TimeLineNode( timeNode: TimeNode( title: 'FlutterUnit 支持 Windows', content: '优化相关界面布局,在新分支中支持 Windows 系统运行 FlutterUnit 程序。', time: '2020-07-09', ), ), TimeLineNode( timeNode: TimeNode( title: 'FlutterUnit 支持 web', content: '优化相关界面布局,在新分支中支持 Windows 系统运行 FlutterUnit 程序。', time: '2020-08-12', ), ), TimeLineNode( timeNode: TimeNode( title: ' Flutter 要点集录', imageUrl: 'https://gitee.com/toly1994/res/raw/master/img2.webp', content: '要点集录会收集 FlutterUnit 中 point 标签的 issues,在应用中进行展示,从而达到对要点知识的收集。', time: '2020-09-04', ), ), TimeLineNode( timeNode: TimeNode( title: '组件集录收集数', content: '收录组件数量从最初的 213 个增加到 306 个,已经涵盖了绝大多数 Flutter 框架中的原生组件。', time: '2020-12-22', ), ), TimeLineNode( timeNode: TimeNode( title: '个人页面的加入', content: '相比于侧滑菜单,通过个人页面,更方便进行一些应用的设置操作。', time: '2020-12-22', ), ), TimeLineNode( timeNode: TimeNode( year: '2021', title: '绘制集录正式加入', imageUrl: 'https://gitee.com/toly1994/res/raw/master/%E7%BB%98%E5%88%B6%E9%9B%86%E5%BD%95%E7%9A%84%E5%89%AF%E6%9C%AC.webp', content: '绘制集录目的在于收录一些有意思的绘制案例,帮助对绘制感兴趣的朋友更好地学习,另一方面也可以反映出 Flutter 在绘制方面的强大能力。', time: '2021-01-22', ), ), TimeLineNode( timeNode: TimeNode( title: '后端服务计划启动', content: '基于 SpringBoot 框架实现 Flutter Unit Server,打算实现用户系统,支持组件收藏的用户化及同步操作,以及应用中组件数据的线上化。(封测中)', time: '2021-03-28', ), ), TimeLineNode( timeNode: TimeNode( title: '用户登录测试', content: '后端完成基本的用户系统,基于邮箱校验,前端完成用户注册、登录等界面及交互逻辑。(封测中)', time: '2021-05-01', ), ), TimeLineNode( timeNode: TimeNode( title: '全面支持空安全', imageUrl: 'https://gitee.com/toly1994/res/raw/master/nullsafe.webp', content: '由于 FlutterUnit 中存在大量的组件示例,支持空安全是一个非常大的挑战。', time: '2021-08-29', ), ), TimeLineNode( timeNode: TimeNode( title: '绘制集录持续收集', content: '根据绘制、手势、动画等小册中的案例,进行收录到 FlutterUnit 绘制集录中,桌面版本也同步更新中。', time: '2020-10-01', ), ), TimeLineNode( timeNode: TimeNode( title: '实现应用内升级', imageUrl: 'https://gitee.com/toly1994/res/raw/master/img.webp', content: '服务端完成应用版本检测接口,应用内升级的实现,方便大家更新版本,及时体验最新版功能。', time: '2021-12-18', ), ), TimeLineNode( timeNode: TimeNode( title: '项目及案例全面优化', year: '2022', imageUrl: 'https://gitee.com/toly1994/res/raw/master/img4.webp', content: '由于代码案例代码比较老旧,为了给新手朋友一个规范的示例。针对 Dart Analysis 中代码的规范性问题进行优化。由于案例数量太多,这又是一个非常巨大的挑战。', time: '2021-03-26', ), ), TimeLineNode( timeNode: TimeNode( title: 'FlutterUnit 2.0 发布', content: '鉴于案例的优化完成、[绘制/组件]集录的收集情况、以及应用内更新的支持,FlutterUnit 正式进入 2.0 版本。', imageUrl: 'https://gitee.com/toly1994/res/raw/master/img3.webp', time: '2022-03-28', ), ), ], ), ), ); } } class TimeLineNode extends StatelessWidget { final TimeNode timeNode; const TimeLineNode({Key? key, required this.timeNode}) : super(key: key); final double dashLineWith = 20; final double marginTop = 10; final double offset = 20 + 10; final double lineWidth = 2; final double circleRadius = 5; @override Widget build(BuildContext context) { Color themeColor = BlocProvider.of(context).state.themeColor.color; return IntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 65, child: Padding( padding: const EdgeInsets.only(top: 20 + 5, left: 10), child: timeNode.year != null ? Text( '${timeNode.year} 年', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold), ) : const SizedBox.shrink()), ), _buildDecoration(themeColor), Expanded(flex: 8, child: _buildCenterWidget(themeColor)), const Spacer(flex: 1) ], ), ); } Widget _buildDecoration(Color themeColor) => Container( margin: const EdgeInsets.only(left: 10), width: dashLineWith, decoration: DashDecoration( circleColor: themeColor, lineColor: Colors.white, circleRadius: circleRadius, color: Colors.white, circleOffset: Offset(lineWidth / 2, offset + 10 / 2)), ); Widget _buildCenterWidget(Color themeColor) { return Padding( padding: const EdgeInsets.only(top: 10), child: Wrapper( color: Colors.white, offset: 20 + 10 / 2 - 6, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( timeNode.title, style: TextStyle(fontWeight: FontWeight.bold, color: themeColor), ), Container( margin: const EdgeInsets.symmetric(vertical: 5), child: Text( timeNode.content, style: const TextStyle( color: Colors.grey, fontSize: 12, shadows: [ Shadow(color: Colors.blueAccent, blurRadius: .1) ]), ), ), if (timeNode.imageUrl != null) Container( width: double.infinity, margin: const EdgeInsets.symmetric(vertical: 5), child: Image( image: NetworkImage( timeNode.imageUrl!, ), height: 100, fit: BoxFit.cover, ), ), Align( alignment: Alignment.centerRight, child: Text(timeNode.time, style: TextStyle( color: themeColor, fontSize: 14, fontWeight: FontWeight.bold)), ) ], ), ), ); } } class DashDecoration extends Decoration { final Color color; final Color circleColor; final Color lineColor; final Offset circleOffset; final double circleRadius; const DashDecoration( {required this.color, required this.circleColor, required this.lineColor, required this.circleOffset, required this.circleRadius}); @override BoxPainter createBoxPainter([VoidCallback? onChanged]) => DashBoxPainter(decoration: this); } class DashBoxPainter extends BoxPainter { final DashDecoration decoration; const DashBoxPainter({required this.decoration}); @override void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { canvas.save(); final Paint paint = Paint()..style = PaintingStyle.stroke; final Path path = Path(); canvas.translate( offset.dx, offset.dy, ); // 绘制直线 canvas.drawLine( Offset(-decoration.circleOffset.dx, 0), Offset(-decoration.circleOffset.dx, configuration.size!.height), paint ..color = decoration.lineColor ..strokeWidth = 2); // 绘制虚线 path ..moveTo(0, decoration.circleOffset.dy) ..relativeLineTo(configuration.size!.width, 0); const DashPainter(span: 2, step: 3).paint( canvas, path, paint ..color = decoration.color ..strokeWidth = 1); //绘制圆点 final Paint paint2 = Paint()..color = Colors.white; canvas.drawCircle( Offset(-decoration.circleOffset.dx, decoration.circleOffset.dy), decoration.circleRadius, paint2); canvas.drawCircle( Offset(-decoration.circleOffset.dx, decoration.circleOffset.dy), decoration.circleRadius * 0.6, paint2..color = decoration.circleColor); canvas.restore(); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/time_line/model/time_node.dart ================================================ class TimeNode { final String title; final String time; final String? year; final String content; final String? imageUrl; TimeNode( {required this.title, required this.time, this.year, required this.content, this.imageUrl}); @override String toString() { return 'TimeNode{title: $title, time: $time, content: $content, imageUrl: $imageUrl}'; } } ================================================ FILE: modules/basic_system/components/lib/project_ui/top_bar/desk_account_top_bar.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; class DeskAccountTopBar extends StatelessWidget { final Widget? leading; const DeskAccountTopBar({super.key, this.leading}); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; return DragToMoveWrapper( child: Container( height: 64, color: isDark ? Color(0xff2C3036) : Colors.white, child: Row( children: [ if (leading != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: leading!, ), const Spacer(), const SizedBox( width: 20, ), const WindowButtons(), ], ), ), ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/top_bar/desk_knowledge_top_bar.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; class DeskKnowledgeTabTopBar extends StatefulWidget { final List tabs; final ValueChanged onTabPressed; const DeskKnowledgeTabTopBar( {Key? key, required this.onTabPressed, required this.tabs}) : super(key: key); @override State createState() => _DeskKnowledgeTabTopBarState(); } class _DeskKnowledgeTabTopBarState extends State with TickerProviderStateMixin { late TabController tabController; @override void initState() { super.initState(); tabController = TabController(length: widget.tabs.length, vsync: this); } @override void didUpdateWidget(covariant DeskKnowledgeTabTopBar oldWidget) { if (oldWidget.tabs.length != widget.tabs.length) { tabController.dispose(); tabController = TabController(length: widget.tabs.length, vsync: this); } super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { Color themeColor = Theme.of(context).primaryColor; bool isDark = Theme.of(context).brightness == Brightness.dark; return DragToMoveWrapper( child: Stack( children: [ Container( height: 64, color: isDark ? const Color(0xff2C3036) : Colors.white, child: Row( children: [ Expanded( child: Center( child: SizedBox( width: 400, child: TabBar( onTap: widget.onTabPressed, indicatorSize: TabBarIndicatorSize.label, labelPadding: const EdgeInsets.symmetric(horizontal: 6), isScrollable: false, indicator: RoundRectTabIndicator( borderSide: BorderSide(color: themeColor, width: 3), ), labelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), controller: tabController, labelColor: themeColor, indicatorWeight: 3, unselectedLabelColor: Colors.grey, indicatorColor: themeColor, tabs: widget.tabs .map((String name) => Tab(text: name)) .toList(), ), ), ), ), const SizedBox( width: 20, ), // WindowButtons(), ], ), ), const Positioned(child: WindowButtons()) ], ), ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/top_bar/desk_simple_top_bar.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; class SimpleDeskTopBar extends StatelessWidget { final Widget? leading; const SimpleDeskTopBar({super.key, this.leading}); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; return DragToMoveWrapper( child: Container( height: 64, color: isDark ? const Color(0xff2C3036) : Colors.white, child: Row( children: [ if (leading != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: leading!, ), const Spacer(), const SizedBox( width: 20, ), const WindowButtons(), ], ), ), ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/top_bar/desk_tab_top_bar.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; class DeskTabTopBar extends StatefulWidget { final List tabs; final ValueChanged onTabPressed; const DeskTabTopBar({Key? key,required this.onTabPressed, required this.tabs}) : super(key: key); @override State createState() => _DeskTabTopBarState(); } class _DeskTabTopBarState extends State with TickerProviderStateMixin { late TabController tabController; @override void initState() { super.initState(); tabController = TabController(length: widget.tabs.length, vsync: this); } @override void didUpdateWidget(covariant DeskTabTopBar oldWidget) { if(oldWidget.tabs.length!=widget.tabs.length){ tabController.dispose(); tabController = TabController(length: widget.tabs.length, vsync: this); } super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { Color themeColor = Theme.of(context).primaryColor; bool isDark = Theme.of(context).brightness == Brightness.dark; return DragToMoveWrapper( child: Container( height: 64, color: isDark?const Color(0xff2C3036):Colors.white, child: Row( children: [ const SizedBox(width: 12,), const BackButton(), SizedBox( width: 350, child: TabBar( onTap: widget.onTabPressed, indicatorSize: TabBarIndicatorSize.label, labelPadding: const EdgeInsets.symmetric(horizontal: 6), isScrollable: false, indicator: RoundRectTabIndicator( borderSide: BorderSide(color: themeColor, width: 3), ), labelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), controller: tabController, labelColor: themeColor, indicatorWeight: 3, unselectedLabelColor: Colors.grey, indicatorColor: themeColor, tabs: widget.tabs.map((String name) => Tab(text: name)).toList(), ), ), const Spacer(), const SizedBox(width: 20,), const WindowButtons(), ], ), ), ); } } ================================================ FILE: modules/basic_system/components/lib/project_ui/unit_app_bar.dart ================================================ import 'package:flutter/material.dart'; class UnitAppbar extends StatelessWidget implements PreferredSizeWidget{ final String title; final Widget? leading; final List? actions; const UnitAppbar({Key? key,required this.title, this.leading,this.actions}) : super(key: key); @override Widget build(BuildContext context) { return AppBar( actions: actions, elevation: 0, centerTitle: true, backgroundColor: Colors.white, leading: const BackButton(color: Colors.black,), title: Text(title,style: const TextStyle(color: Colors.black,fontSize: 16),), ); } @override Size get preferredSize => AppBar().preferredSize; } ================================================ FILE: modules/basic_system/components/lib/project_ui/wrapper/honour_wrapper.dart ================================================ import 'dart:ui' as ui; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2021/1/30 /// contact me by email 1981462002@qq.com /// 说明: class HonourWrapper extends StatefulWidget { final String username; const HonourWrapper({Key? key, this.username = '张风捷特烈'}) : super(key: key); @override _HonourWrapperState createState() => _HonourWrapperState(); } class _HonourWrapperState extends State with SingleTickerProviderStateMixin { late AnimationController _ctrl; @override void initState() { super.initState(); _ctrl = AnimationController(vsync: this, duration: const Duration(seconds: 5)) ..repeat(reverse: true); } @override void dispose() { _ctrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return RepaintBoundary( child: CustomPaint( painter: HonourPainter(factor: _ctrl), child: Padding( padding: const EdgeInsets.only(right: 5, left: 5, top: 6, bottom: 6), child: Text( widget.username, style: const TextStyle(fontSize: 16, color: Colors.white, height: 1), ), ), ), ); } } class HonourPainter extends CustomPainter { final Animation factor; final TextPainter _textPainter = TextPainter( text: const TextSpan( text: '尊享', style: TextStyle(fontSize: 10, color: Colors.white, height: 1), ), textAlign: TextAlign.center, textDirection: TextDirection.ltr); HonourPainter({required this.factor}) : super(repaint: factor); static const List colors = [ Color(0xffFFAB40), Color(0xaaFFAB40), Color(0x88FFAB40), Color(0x55FFAB40), ]; @override void paint(Canvas canvas, Size size) { canvas.drawRRect( RRect.fromRectXY(Offset.zero & size, 5, 5), Paint() ..color = const Color(0xaaFFAB40) ..maskFilter = const MaskFilter.blur(BlurStyle.solid, 2) ..shader = ui.Gradient.linear( const Offset(0, 0), Offset(size.width, 0), colors, [0, 0.3, 0.6, 1.0])); canvas.drawLine( Offset.zero.translate(size.width * factor.value, 0), Offset.zero.translate(size.width * factor.value / 2, size.height), Paint() ..strokeWidth = 5 ..color = Colors.blue.withOpacity(0.3) ..strokeCap = StrokeCap.round, ); _textPainter.layout(); canvas.drawRRect( RRect.fromRectXY( Offset.zero .translate(size.width * factor.value - 3, -15 - 3.0) .translate(-_textPainter.size.width / 2, 0) & (_textPainter.size + const Offset(6, 6)), 5, 5), Paint()..color = Colors.green); _textPainter.paint( canvas, Offset.zero .translate(size.width * factor.value, -15) .translate(-_textPainter.size.width / 2, 0)); } @override bool shouldRepaint(covariant HonourPainter oldDelegate) => oldDelegate.factor != factor; } ================================================ FILE: modules/basic_system/components/pubspec.yaml ================================================ name: components description: A new Flutter package project. version: 0.0.1 homepage: publish_to: none environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/basic_system/components/test/components_test.dart ================================================ // import 'package:flutter_test/flutter_test.dart'; // // import 'package:components/components.dart'; // // void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); // } ================================================ FILE: modules/basic_system/fx_updater/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies build/ ================================================ FILE: modules/basic_system/fx_updater/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "c519ee916eaeb88923e67befb89c0f1dabfa83e6" channel: "stable" project_type: package ================================================ FILE: modules/basic_system/fx_updater/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/basic_system/fx_updater/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/basic_system/fx_updater/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/basic_system/fx_updater/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/basic_system/fx_updater/lib/bloc/bloc.dart ================================================ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fx_dio/fx_dio.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../fx_updater.dart'; import '../strategy/update_strategy.dart'; class UpgradeBloc extends Bloc { final UpgradeApi api; UpgradeBloc({required this.api}) : super(const NoUpdateState()) { on(_onCheckUpdate); on(_onDownloadEvent); on(_onProgressChangeEvent); } void _onCheckUpdate(CheckUpdate event, Emitter emit) async { emit(const CheckLoadingState()); ApiRet ret = await api.fetch(event.appId, event.locale); if (ret.failed) { emit(UpdateErrorState(error: ret.msg)); return; } PackageInfo packageInfo = await PackageInfo.fromPlatform(); String version = packageInfo.version; AppInfo result = ret.data; if (result.shouldUpgrade(version)) { emit(ShouldUpdateState(oldVersion: version, info: result)); } else { int time = DateTime.now().millisecondsSinceEpoch; emit(NoUpdateState(isChecked: true, checkTime: time)); } } void _onDownloadEvent(DownloadEvent event, Emitter emit) async { UpdateState curState = state; if (curState is! ShouldUpdateState) return; String url = event.appInfo.url; void onProgressChange(double progress) { add(ProgressChangeEvent(progress: progress)); } onProgressChange(0.001); UpdateStrategy strategy = UpdateStrategyFactory.create(); await strategy.update(url, onProgressChange); } FutureOr _onProgressChangeEvent( ProgressChangeEvent event, Emitter emit) async { UpdateState curState = state; if (curState is! ShouldUpdateState) return; emit(curState.copyWith(progress: event.progress)); } } ================================================ FILE: modules/basic_system/fx_updater/lib/bloc/event.dart ================================================ import 'package:equatable/equatable.dart'; import '../repository/model/app_info.dart'; sealed class UpdateEvent extends Equatable { const UpdateEvent(); } // 检查更新 ---> 校验,转换状态 class CheckUpdate extends UpdateEvent { final int appId; final String locale; const CheckUpdate({required this.appId,required this.locale, }); @override List get props => [appId]; } class DownloadEvent extends UpdateEvent { final AppInfo appInfo; const DownloadEvent({required this.appInfo}); @override List get props => [appInfo]; } class ProgressChangeEvent extends UpdateEvent { final double progress; const ProgressChangeEvent({required this.progress}); @override List get props => [progress]; } ================================================ FILE: modules/basic_system/fx_updater/lib/bloc/state.dart ================================================ import 'package:equatable/equatable.dart'; import '../repository/model/app_info.dart'; sealed class UpdateState extends Equatable { const UpdateState(); } class NoUpdateState extends UpdateState { final bool isChecked; final int checkTime; const NoUpdateState({this.isChecked = false, this.checkTime = 0}); @override List get props => [isChecked, checkTime]; } class CheckLoadingState extends UpdateState { const CheckLoadingState(); @override List get props => []; } class UpdateErrorState extends UpdateState { final String error; const UpdateErrorState({required this.error}); @override List get props => [error]; @override String toString() { return 'CheckErrorState{error: $error}'; } } class ShouldUpdateState extends UpdateState { final String oldVersion; final double progress; final AppInfo info; const ShouldUpdateState({ required this.oldVersion, required this.info, this.progress = 0, }); @override List get props => [oldVersion, info,progress]; @override String toString() { return 'ShouldUpdateState{oldVersion: $oldVersion, info: $info}'; } bool get isDownloading => progress > 0 && progress != 1; String get progressDisplay => "${(progress * 100).toStringAsFixed(2)}%"; UpdateState copyWith({double? progress}) { return ShouldUpdateState( oldVersion: oldVersion, info: info, progress: progress ?? this.progress, ); } } ================================================ FILE: modules/basic_system/fx_updater/lib/fx_updater.dart ================================================ import 'dart:io'; import 'package:flutter/foundation.dart'; export 'bloc/bloc.dart'; export 'bloc/state.dart'; export 'bloc/event.dart'; export 'repository/model/app_info.dart'; export 'views/app_update_panel.dart'; export 'views/update_red_point.dart'; export 'views/version_shower.dart'; export 'repository/api/upgrade_api.dart'; bool kIsDesk = kIsWeb || Platform.isMacOS || Platform.isWindows || Platform.isLinux; ================================================ FILE: modules/basic_system/fx_updater/lib/repository/api/upgrade_api.dart ================================================ import '../model/app_info.dart'; import 'package:fx_dio/fx_dio.dart'; typedef OnProgressChange = void Function(double progress); abstract class UpgradeApi with CheckUpgrade {} mixin CheckUpgrade { Future> fetch(int appId, String locale); } ================================================ FILE: modules/basic_system/fx_updater/lib/repository/model/app_info.dart ================================================ import 'package:path/path.dart' as p; class AppInfo { final String version; final String url; final int size; final String? description; final String? sha256; const AppInfo({ required this.version, required this.url, required this.size, required this.description, required this.sha256, }); String get appName => p.basename(url); factory AppInfo.fromMap(dynamic map) => AppInfo( version: map['version'] ?? '', url: map['url'] ?? '', size: map['size'] ?? 0, description: map['description'] ?? '', sha256: map['sha256'] ?? '', ); @override String toString() { return 'AppInfo{appName: $appName, appVersion: $version, appUrl: $url, appSize: $size}'; } bool shouldUpgrade(String current) => needsUpdate(current, version); } bool needsUpdate( String oldVersion, String newVersion, { int versionParts = 3, // 默认三位版本号 String prefix = '', // 默认无前缀 }) { // 去除版本号前缀并将其解析为整数列表 List parseVersion(String version) { if (prefix.isNotEmpty && version.startsWith(prefix)) { version = version.substring(prefix.length); // 移除前缀 } final parts = version.split('.'); if (parts.length != versionParts) { throw FormatException( '版本号格式错误,应为包含 $versionParts 位版本号的格式,如 ${prefix}1.0.0'); } return parts.map(int.parse).toList(); } final oldParts = parseVersion(oldVersion); final newParts = parseVersion(newVersion); // 按位比较版本号 for (int i = 0; i < versionParts; i++) { if (newParts[i] > oldParts[i]) { return true; // 新版本号更大,需要更新 } else if (newParts[i] < oldParts[i]) { return false; // 新版本号更小,不需要更新 } } // 版本号相同,不需要更新 return false; } ================================================ FILE: modules/basic_system/fx_updater/lib/strategy/android_strategy.dart ================================================ import 'package:fx_install_plugin/install_plugin.dart'; import '../fx_updater.dart'; import 'download_mixin.dart'; import 'update_strategy.dart'; class AndroidUpdateStrategy with DownloadMixin implements UpdateStrategy { @override Future update(String url, OnProgressChange onProgress) async { String filePath = await downloadFile(url, onProgress); await InstallPlugin.installApk(filePath); } } ================================================ FILE: modules/basic_system/fx_updater/lib/strategy/desktop_strategy.dart ================================================ import 'package:url_launcher/url_launcher.dart'; import '../fx_updater.dart'; import 'download_mixin.dart'; import 'update_strategy.dart'; class DesktopUpdateStrategy with DownloadMixin implements UpdateStrategy { @override Future update(String url, OnProgressChange onProgress) async { String filePath = await downloadFile(url, onProgress); await launchUrl(Uri.file(filePath)); } } ================================================ FILE: modules/basic_system/fx_updater/lib/strategy/download_mixin.dart ================================================ import 'dart:io'; import 'package:fx_dio/fx_dio.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; import '../fx_updater.dart'; mixin DownloadMixin { Future downloadFile(String url, OnProgressChange onProgress) async { Dio dio = Dio(); Directory dir = await getTemporaryDirectory(); String filePath = p.join(dir.path, p.basename(url)); Response rep = await dio.download( url, filePath, onReceiveProgress: (received, total) { if (total != -1) { onProgress(received / total); } }, ); if (rep.statusCode == 200) { onProgress(1.0); return filePath; } throw Exception('Download failed'); } } ================================================ FILE: modules/basic_system/fx_updater/lib/strategy/macos_strategy.dart ================================================ import 'package:url_launcher/url_launcher.dart'; import '../fx_updater.dart'; import 'update_strategy.dart'; class MacOSUpdateStrategy implements UpdateStrategy { @override Future update(String url, OnProgressChange onProgress) async { await launchUrl(Uri.parse(url)); } } ================================================ FILE: modules/basic_system/fx_updater/lib/strategy/update_strategy.dart ================================================ import 'dart:io'; import '../fx_updater.dart'; import 'android_strategy.dart'; import 'desktop_strategy.dart'; import 'macos_strategy.dart'; abstract class UpdateStrategy { Future update(String url, OnProgressChange onProgress); } class UpdateStrategyFactory { static UpdateStrategy create() { if (Platform.isAndroid) { return AndroidUpdateStrategy(); } else if (Platform.isMacOS) { return MacOSUpdateStrategy(); } else if (kIsDesk) { return DesktopUpdateStrategy(); } throw UnsupportedError('Unsupported platform'); } } ================================================ FILE: modules/basic_system/fx_updater/lib/views/app_update_panel.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../bloc/bloc.dart'; import '../bloc/event.dart'; import '../bloc/state.dart'; import 'dialog/update_dialog.dart'; class AppUpdatePanel extends StatelessWidget { const AppUpdatePanel({super.key}); @override Widget build(BuildContext context) { return BlocConsumer( builder: _buildByUpdateState, listener: _listenerByUpdateState, ); } Widget _buildProgress(BuildContext context, double progress, int appSize) { return Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ Column( children: [ Text( '${(progress * 100).toStringAsFixed(2)} %', style: const TextStyle( height: 1, fontSize: 12, color: Colors.grey), ), const SizedBox( height: 5, ), Text( '${convertFileSize((appSize * progress).floor())}/${convertFileSize(appSize)}', style: const TextStyle( height: 1, fontSize: 10, color: Colors.grey), ), ], ), const SizedBox( width: 15, ), SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, backgroundColor: Colors.grey, value: progress, ), ) ]); } Widget _buildByUpdateState(BuildContext context, UpdateState state) { // String info = context.l10n.checkUpdate; String info = '检测更新'; Widget trail = const SizedBox.shrink(); if (state is ShouldUpdateState) { if (state.progress > 0) { info = '新版本下载中...'; trail = _buildProgress(context, state.progress, state.info.size); } else { info = '下载新版本'; trail = Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ Text( '${state.oldVersion} --> ${state.info.version} ', style: const TextStyle( height: 1, fontSize: 12, color: Colors.grey), ), const SizedBox(width: 5), const Icon(Icons.update, color: Colors.green) ]); } } if (state is CheckLoadingState) { trail = const CupertinoActivityIndicator(); } return ListTile( title: Text( info, style: const TextStyle(fontSize: 13), ), trailing: trail, onTap: () => _tapByState(state, context), ); } void _tapByState(UpdateState state, BuildContext context) { if (state is NoUpdateState) { String locale = Localizations.localeOf(context).toString(); context.read().add(CheckUpdate(appId: 1, locale: locale)); } if (state is ShouldUpdateState) { showDialog( barrierDismissible: false, context: context, builder: (ctx) => UpdateDialog( result: state.info, onConfirm: () { context .read() .add(DownloadEvent(appInfo: state.info)); }, )); // if(Platform.isIOS){ // // ios 跳转应用商店 // RUpgrade.upgradeFromAppStore('6450545123', false); // return; // } // // 处理下载的事件 // BlocProvider.of(context).add(DownloadEvent(appInfo: state.info)); } } void _listenerByUpdateState(BuildContext context, UpdateState state) { if (state is NoUpdateState) { if (state.isChecked) { // Toast.success(context, context.l10n.currentIsNew); } } // if (state is ShouldUpdateState) { // showDialog( // barrierDismissible: false, // context: context, // builder: (ctx) => FeiShuUpdateDialog( // result: state.info, // onConfirm: () {}, // )); // } } String convertFileSize(int size) { double result = size / 1024.0; if (result < 1024) { return "${result.toStringAsFixed(2)} Kb"; } else if (result > 1024 && result < 1024 * 1024) { return "${(result / 1024).toStringAsFixed(2)} Mb"; } else { return "${(result / 1024 / 1024).toStringAsFixed(2)} Gb"; } } } ================================================ FILE: modules/basic_system/fx_updater/lib/views/dialog/top_bar.dart ================================================ import 'package:flutter/material.dart'; class UpdateTopBar extends StatelessWidget { final String text; const UpdateTopBar({Key? key, required this.text}) : super(key: key); @override Widget build(BuildContext context) { return Container( height: 68, decoration: BoxDecoration( color: Colors.blue, gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment(0.8, 1), colors: [ Color(0xff4181b4), Color(0xff1fbcfd), Color(0xff46d1fd), ], // Gradient from https://learnui.design/tools/gradient-generator.html tileMode: TileMode.mirror, ), borderRadius: BorderRadius.only( topLeft: Radius.circular(8), topRight: Radius.circular(8))), alignment: Alignment.center, child: Text( text, style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold), ), ); } } ================================================ FILE: modules/basic_system/fx_updater/lib/views/dialog/update_dialog.dart ================================================ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../bloc/bloc.dart'; import '../../bloc/state.dart'; import '../../fx_updater.dart'; import '../../repository/model/app_info.dart'; import 'top_bar.dart'; class UpdateDialog extends StatefulWidget { final AppInfo result; final ValueChanged? onDownloadSuccess; final VoidCallback? onBackHide; final VoidCallback? onConfirm; const UpdateDialog({ super.key, required this.result, this.onDownloadSuccess, this.onBackHide, this.onConfirm, }); @override State createState() => _UpdateDialogState(); } class _UpdateDialogState extends State { final TextStyle noticeStyle = const TextStyle(color: Colors.grey, fontSize: 16); final TextStyle cancelTextStyle = const TextStyle(color: Colors.grey, fontSize: 16); final TextStyle subTextStyle = const TextStyle( color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold); @override Widget build(BuildContext context) { UpdateState state = context.watch().state; return WillPopScope( onWillPop: () async { // UpdateCubit.isShowDialog = false; return true; }, child: Dialog( elevation: 0, backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(8))), child: Container( height: 360, alignment: Alignment.topLeft, width: kIsDesk ? 400 : 320, // color: Colors.green, child: Column( mainAxisSize: MainAxisSize.min, // crossAxisAlignment: CrossAxisAlignment.start, children: [ //大小为 ${(widget.result.appSize!/1024/1024).toStringAsFixed(1)}M buildTitle(state), Expanded(child: buildContent(state)), buildButtons(state), ], ), ), ), ); } Widget buildContent(UpdateState state) { if (state is ShouldUpdateState) { if (state.progress > 0) { return downloadProgress(state.progress); } return _buildUpdateInfo(); } return const SizedBox.shrink(); } Widget downloadProgress(double progress) { return Center( child: Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( progress == 1 ? "文件校验中..." : '下载中...', style: TextStyle(height: 1, fontSize: 12, color: Colors.grey), ), const SizedBox( height: 10, ), LinearProgressIndicator( minHeight: 10, value: progress, ), const SizedBox( height: 10, ), Row( children: [ Text("${(progress * 100).toStringAsFixed(1)}%", style: TextStyle(height: 1, fontSize: 12, color: Colors.grey)), Spacer(), Text( "${convertFileSize(((widget.result.size) * (progress)).toInt())}/${convertFileSize(widget.result.size)}", style: TextStyle(height: 1, fontSize: 12, color: Colors.grey), ) ], ) ], ), ), ); } String convertFileSize(int? size) { if (size == null) return '0 KB'; double result = size / 1024.0; if (result < 1024) { return "${result.toStringAsFixed(2)} KB"; } else if (result > 1024 && result < 1024 * 1024) { return "${(result / 1024).toStringAsFixed(2)} MB"; } else { return "${(result / 1024 / 1024).toStringAsFixed(2)} GB"; } } Widget _buildUpdateInfo() { return SingleChildScrollView( child: Container( width: 500, padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '更新内容: ', style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox( height: 6, ), Text( widget.result.description ?? '', style: TextStyle(color: Colors.grey), ), ], ), ), ); } Widget buildTitle(UpdateState state) { String text = ''; if (state is ShouldUpdateState) { text = "闪讯 v${state.info.version} 准备就绪"; if (state.progress > 0) { text = "正在下载更新.."; } } return UpdateTopBar(text: text); } Widget buildButtons(UpdateState state) { if (state is ShouldUpdateState) { if (state.progress > 0) { // 下载中 return Row( children: [ Expanded( child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { // widget.onBackHide?.call(); Navigator.of(context).pop(); // UpdateCubit.isShowDialog = false; }, child: Container( alignment: Alignment.center, decoration: BoxDecoration( border: Border( top: BorderSide( color: Colors.grey.withAlpha(88), width: 1 / window.devicePixelRatio))), height: 50, child: Text( '后台执行', style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 16), ), ), )), ], ); } return Row( children: [ Expanded( child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { Navigator.of(context).pop(false); // UpdateCubit.isShowDialog = false; }, child: Container( decoration: BoxDecoration( border: Border( top: BorderSide( color: Colors.grey.withAlpha(88), width: 1 / window.devicePixelRatio), right: BorderSide( color: Colors.grey.withAlpha(88), width: 1 / window.devicePixelRatio))), alignment: Alignment.center, height: 50, child: Text( '稍后再说', style: cancelTextStyle, ), ), ), ), Expanded( child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { widget.onConfirm?.call(); }, child: Container( decoration: BoxDecoration( border: Border( top: BorderSide( color: Colors.grey.withAlpha(88), width: 1 / window.devicePixelRatio), right: BorderSide( color: Colors.grey.withAlpha(88), width: 1 / window.devicePixelRatio))), alignment: Alignment.center, height: 50, child: Text( '立即升级', style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 16), ), ), )), ], ); } return const SizedBox.shrink(); } void upgradeWindows() async { // context.read().doUpdate(); } } ================================================ FILE: modules/basic_system/fx_updater/lib/views/update_red_point.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../bloc/bloc.dart'; import '../bloc/state.dart'; class UpdateRedPoint extends StatelessWidget { const UpdateRedPoint({super.key}); @override Widget build(BuildContext context) { Widget radPoint = Container( width: 8, height: 8, decoration: const BoxDecoration(color: Colors.red, shape: BoxShape.circle), ); return BlocBuilder( builder: (BuildContext context, UpdateState state) { if (state is ShouldUpdateState) { return radPoint; } else { return const SizedBox.shrink(); } }, ); } } ================================================ FILE: modules/basic_system/fx_updater/lib/views/version_shower.dart ================================================ import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; class VersionShower extends StatefulWidget { const VersionShower({super.key}); @override _VersionShowerState createState() => _VersionShowerState(); } class _VersionShowerState extends State { String version = '1.0.0'; @override void initState() { super.initState(); _initVersion(); } @override Widget build(BuildContext context) { return Text('Version $version'); } void _initVersion() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); version = packageInfo.version; setState(() {}); } } ================================================ FILE: modules/basic_system/fx_updater/pubspec.yaml ================================================ name: fx_updater description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ^3.6.1 flutter: ">=1.17.0" dependencies: flutter: sdk: flutter flutter_bloc: ^9.1.1 # 状态管理 dio: ^5.7.0 convert: ^3.1.2 equatable: ^2.0.5 # 相等辅助 shared_preferences: ^2.5.1 # xml 固化 fx_install_plugin: 2.1.0 open_file: ^3.5.9 # 打开文件 url_launcher: ^6.3.0 # url package_info_plus: ^8.1.4 path_provider: ^2.1.5 fx_dio: 0.0.4+3 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/to/asset-from-package # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/to/font-from-package ================================================ FILE: modules/basic_system/fx_updater/test/fx_updater_test.dart ================================================ // import 'package:flutter_test/flutter_test.dart'; // // import 'package:fx_updater/fx_updater.dart'; // // void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); // } ================================================ FILE: modules/basic_system/l10n/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ build/ ================================================ FILE: modules/basic_system/l10n/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "67457e669f79e9f8d13d7a68fe09775fefbb79f4" channel: "stable" project_type: package ================================================ FILE: modules/basic_system/l10n/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/basic_system/l10n/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/basic_system/l10n/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/basic_system/l10n/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/basic_system/l10n/desiredFileName.txt ================================================ {} ================================================ FILE: modules/basic_system/l10n/l10n.yaml ================================================ arb-dir: lib/arb template-arb-file: app_zh.arb output-localization-file: app_localizations.dart synthetic-package: false output-dir: lib/gen_l10n output-class: AppLocalizations nullable-getter: false untranslated-messages-file: desiredFileName.txt ================================================ FILE: modules/basic_system/l10n/l10n_copy.sh ================================================ #!/bin/bash # 复制文件夹 cp -r .dart_tool/flutter_gen/gen_l10n lib # 检查拷贝是否成功 if [ $? -eq 0 ]; then echo "文件夹拷贝成功" else echo "文件夹拷贝失败" fi ================================================ FILE: modules/basic_system/l10n/lib/arb/app_en.arb ================================================ { "widgetCollection": "Widgets", "paintCollection": "Painter", "knowledgeCollection": "Knowledge", "homeAccount": "Application", "messageBoard": "Message Board", "homeAccountTabInfo": "About App", "homeAccountTabMe": "Contact Me", "homeAccountSupport": "Support Project", "collectCollection": "Collection", "essentialCollection": "KeypointsCollection", "treasureTools": "Treasure", "searchWidget": "search widget ...", "stateless":"Stateless", "stateful":"Stateful", "single":"Single", "multi":"Multi", "sliver":"Sliver", "proxy":"Proxy", "other":"Other", "homeTabWidget": "Widget", "homeTabPaint": "Paint", "homeTabKnowledge": "Knowledge", "homeTabTools": "Treasure", "homeTabMine": "Mine", "favorite":"Collected", "userCollection":"Collection", "appSettings":"Application Setting", "darkMode":"Dark Mode ", "themeColorSetting":"Theme Color", "fontSetting":"Font Setting", "settingLanguage": "Language Setting", "codeHighlightStyle":"Code Highlight Style", "versionInformation":"App Version", "showFloatingTools":"Show floating tools", "displayPerformanceFloatingLayer":"Performance Layer", "showBackground":"Display Background", "followSystem":"Follow system", "manualSetting":"Manual settings", "lightMode":"Light mode", "appDetails":"Application details", "settingLanguageText": "Setting Language", "checkUpdate":"Check New Version", "downloadNewVersion":"Download New Version", "downloadingNewVersion":"Downloading New Version ...", "currentIsNew":"There is the latest version of FlutterUnit!", "enterComponentName":"Input widget name", "containerComponents":"Container components", "relatedComponents":"Related Widgets", "componentTavern":"Component Tavern", "cherishedComponents":"Treasure components", "textImageCollection":"TextImageCollection", "layoutCollection":"LayoutCollection", "eventCollection":"EventCollection", "animationCollection":"AnimationCollection", "slidingCollection":"SlidingCollection", "decorationCollection":"DecorativeCollection", "assemblyCollection":"AssemblyCollection", "functionCollection":"FeatureCollection", "popupCollection":"Pop upCollection", "themeCollection":"ThemeCollection", "derivativeCollection":"DerivativeCollection", "hardToCategorize":"It's hard to distinguish", "basicDrawing":"Basic drawing", "animationGesture":"Animated gestures", "particleDrawing":"Particle drawing", "interestingDrawing":"Fun drawing", "artGallery":"Art galleries ", "drawingOfImages":"This example explains how to draw images: by loading images and drawing image resources to a specified area. Draw a batch of 45 \"angled grid lines on the upper layer to practice drawing the lines ", "digitalDisplayTube":"This example introduces how to draw LED digital display tubes to practice the use, transformation, combination of path paths, and knowledge of component packaging. It is a very good drawing case ", "pathDrawing":"This example introduces how to perform simple path drawing, rotate the drawing board, and combine animation to make the windmill rotate. This is a very concise case of combining drawing and animation. ", "gridCoordinateSystem":"This example explains how to use line diameter and text to draw a grid coordinate system, and encapsulate the drawn objects for easy reuse. The coordinate system also provides reference during drawing, which is essential for beginners.", "polarCoordinateSystemOfFaces":"This example explains how to use a polar coordinate system to draw a plane and collect polar coordinates based on a function equation for drawing. ", "drawFunctionCurvesForPathPairs":"This example explains how to use a path to draw a function curve, fitting a small number of points on the function curve through a Bessel curve. ", "drawRegularPolygons":"This example introduces how to collect points in a circle and draw regular polygons, which is a good example for practicing drawing and forming paths. \n Special operations:+, - Modify the number of edges", "randomNumberProcessing":"This example introduces drawing rectangles and handling random numbers. Determine the rectangular position information through a set of points and draw it. Can practice the ability to control data.", "clockDrawing":"This example uses the drawing of a clock to practice the drawing technique of rotating scale types in Flutter, and uses animation to rotate the dial pointer.", "drawSprings":" This example introduces how to draw a spring, stretch and compress it vertically through the contact points, and restore the animation when releasing it. It is a good comprehensive small case. Special operation: Drag the telescopic spring up and down ", "theApplicationOfAnglesInDrawing":"This example explains how to perform rotational motion based on a point as the center. Learn the application of the angle between two points in drawing. \n Special operation: Click to run", "usingShadersAndFilters":"This example explains how to use shaders and filters in painting, and achieve a rotating streamer effect through animation with numerical variations.", "pathDrawingFunctionCurve":"This example explains how to use path to draw function curves and use path measurement for animation", "thePathOfBingDwenDwen":"This sample will draw the path of the mascot Bing Dwen Dwen for the 2022 Beijing Winter Olympics and use path measurement for animation. \n Special operation: Click to run", "drawCubicBesselCurve":"This example introduces how to draw a cubic Bezier curve, determine whether a point is activated through the contacts, and use this to control the position of the point to achieve drag control effect. \n Special operation: Click on the drawing point, double-click to clear it", "theEffectOfAnimationCurve":"This example provides an intuitive way to examine the effect of animation curves, allowing everyone to have a deeper understanding of animation. \n Special operation: Click to run", "randomParticlesAndBoundaryBouncing":"This example introduces how to create random particles and handle boundary bounce logic, which is a great starting point for learning particle motion. Special operation: click to stop running ", "particleCollision":"This example introduces how to perform collision detection on a particle and split multiple particles, which is an interesting case. \n Special operation: Click Reset", "particle":"This example introduces using particles to represent images and animating them to achieve explosive effects. \nSpecial operation: Click to run", "rectangleAndRandomNumbers":"This example introduces drawing rectangles and handling random numbers. Determine the rectangular position information through a set of points and draw it. Can practice the ability to control data. \nSpecial operation: Click to randomly generate", "bingDwenDwen":"This example is to draw the shape of the mascot Bing Dwen Dwen for the 2022 Beijing Winter Olympics, from which you can learn knowledge such as path drawing and gradient colors.", "pufengInjectionTest":"This sample implements the testing process of the Pufeng needle injection test, estimating pi based on probability. You can learn some drawing tips and logical processing of data.", "ticTacToe":"This example combines important skills such as gestures, drawing, animation, and verification through the drawing and logical verification of the Chinese checkerboard, making it a very good case study. \n Special operation: Double click to reset", "tiledLines":"The root cause of this example comes from generateArchistry.com tiled-lines,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry", "joyDivision":"The root cause of this example comes from generateArchistry.com joy-division,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry", "cubicDisarray":"The root cause of this example comes from generateArchistry.com cubic-disarray,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry", "triangularMesh":"The root cause of this example comes from generateArchistry.com triangular-mesh,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry", "unDeuxTrois": "The root cause of this example comes from generateArchistry.com un-deux-trois,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry", "circlePacking":"The root cause of this example comes from generateArchistry.com circle-packing,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry", "hypnoticSquares":"The root cause of this example comes from generateArchistry.com hypnotic-squares,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry", "pietMondrian":"The root cause of this example comes from generateArchistry.com piet-mondrian,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry", "downloadCompressedPackage":"Usage: \n1. Select the icon in iconfont.cn, add the project, and download the compressed file. \n2. Select the Flutter project address, configure resource and product file locations. \n3.Click the Generate Code button to generate the relevant code.", "qAIssues":"The QA data in the key points collection is included in FlutterUnit's issues labeled with points. If data needs to be provided, simply ask and answer in the issues section.", "tips":"tips:", "visualSorting":"Visual sorting", "visual":"Visual sorting", "insertion": "Insert sorting", "bubble": "Bubble sorting", "cocktail": "Cocktail sorting (bidirectional bubble sorting)", "comb": "Comb sorting", "pigeonHole": "Pigeonhole sorting ", "shell": "Shell sorting ", "selection": "Select sorting", "gnome": "Dwarf Sorting", "cycle": "Circular sorting", "heap": "Heap sorting", "quick": "Quick sorting", "merge": "Merge sorting", "sortingAlgorithmConfiguration":"Sorting algorithm configuration", "dataCount":"Data quantity (number)", "timeInterval":"Time interval (microseconds)", "randomSeed":"Random Seed", "codeGeneration":"Code generation", "generateCode":"Generate Code", "artifactLocation":"Product location", "codeClassLocation":"Code class storage location", "resourceDirectory":"Resource Catalog", "iconfontResourceLocation":"iconfont Resource storage location", "projectPath":"Project Path", "inputProjectAddress":"Please select or enter the project address", "iconfontCompressedPackagePath":"Iconfont Compressed package path", "pleaseSelectOrInputIconfontCompressedPackagePath":"Please select or enter the compressed file path for iconfont download", "stayTuned":"Stay tuned", "iconFont":"IconFont", "dataClass":"Data class", "stateManagement":"State management", "jsonParsing":"Json Parsing", "clickHereToJump":"Click here to jump to", "knowledgeTabToly":"Toly Articles", "knowledgeTabAlgo":"Algo Player", "knowledgeTabLayout":"Layout Treasury", "knowledgeTabPoint":"Key Points", "knowledgeConstruction":"In Construction", "knowledgeToJuejin":"To Juejin", "srcPath":"Source Path", "widgetsInn":"Widgets Inn", "likedWidgets":"Liked Widgets", "afterOpeningWillFollowTheSystemToOpenOrCloseDarkMode":"After activation, it will follow the system to turn on or off dark mode", "basicDrawingDesc":"Including some basic graphics drawing examples would be very friendly to beginners in programming. Through these examples, one can learn how to draw basic shapes such as points, lines, rectangles, circles, arcs, text, images, etc., and understand the usage of core objects in drawing like Canvas, Paint, Path, etc.", "animationGestureDesc":"Includes some drawing examples of animation and gestures, which make drawing more interactive. Through these examples, one can learn the usage of animation and gestures, such as sliding, rotating, scaling, moving effects, etc., making drawing not just static presentation.", "particleDrawingDesc": "Includes some drawing examples related to particles, which are top-level operations in drawing. Through these examples, one can learn how to use particles to create stunning visual effects, such as particle clocks, particle explosions, particle backgrounds, etc., giving drawing endless possibilities.", "interestingDrawingDesc": "Includes some fun drawing examples, let's experience the joy of drawing, programming, and intelligence together here.", "artGalleryDesc": "Includes some hall-level drawing examples, which are pinnacle works of drawing. They have no practicality and are not born for any demand. They exist only because they exist, serving as a medium for human wisdom and expression, called art.", "checkDatabaseNewVersion":"Check for new versions of the database", "viewThisProjectGithubRepository":"《View the Github Repository for this project》", "dataManagement":"Data management", "backupFavoritesCollectionData":"Backup Collection Data", "syncFavoritesCollectionData":"Synchronize collection data", "favoritesCollectionDataReset":"Reset Collection Data", "resetSuccess":"Reset successful!", "dataSetBackupSuccess":"Dataset backup successful!", "dataSetBackupFailure":"Dataset backup failed!", "dataSynchronizationCopySuccess":"Data synchronization successful!", "dataSynchronizationCopyFailure":"Data synchronization failed!", "destructionRed":"Destruction Red ", "rageOrange":"Anger Orange", "warningYellow":"Warning Yellow", "camouflageGreen":"Disguising Green", "coldBlue":"Indifferent Blue", "infiniteBlue":"Infinite Indigo", "mysteryPurple":"Mysterious Purple", "destinyBlack":"Destiny Black", "toly":"toly", "dartHandbook":"Dart Handbook", "aboutApplications":"About Applications", "contactThisKing":"Contact this king", "codeCopiedSuccessfully":"Code copied successfully", "favoriteFolderManagement":"Favorite folder management", "assembly":"Assembly", "draw":"Draw", "knowledge":"Knowledge", "collection":"Collection", "my":"My", "picture":"pics", "widgetInn":"Widget Collection", "emptySearch":"No Result \n(≡ _ ≡)/~┴┴", "searchSomething":"Search Something ≧◔◡◔≦", "slogan":"The unity of flutter, The unity of coder." } ================================================ FILE: modules/basic_system/l10n/lib/arb/app_zh.arb ================================================ { "widgetCollection": "组件集录", "paintCollection": "绘制集录", "knowledgeCollection": "知识集锦", "treasureTools": "工具宝箱", "collectCollection": "收藏集录", "essentialCollection": "要点集录", "homeAccount": "应用信息", "homeAccountTabInfo": "关于应用", "messageBoard": "留言板", "homeAccountTabMe": "联系本王", "homeAccountSupport": "支持项目", "searchWidget": "搜索组件", "stateless":"无态", "stateful":"有态", "single":"单渲", "multi":"多渲", "sliver":"滑片", "proxy":"代理", "other":"其他", "homeTabWidget": "组件", "homeTabPaint": "绘制", "homeTabKnowledge": "知识", "homeTabTools": "工具", "homeTabMine": "我的", "dataManagement":"数据管理", "userCollection":"我的收藏", "aboutApplications":"关于应用", "contactThisKing":"联系本王", "appSettings":"应用设置", "darkMode":"深色模式", "themeColorSetting":"主题色设置", "fontSetting":"字体设置", "settingLanguageText": "多语言", "codeHighlightStyle":"代码高亮样式", "versionInformation":"版本信息", "displayPerformanceFloatingLayer":"显示性能浮层", "showFloatingTools":"显示浮动工具", "followSystem":"跟随系统", "afterOpeningWillFollowTheSystemToOpenOrCloseDarkMode":"开启后,将跟随系统打开或关闭深色模式", "manualSetting":"手动设置", "lightMode":"浅色模式", "settingLanguage": "设置语言", "appDetails":"应用详情", "checkUpdate":"检查新版本", "downloadNewVersion":"下载新版本", "downloadingNewVersion":"新版本下载中...", "currentIsNew":"当前应用已是最新版本!", "checkDatabaseNewVersion":"检查数据库新版本", "viewThisProjectGithubRepository":"《查看本项目Github仓库》", "favorite":"已收藏", "enterComponentName":"输入组件名称", "containerComponents":"容器组件", "componentTavern":"组件酒肆", "cherishedComponents":"珍藏组件", "textImageCollection":"图文集", "layoutCollection":"布局集", "eventCollection":"事件集", "animationCollection":"动画集", "slidingCollection":"滑动集", "decorationCollection":"装饰集", "assemblyCollection":"组装集", "functionCollection":"功能集", "popupCollection":"弹出集", "themeCollection":"主题集", "derivativeCollection":"衍生集", "hardToCategorize":"很难分", "basicDrawing":"基础绘制", "basicDrawingDesc":"收录一些基础图形绘制案例,这些案例对初涉绘制的编程者会非常友好。通过这些案例,可以学会点、线、矩形、圆、圆弧、文字、图片等基本图形的绘制方法,了解 Canvas、Paint、Path 等绘制中核心对象的使用。", "animationGesture":"动画手势", "animationGestureDesc":"收录一些动画和手势的绘制案例,这些案例会让绘制更具有操作性。通过这些案例,可以学会动画和手势的使用,如滑动、旋转、缩放、移动等效果,让绘制不再只是静态展现。", "particleDrawing":"粒子绘制", "particleDrawingDesc":"收录一些粒子相关的绘制案例,这些案例将是绘制的顶级操作。通过这些案例,可以学会如何使用粒子来绘制惊艳的视觉效果,如粒子时钟、粒子爆炸、粒子背景等效果,让绘制拥有无限可能。", "interestingDrawing":"趣味绘制", "interestingDrawingDesc":"收录一些比较有趣的绘制案例,让我们一起在这里一起体验绘制的乐趣、编程的乐趣和智慧的乐趣吧。", "artGallery":"艺术画廊", "artGalleryDesc":"收录一些殿堂级的绘制案例,这些案例将是绘制的巅峰作品,它们的没有任何的实用性,也不为任何需求而生,它们仅是因为存在而存在,是人类智慧和表达的媒介,称谓艺术。", "drawingOfImages":"本样例介绍如何进行图片的绘制: 通过加载图片并将图片资源绘制到指定的区域。在上层绘制一批 45”倾角的栅格线,来练习线条的绘制 ", "digitalDisplayTube":"本样例介绍如何绘制 LED 数字显示管,以此练习对路径 Path 的使用、变换、组合,以及组件封装的知识。是一个非常好的绘制案例 ", "pathDrawing":"本样例介绍如何进行简单的路径绘制,以及画板的旋转,再结合动画让风车旋转。这是一个非常精简的绘制与动画结合的案例。 ", "gridCoordinateSystem":"本样例介绍如何使用线路径和文字绘制网格坐标系,并将绘制对象进行封装,方便重用。坐标系也会在绘制时提供参考,入门必备。 ", "polarCoordinateSystemOfFaces":"本样例介绍如何使用绘制平面的极坐标系,并根据函数方程收集极坐标进行绘制。 ", "drawFunctionCurvesForPathPairs":"本样例介绍如何使用路径对函数曲线进行绘制,通过函数曲线上的少量点通过贝塞尔曲线进行拟合。 ", "drawRegularPolygons":"本样例介绍如何在圆中收集点位,绘制正多边形,是练习绘制及形成路径的很好案例。\n特殊操作:+、- 修改边数", "randomNumberProcessing":"本样例介绍绘制矩形及随机数处理。通过点位集合确定矩形位置信息,将其绘制出来。可以练习对数据的控制能力。", "clockDrawing":"本样例通过时钟的绘制,练习 Flutter 中旋转刻度类型的绘制技巧,并通过动画使表盘指针转动。", "drawSprings":" 本样例介绍如何绘制弹簧,通过触点竖直拖拽拉伸、压缩,放手时进行恢复动画,是一个很好的综合小案例。特殊操作:上下拖拽伸缩弹簧 ", "theApplicationOfAnglesInDrawing":"本样例介绍如何根据以某个点为中心,进行旋转运动。以此学习两点间的角度在绘制中的应用。\n特殊操作:点击运行", "usingShadersAndFilters":"本样例介绍如何在绘制中使用着色器和过滤器,并通过动画进行数值变化达到旋转流光效果。", "pathDrawingFunctionCurve":"本样例介绍如何使用路径绘制函数曲线,并使用路径测量进行动画", "thePathOfBingDwenDwen":"本样例会绘制 2022 年北京冬奥会吉祥物冰墩墩的路径,并使用路径测量进行动画。\n特殊操作:点击运行", "drawCubicBesselCurve":"本样例介绍如何绘制三次贝塞尔曲线,通过触点判断某点是否激活,据此控制点的位置达到拖动控制效果。\n特殊操作:单击绘点,双击清除", "theEffectOfAnimationCurve":"本样例通过直观的方式,来查看动画曲线 curve 的作用效果,让大家对动画有更深的理解。\n特殊操作:点击运行", "randomParticlesAndBoundaryBouncing":" 本样例介绍如何创建随机粒子及边界反弹逻辑处理,是学习粒子运动非常好的入门案例特殊操作:单击停止/运行 ", "particleCollision":"本样例介绍如何对个粒子进行碰撞检测,并分裂处多个粒子,是一个比较有趣的案例。\n特殊操作:单击重置", "particle":"本样例介绍将图片使用粒子表示,并对粒子进行动画处理,达到爆炸的效果。\n特殊操作:单击运行", "rectangleAndRandomNumbers":"本样例介绍绘制矩形及随机数处理。通过点位集合确定矩形位置信息,将其绘制出来。可以练习对数据的控制能力。\n特殊操作:点击随机生成", "bingDwenDwen":"本样例是绘制 2022 年北京冬奥会吉祥物冰墩墩的形体,从中可以学到路径绘制、渐变色等知识。", "pufengInjectionTest":"本样实现蒲丰投针试验的测试过程,根据概率来估算圆周率。其中可以学习到一些绘制小技巧已经数据的逻辑处理。", "ticTacToe":"本例通过井字棋的绘制与逻辑校验,集合了手势、绘制、动画、校验等重要的技能,是一个非常好的联系案例。\n特殊操作:双击重置", "tiledLines":"本样例根源来自generativeartistry.com的tiled-lines,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry", "joyDivision":"本样例根源来自generativeartistry.com的joy-division,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry", "cubicDisarray":"本样例根源来自generativeartistry.com的cubic-disarray,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry", "triangularMesh":"本样例根源来自generativeartistry.com的triangular-mesh,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry", "unDeuxTrois": "本样例根源来自generativeartistry.com的un-deux-trois,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry", "circlePacking":"本样例根源来自generativeartistry.com的circle-packing,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry", "hypnoticSquares":"本样例根源来自generativeartistry.com的hypnotic-squares,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry", "pietMondrian":"本样例根源来自generativeartistry.com的piet-mondrian,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry", "downloadCompressedPackage":"使用方式:\n1. 在 iconfont.cn 挑选图标,加入项目,下载压缩包。\n2. 选择 Flutter 项目地址,配置资源、产物文件位置。\n3. 点击生成代码按钮,即可生成相关代码。", "qAIssues":"要点集录中的 QA 数据收录rUnit 以 point 为标签的 issues 中。如果需要提供数据,在 issues 中问答即可。", "tips":"注:", "visualSorting":"可视化排序", "visual":"可视排序", "insertion": "插入排序", "bubble": "冒泡排序", "cocktail": "鸡尾酒排序(双向冒泡排序)", "comb": "梳排序", "pigeonHole": "鸽巢排序", "shell": "希尔排序", "selection": "选择排序", "gnome": "侏儒排序", "cycle": "循环排序", "heap": "堆排序", "quick": "快速排序", "merge": "归并排序", "sortingAlgorithmConfiguration":"排序算法配置", "dataCount":"数据数量(个数)", "timeInterval":"时间间隔(微秒)", "randomSeed":"随机种子", "codeGeneration":"代码生成", "generateCode":"生成代码", "artifactLocation":"产物位置", "codeClassLocation":"代码类存放位置", "resourceDirectory":"资源目录", "iconfontResourceLocation":"iconfont 资源存放位置", "projectPath":"项目路径", "inputProjectAddress":"请选择或输入项目地址", "iconfontCompressedPackagePath":"Iconfont 压缩包路径", "pleaseSelectOrInputIconfontCompressedPackagePath":"请选择或输入 iconfont 下载的压缩包路径", "stayTuned":"敬请期待", "iconFont":"IconFont", "dataClass":"数据类", "stateManagement":"状态管理", "jsonParsing":"Json 解析", "clickHereToJump":"点击这里跳转", "knowledgeTabToly":"捷特文库", "knowledgeTabAlgo":"算法演绎", "knowledgeTabLayout":"布局宝库", "knowledgeTabPoint":"要点宝库", "knowledgeConstruction":"正在建设中", "knowledgeToJuejin":"前往掘金", "srcPath":"源码地址", "widgetsInn":"组件酒肆", "likedWidgets":"珍藏组件", "relatedComponents":"相关组件", "backupFavoritesCollectionData":"备份收藏集数据", "syncFavoritesCollectionData":"同步收藏集数据", "favoritesCollectionDataReset":"收藏集数据重置", "resetSuccess":"重置成功!", "dataSetBackupSuccess":"数据集备份成功!", "dataSetBackupFailure":"数据集备份失败!", "dataSynchronizationCopySuccess":"数据同步份成功!", "dataSynchronizationCopyFailure":"数据同步份失败!", "destructionRed":"毁灭之红", "rageOrange":"愤怒之橙", "warningYellow":"警告之黄", "camouflageGreen":"伪装之绿", "coldBlue":"冷漠之蓝", "infiniteBlue":"无限之靛", "mysteryPurple":"神秘之紫", "destinyBlack":"归宿之黑", "showBackground":"显示背景", "toly":"张风捷特烈", "dartHandbook":"Dart 手册", "codeCopiedSuccessfully":"代码复制成功", "favoriteFolderManagement":"收藏夹管理", "assembly":"组件", "draw":"绘制", "knowledge":"知识", "collection":"收藏", "my":"我的", "picture":"幅", "widgetInn":"组件酒肆", "emptySearch":"没数据,哥也没办法\n(≡ _ ≡)/~┴┴", "searchSomething":"哥们,搜点啥...≧◔◡◔≦", "slogan":"Flutter 的联合,编程者的联合" } ================================================ FILE: modules/basic_system/l10n/lib/enum/language.dart ================================================ import 'dart:ui'; enum Language { zh_CN(locale: Locale('zh', 'CN'), label: '简体中文'), en_US(locale: Locale('en', 'US'), label: 'English'), // ru_RU(locale: Locale('ru','RU'), label: 'Русский'), // fr_FR(locale: Locale('fr','FR'), label: 'Français'), // ko_KR(locale: Locale('ko','KR'), label: '한국어'), // de_DE(locale: Locale('de','DE'), label: 'Deutsch'), // ja_JP(locale: Locale('ja','JP'), label: '日本語'), // it_IT(locale: Locale('it','IT'), label: 'Italiano'), // pt_PT(locale: Locale('pt','PT'), label: 'Português'), // es_ES(locale: Locale('es','ES'), label: 'Español'), ; String get code => '${locale.languageCode}-${locale.countryCode}'.toLowerCase(); final Locale locale; final String label; bool get isZh => this == Language.zh_CN; const Language({ required this.locale, required this.label, }); } ================================================ FILE: modules/basic_system/l10n/lib/ext.dart ================================================ import 'package:flutter/material.dart'; import 'gen_l10n/app_localizations.dart'; export 'gen_l10n/app_localizations.dart' show AppLocalizations; const l10nDelegates = AppLocalizations.localizationsDelegates; const l10nLocales = AppLocalizations.supportedLocales; extension AppLocalizationsX on BuildContext { AppLocalizations get l10n => AppLocalizations.of(this); } ================================================ FILE: modules/basic_system/l10n/lib/gen_l10n/app_localizations.dart ================================================ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:intl/intl.dart' as intl; import 'app_localizations_en.dart'; import 'app_localizations_zh.dart'; // ignore_for_file: type=lint /// Callers can lookup localized strings with an instance of AppLocalizations /// returned by `AppLocalizations.of(context)`. /// /// Applications need to include `AppLocalizations.delegate()` in their app's /// `localizationDelegates` list, and the locales they support in the app's /// `supportedLocales` list. For example: /// /// ```dart /// import 'gen_l10n/app_localizations.dart'; /// /// return MaterialApp( /// localizationsDelegates: AppLocalizations.localizationsDelegates, /// supportedLocales: AppLocalizations.supportedLocales, /// home: MyApplicationHome(), /// ); /// ``` /// /// ## Update pubspec.yaml /// /// Please make sure to update your pubspec.yaml to include the following /// packages: /// /// ```yaml /// dependencies: /// # Internationalization support. /// flutter_localizations: /// sdk: flutter /// intl: any # Use the pinned version from flutter_localizations /// /// # Rest of dependencies /// ``` /// /// ## iOS Applications /// /// iOS applications define key application metadata, including supported /// locales, in an Info.plist file that is built into the application bundle. /// To configure the locales supported by your app, you’ll need to edit this /// file. /// /// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. /// Then, in the Project Navigator, open the Info.plist file under the Runner /// project’s Runner folder. /// /// Next, select the Information Property List item, select Add Item from the /// Editor menu, then select Localizations from the pop-up menu. /// /// Select and expand the newly-created Localizations item then, for each /// locale your application supports, add a new item and select the locale /// you wish to add from the pop-up menu in the Value field. This list should /// be consistent with the languages listed in the AppLocalizations.supportedLocales /// property. abstract class AppLocalizations { AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; static AppLocalizations of(BuildContext context) { return Localizations.of(context, AppLocalizations)!; } static const LocalizationsDelegate delegate = _AppLocalizationsDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. /// /// Returns a list of localizations delegates containing this delegate along with /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, /// and GlobalWidgetsLocalizations.delegate. /// /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. static const List> localizationsDelegates = >[ delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ]; /// A list of this localizations delegate's supported locales. static const List supportedLocales = [ Locale('en'), Locale('zh') ]; /// No description provided for @widgetCollection. /// /// In zh, this message translates to: /// **'组件集录'** String get widgetCollection; /// No description provided for @paintCollection. /// /// In zh, this message translates to: /// **'绘制集录'** String get paintCollection; /// No description provided for @knowledgeCollection. /// /// In zh, this message translates to: /// **'知识集锦'** String get knowledgeCollection; /// No description provided for @treasureTools. /// /// In zh, this message translates to: /// **'工具宝箱'** String get treasureTools; /// No description provided for @collectCollection. /// /// In zh, this message translates to: /// **'收藏集录'** String get collectCollection; /// No description provided for @essentialCollection. /// /// In zh, this message translates to: /// **'要点集录'** String get essentialCollection; /// No description provided for @homeAccount. /// /// In zh, this message translates to: /// **'应用信息'** String get homeAccount; /// No description provided for @homeAccountTabInfo. /// /// In zh, this message translates to: /// **'关于应用'** String get homeAccountTabInfo; /// No description provided for @messageBoard. /// /// In zh, this message translates to: /// **'留言板'** String get messageBoard; /// No description provided for @homeAccountTabMe. /// /// In zh, this message translates to: /// **'联系本王'** String get homeAccountTabMe; /// No description provided for @homeAccountSupport. /// /// In zh, this message translates to: /// **'支持项目'** String get homeAccountSupport; /// No description provided for @searchWidget. /// /// In zh, this message translates to: /// **'搜索组件'** String get searchWidget; /// No description provided for @stateless. /// /// In zh, this message translates to: /// **'无态'** String get stateless; /// No description provided for @stateful. /// /// In zh, this message translates to: /// **'有态'** String get stateful; /// No description provided for @single. /// /// In zh, this message translates to: /// **'单渲'** String get single; /// No description provided for @multi. /// /// In zh, this message translates to: /// **'多渲'** String get multi; /// No description provided for @sliver. /// /// In zh, this message translates to: /// **'滑片'** String get sliver; /// No description provided for @proxy. /// /// In zh, this message translates to: /// **'代理'** String get proxy; /// No description provided for @other. /// /// In zh, this message translates to: /// **'其他'** String get other; /// No description provided for @homeTabWidget. /// /// In zh, this message translates to: /// **'组件'** String get homeTabWidget; /// No description provided for @homeTabPaint. /// /// In zh, this message translates to: /// **'绘制'** String get homeTabPaint; /// No description provided for @homeTabKnowledge. /// /// In zh, this message translates to: /// **'知识'** String get homeTabKnowledge; /// No description provided for @homeTabTools. /// /// In zh, this message translates to: /// **'工具'** String get homeTabTools; /// No description provided for @homeTabMine. /// /// In zh, this message translates to: /// **'我的'** String get homeTabMine; /// No description provided for @dataManagement. /// /// In zh, this message translates to: /// **'数据管理'** String get dataManagement; /// No description provided for @userCollection. /// /// In zh, this message translates to: /// **'我的收藏'** String get userCollection; /// No description provided for @aboutApplications. /// /// In zh, this message translates to: /// **'关于应用'** String get aboutApplications; /// No description provided for @contactThisKing. /// /// In zh, this message translates to: /// **'联系本王'** String get contactThisKing; /// No description provided for @appSettings. /// /// In zh, this message translates to: /// **'应用设置'** String get appSettings; /// No description provided for @darkMode. /// /// In zh, this message translates to: /// **'深色模式'** String get darkMode; /// No description provided for @themeColorSetting. /// /// In zh, this message translates to: /// **'主题色设置'** String get themeColorSetting; /// No description provided for @fontSetting. /// /// In zh, this message translates to: /// **'字体设置'** String get fontSetting; /// No description provided for @settingLanguageText. /// /// In zh, this message translates to: /// **'多语言'** String get settingLanguageText; /// No description provided for @codeHighlightStyle. /// /// In zh, this message translates to: /// **'代码高亮样式'** String get codeHighlightStyle; /// No description provided for @versionInformation. /// /// In zh, this message translates to: /// **'版本信息'** String get versionInformation; /// No description provided for @displayPerformanceFloatingLayer. /// /// In zh, this message translates to: /// **'显示性能浮层'** String get displayPerformanceFloatingLayer; /// No description provided for @showFloatingTools. /// /// In zh, this message translates to: /// **'显示浮动工具'** String get showFloatingTools; /// No description provided for @followSystem. /// /// In zh, this message translates to: /// **'跟随系统'** String get followSystem; /// No description provided for @afterOpeningWillFollowTheSystemToOpenOrCloseDarkMode. /// /// In zh, this message translates to: /// **'开启后,将跟随系统打开或关闭深色模式'** String get afterOpeningWillFollowTheSystemToOpenOrCloseDarkMode; /// No description provided for @manualSetting. /// /// In zh, this message translates to: /// **'手动设置'** String get manualSetting; /// No description provided for @lightMode. /// /// In zh, this message translates to: /// **'浅色模式'** String get lightMode; /// No description provided for @settingLanguage. /// /// In zh, this message translates to: /// **'设置语言'** String get settingLanguage; /// No description provided for @appDetails. /// /// In zh, this message translates to: /// **'应用详情'** String get appDetails; /// No description provided for @checkUpdate. /// /// In zh, this message translates to: /// **'检查新版本'** String get checkUpdate; /// No description provided for @downloadNewVersion. /// /// In zh, this message translates to: /// **'下载新版本'** String get downloadNewVersion; /// No description provided for @downloadingNewVersion. /// /// In zh, this message translates to: /// **'新版本下载中...'** String get downloadingNewVersion; /// No description provided for @currentIsNew. /// /// In zh, this message translates to: /// **'当前应用已是最新版本!'** String get currentIsNew; /// No description provided for @checkDatabaseNewVersion. /// /// In zh, this message translates to: /// **'检查数据库新版本'** String get checkDatabaseNewVersion; /// No description provided for @viewThisProjectGithubRepository. /// /// In zh, this message translates to: /// **'《查看本项目Github仓库》'** String get viewThisProjectGithubRepository; /// No description provided for @favorite. /// /// In zh, this message translates to: /// **'已收藏'** String get favorite; /// No description provided for @enterComponentName. /// /// In zh, this message translates to: /// **'输入组件名称'** String get enterComponentName; /// No description provided for @containerComponents. /// /// In zh, this message translates to: /// **'容器组件'** String get containerComponents; /// No description provided for @componentTavern. /// /// In zh, this message translates to: /// **'组件酒肆'** String get componentTavern; /// No description provided for @cherishedComponents. /// /// In zh, this message translates to: /// **'珍藏组件'** String get cherishedComponents; /// No description provided for @textImageCollection. /// /// In zh, this message translates to: /// **'图文集'** String get textImageCollection; /// No description provided for @layoutCollection. /// /// In zh, this message translates to: /// **'布局集'** String get layoutCollection; /// No description provided for @eventCollection. /// /// In zh, this message translates to: /// **'事件集'** String get eventCollection; /// No description provided for @animationCollection. /// /// In zh, this message translates to: /// **'动画集'** String get animationCollection; /// No description provided for @slidingCollection. /// /// In zh, this message translates to: /// **'滑动集'** String get slidingCollection; /// No description provided for @decorationCollection. /// /// In zh, this message translates to: /// **'装饰集'** String get decorationCollection; /// No description provided for @assemblyCollection. /// /// In zh, this message translates to: /// **'组装集'** String get assemblyCollection; /// No description provided for @functionCollection. /// /// In zh, this message translates to: /// **'功能集'** String get functionCollection; /// No description provided for @popupCollection. /// /// In zh, this message translates to: /// **'弹出集'** String get popupCollection; /// No description provided for @themeCollection. /// /// In zh, this message translates to: /// **'主题集'** String get themeCollection; /// No description provided for @derivativeCollection. /// /// In zh, this message translates to: /// **'衍生集'** String get derivativeCollection; /// No description provided for @hardToCategorize. /// /// In zh, this message translates to: /// **'很难分'** String get hardToCategorize; /// No description provided for @basicDrawing. /// /// In zh, this message translates to: /// **'基础绘制'** String get basicDrawing; /// No description provided for @basicDrawingDesc. /// /// In zh, this message translates to: /// **'收录一些基础图形绘制案例,这些案例对初涉绘制的编程者会非常友好。通过这些案例,可以学会点、线、矩形、圆、圆弧、文字、图片等基本图形的绘制方法,了解 Canvas、Paint、Path 等绘制中核心对象的使用。'** String get basicDrawingDesc; /// No description provided for @animationGesture. /// /// In zh, this message translates to: /// **'动画手势'** String get animationGesture; /// No description provided for @animationGestureDesc. /// /// In zh, this message translates to: /// **'收录一些动画和手势的绘制案例,这些案例会让绘制更具有操作性。通过这些案例,可以学会动画和手势的使用,如滑动、旋转、缩放、移动等效果,让绘制不再只是静态展现。'** String get animationGestureDesc; /// No description provided for @particleDrawing. /// /// In zh, this message translates to: /// **'粒子绘制'** String get particleDrawing; /// No description provided for @particleDrawingDesc. /// /// In zh, this message translates to: /// **'收录一些粒子相关的绘制案例,这些案例将是绘制的顶级操作。通过这些案例,可以学会如何使用粒子来绘制惊艳的视觉效果,如粒子时钟、粒子爆炸、粒子背景等效果,让绘制拥有无限可能。'** String get particleDrawingDesc; /// No description provided for @interestingDrawing. /// /// In zh, this message translates to: /// **'趣味绘制'** String get interestingDrawing; /// No description provided for @interestingDrawingDesc. /// /// In zh, this message translates to: /// **'收录一些比较有趣的绘制案例,让我们一起在这里一起体验绘制的乐趣、编程的乐趣和智慧的乐趣吧。'** String get interestingDrawingDesc; /// No description provided for @artGallery. /// /// In zh, this message translates to: /// **'艺术画廊'** String get artGallery; /// No description provided for @artGalleryDesc. /// /// In zh, this message translates to: /// **'收录一些殿堂级的绘制案例,这些案例将是绘制的巅峰作品,它们的没有任何的实用性,也不为任何需求而生,它们仅是因为存在而存在,是人类智慧和表达的媒介,称谓艺术。'** String get artGalleryDesc; /// No description provided for @drawingOfImages. /// /// In zh, this message translates to: /// **'本样例介绍如何进行图片的绘制: 通过加载图片并将图片资源绘制到指定的区域。在上层绘制一批 45”倾角的栅格线,来练习线条的绘制 '** String get drawingOfImages; /// No description provided for @digitalDisplayTube. /// /// In zh, this message translates to: /// **'本样例介绍如何绘制 LED 数字显示管,以此练习对路径 Path 的使用、变换、组合,以及组件封装的知识。是一个非常好的绘制案例 '** String get digitalDisplayTube; /// No description provided for @pathDrawing. /// /// In zh, this message translates to: /// **'本样例介绍如何进行简单的路径绘制,以及画板的旋转,再结合动画让风车旋转。这是一个非常精简的绘制与动画结合的案例。 '** String get pathDrawing; /// No description provided for @gridCoordinateSystem. /// /// In zh, this message translates to: /// **'本样例介绍如何使用线路径和文字绘制网格坐标系,并将绘制对象进行封装,方便重用。坐标系也会在绘制时提供参考,入门必备。 '** String get gridCoordinateSystem; /// No description provided for @polarCoordinateSystemOfFaces. /// /// In zh, this message translates to: /// **'本样例介绍如何使用绘制平面的极坐标系,并根据函数方程收集极坐标进行绘制。 '** String get polarCoordinateSystemOfFaces; /// No description provided for @drawFunctionCurvesForPathPairs. /// /// In zh, this message translates to: /// **'本样例介绍如何使用路径对函数曲线进行绘制,通过函数曲线上的少量点通过贝塞尔曲线进行拟合。 '** String get drawFunctionCurvesForPathPairs; /// No description provided for @drawRegularPolygons. /// /// In zh, this message translates to: /// **'本样例介绍如何在圆中收集点位,绘制正多边形,是练习绘制及形成路径的很好案例。\n特殊操作:+、- 修改边数'** String get drawRegularPolygons; /// No description provided for @randomNumberProcessing. /// /// In zh, this message translates to: /// **'本样例介绍绘制矩形及随机数处理。通过点位集合确定矩形位置信息,将其绘制出来。可以练习对数据的控制能力。'** String get randomNumberProcessing; /// No description provided for @clockDrawing. /// /// In zh, this message translates to: /// **'本样例通过时钟的绘制,练习 Flutter 中旋转刻度类型的绘制技巧,并通过动画使表盘指针转动。'** String get clockDrawing; /// No description provided for @drawSprings. /// /// In zh, this message translates to: /// **' 本样例介绍如何绘制弹簧,通过触点竖直拖拽拉伸、压缩,放手时进行恢复动画,是一个很好的综合小案例。特殊操作:上下拖拽伸缩弹簧 '** String get drawSprings; /// No description provided for @theApplicationOfAnglesInDrawing. /// /// In zh, this message translates to: /// **'本样例介绍如何根据以某个点为中心,进行旋转运动。以此学习两点间的角度在绘制中的应用。\n特殊操作:点击运行'** String get theApplicationOfAnglesInDrawing; /// No description provided for @usingShadersAndFilters. /// /// In zh, this message translates to: /// **'本样例介绍如何在绘制中使用着色器和过滤器,并通过动画进行数值变化达到旋转流光效果。'** String get usingShadersAndFilters; /// No description provided for @pathDrawingFunctionCurve. /// /// In zh, this message translates to: /// **'本样例介绍如何使用路径绘制函数曲线,并使用路径测量进行动画'** String get pathDrawingFunctionCurve; /// No description provided for @thePathOfBingDwenDwen. /// /// In zh, this message translates to: /// **'本样例会绘制 2022 年北京冬奥会吉祥物冰墩墩的路径,并使用路径测量进行动画。\n特殊操作:点击运行'** String get thePathOfBingDwenDwen; /// No description provided for @drawCubicBesselCurve. /// /// In zh, this message translates to: /// **'本样例介绍如何绘制三次贝塞尔曲线,通过触点判断某点是否激活,据此控制点的位置达到拖动控制效果。\n特殊操作:单击绘点,双击清除'** String get drawCubicBesselCurve; /// No description provided for @theEffectOfAnimationCurve. /// /// In zh, this message translates to: /// **'本样例通过直观的方式,来查看动画曲线 curve 的作用效果,让大家对动画有更深的理解。\n特殊操作:点击运行'** String get theEffectOfAnimationCurve; /// No description provided for @randomParticlesAndBoundaryBouncing. /// /// In zh, this message translates to: /// **' 本样例介绍如何创建随机粒子及边界反弹逻辑处理,是学习粒子运动非常好的入门案例特殊操作:单击停止/运行 '** String get randomParticlesAndBoundaryBouncing; /// No description provided for @particleCollision. /// /// In zh, this message translates to: /// **'本样例介绍如何对个粒子进行碰撞检测,并分裂处多个粒子,是一个比较有趣的案例。\n特殊操作:单击重置'** String get particleCollision; /// No description provided for @particle. /// /// In zh, this message translates to: /// **'本样例介绍将图片使用粒子表示,并对粒子进行动画处理,达到爆炸的效果。\n特殊操作:单击运行'** String get particle; /// No description provided for @rectangleAndRandomNumbers. /// /// In zh, this message translates to: /// **'本样例介绍绘制矩形及随机数处理。通过点位集合确定矩形位置信息,将其绘制出来。可以练习对数据的控制能力。\n特殊操作:点击随机生成'** String get rectangleAndRandomNumbers; /// No description provided for @bingDwenDwen. /// /// In zh, this message translates to: /// **'本样例是绘制 2022 年北京冬奥会吉祥物冰墩墩的形体,从中可以学到路径绘制、渐变色等知识。'** String get bingDwenDwen; /// No description provided for @pufengInjectionTest. /// /// In zh, this message translates to: /// **'本样实现蒲丰投针试验的测试过程,根据概率来估算圆周率。其中可以学习到一些绘制小技巧已经数据的逻辑处理。'** String get pufengInjectionTest; /// No description provided for @ticTacToe. /// /// In zh, this message translates to: /// **'本例通过井字棋的绘制与逻辑校验,集合了手势、绘制、动画、校验等重要的技能,是一个非常好的联系案例。\n特殊操作:双击重置'** String get ticTacToe; /// No description provided for @tiledLines. /// /// In zh, this message translates to: /// **'本样例根源来自generativeartistry.com的tiled-lines,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'** String get tiledLines; /// No description provided for @joyDivision. /// /// In zh, this message translates to: /// **'本样例根源来自generativeartistry.com的joy-division,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'** String get joyDivision; /// No description provided for @cubicDisarray. /// /// In zh, this message translates to: /// **'本样例根源来自generativeartistry.com的cubic-disarray,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'** String get cubicDisarray; /// No description provided for @triangularMesh. /// /// In zh, this message translates to: /// **'本样例根源来自generativeartistry.com的triangular-mesh,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'** String get triangularMesh; /// No description provided for @unDeuxTrois. /// /// In zh, this message translates to: /// **'本样例根源来自generativeartistry.com的un-deux-trois,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'** String get unDeuxTrois; /// No description provided for @circlePacking. /// /// In zh, this message translates to: /// **'本样例根源来自generativeartistry.com的circle-packing,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'** String get circlePacking; /// No description provided for @hypnoticSquares. /// /// In zh, this message translates to: /// **'本样例根源来自generativeartistry.com的hypnotic-squares,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'** String get hypnoticSquares; /// No description provided for @pietMondrian. /// /// In zh, this message translates to: /// **'本样例根源来自generativeartistry.com的piet-mondrian,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'** String get pietMondrian; /// No description provided for @downloadCompressedPackage. /// /// In zh, this message translates to: /// **'使用方式:\n1. 在 iconfont.cn 挑选图标,加入项目,下载压缩包。\n2. 选择 Flutter 项目地址,配置资源、产物文件位置。\n3. 点击生成代码按钮,即可生成相关代码。'** String get downloadCompressedPackage; /// No description provided for @qAIssues. /// /// In zh, this message translates to: /// **'要点集录中的 QA 数据收录rUnit 以 point 为标签的 issues 中。如果需要提供数据,在 issues 中问答即可。'** String get qAIssues; /// No description provided for @tips. /// /// In zh, this message translates to: /// **'注:'** String get tips; /// No description provided for @visualSorting. /// /// In zh, this message translates to: /// **'可视化排序'** String get visualSorting; /// No description provided for @visual. /// /// In zh, this message translates to: /// **'可视排序'** String get visual; /// No description provided for @insertion. /// /// In zh, this message translates to: /// **'插入排序'** String get insertion; /// No description provided for @bubble. /// /// In zh, this message translates to: /// **'冒泡排序'** String get bubble; /// No description provided for @cocktail. /// /// In zh, this message translates to: /// **'鸡尾酒排序(双向冒泡排序)'** String get cocktail; /// No description provided for @comb. /// /// In zh, this message translates to: /// **'梳排序'** String get comb; /// No description provided for @pigeonHole. /// /// In zh, this message translates to: /// **'鸽巢排序'** String get pigeonHole; /// No description provided for @shell. /// /// In zh, this message translates to: /// **'希尔排序'** String get shell; /// No description provided for @selection. /// /// In zh, this message translates to: /// **'选择排序'** String get selection; /// No description provided for @gnome. /// /// In zh, this message translates to: /// **'侏儒排序'** String get gnome; /// No description provided for @cycle. /// /// In zh, this message translates to: /// **'循环排序'** String get cycle; /// No description provided for @heap. /// /// In zh, this message translates to: /// **'堆排序'** String get heap; /// No description provided for @quick. /// /// In zh, this message translates to: /// **'快速排序'** String get quick; /// No description provided for @merge. /// /// In zh, this message translates to: /// **'归并排序'** String get merge; /// No description provided for @sortingAlgorithmConfiguration. /// /// In zh, this message translates to: /// **'排序算法配置'** String get sortingAlgorithmConfiguration; /// No description provided for @dataCount. /// /// In zh, this message translates to: /// **'数据数量(个数)'** String get dataCount; /// No description provided for @timeInterval. /// /// In zh, this message translates to: /// **'时间间隔(微秒)'** String get timeInterval; /// No description provided for @randomSeed. /// /// In zh, this message translates to: /// **'随机种子'** String get randomSeed; /// No description provided for @codeGeneration. /// /// In zh, this message translates to: /// **'代码生成'** String get codeGeneration; /// No description provided for @generateCode. /// /// In zh, this message translates to: /// **'生成代码'** String get generateCode; /// No description provided for @artifactLocation. /// /// In zh, this message translates to: /// **'产物位置'** String get artifactLocation; /// No description provided for @codeClassLocation. /// /// In zh, this message translates to: /// **'代码类存放位置'** String get codeClassLocation; /// No description provided for @resourceDirectory. /// /// In zh, this message translates to: /// **'资源目录'** String get resourceDirectory; /// No description provided for @iconfontResourceLocation. /// /// In zh, this message translates to: /// **'iconfont 资源存放位置'** String get iconfontResourceLocation; /// No description provided for @projectPath. /// /// In zh, this message translates to: /// **'项目路径'** String get projectPath; /// No description provided for @inputProjectAddress. /// /// In zh, this message translates to: /// **'请选择或输入项目地址'** String get inputProjectAddress; /// No description provided for @iconfontCompressedPackagePath. /// /// In zh, this message translates to: /// **'Iconfont 压缩包路径'** String get iconfontCompressedPackagePath; /// No description provided for @pleaseSelectOrInputIconfontCompressedPackagePath. /// /// In zh, this message translates to: /// **'请选择或输入 iconfont 下载的压缩包路径'** String get pleaseSelectOrInputIconfontCompressedPackagePath; /// No description provided for @stayTuned. /// /// In zh, this message translates to: /// **'敬请期待'** String get stayTuned; /// No description provided for @iconFont. /// /// In zh, this message translates to: /// **'IconFont'** String get iconFont; /// No description provided for @dataClass. /// /// In zh, this message translates to: /// **'数据类'** String get dataClass; /// No description provided for @stateManagement. /// /// In zh, this message translates to: /// **'状态管理'** String get stateManagement; /// No description provided for @jsonParsing. /// /// In zh, this message translates to: /// **'Json 解析'** String get jsonParsing; /// No description provided for @clickHereToJump. /// /// In zh, this message translates to: /// **'点击这里跳转'** String get clickHereToJump; /// No description provided for @knowledgeTabToly. /// /// In zh, this message translates to: /// **'捷特文库'** String get knowledgeTabToly; /// No description provided for @knowledgeTabAlgo. /// /// In zh, this message translates to: /// **'算法演绎'** String get knowledgeTabAlgo; /// No description provided for @knowledgeTabLayout. /// /// In zh, this message translates to: /// **'布局宝库'** String get knowledgeTabLayout; /// No description provided for @knowledgeTabPoint. /// /// In zh, this message translates to: /// **'要点宝库'** String get knowledgeTabPoint; /// No description provided for @knowledgeConstruction. /// /// In zh, this message translates to: /// **'正在建设中'** String get knowledgeConstruction; /// No description provided for @knowledgeToJuejin. /// /// In zh, this message translates to: /// **'前往掘金'** String get knowledgeToJuejin; /// No description provided for @srcPath. /// /// In zh, this message translates to: /// **'源码地址'** String get srcPath; /// No description provided for @widgetsInn. /// /// In zh, this message translates to: /// **'组件酒肆'** String get widgetsInn; /// No description provided for @likedWidgets. /// /// In zh, this message translates to: /// **'珍藏组件'** String get likedWidgets; /// No description provided for @relatedComponents. /// /// In zh, this message translates to: /// **'相关组件'** String get relatedComponents; /// No description provided for @backupFavoritesCollectionData. /// /// In zh, this message translates to: /// **'备份收藏集数据'** String get backupFavoritesCollectionData; /// No description provided for @syncFavoritesCollectionData. /// /// In zh, this message translates to: /// **'同步收藏集数据'** String get syncFavoritesCollectionData; /// No description provided for @favoritesCollectionDataReset. /// /// In zh, this message translates to: /// **'收藏集数据重置'** String get favoritesCollectionDataReset; /// No description provided for @resetSuccess. /// /// In zh, this message translates to: /// **'重置成功!'** String get resetSuccess; /// No description provided for @dataSetBackupSuccess. /// /// In zh, this message translates to: /// **'数据集备份成功!'** String get dataSetBackupSuccess; /// No description provided for @dataSetBackupFailure. /// /// In zh, this message translates to: /// **'数据集备份失败!'** String get dataSetBackupFailure; /// No description provided for @dataSynchronizationCopySuccess. /// /// In zh, this message translates to: /// **'数据同步份成功!'** String get dataSynchronizationCopySuccess; /// No description provided for @dataSynchronizationCopyFailure. /// /// In zh, this message translates to: /// **'数据同步份失败!'** String get dataSynchronizationCopyFailure; /// No description provided for @destructionRed. /// /// In zh, this message translates to: /// **'毁灭之红'** String get destructionRed; /// No description provided for @rageOrange. /// /// In zh, this message translates to: /// **'愤怒之橙'** String get rageOrange; /// No description provided for @warningYellow. /// /// In zh, this message translates to: /// **'警告之黄'** String get warningYellow; /// No description provided for @camouflageGreen. /// /// In zh, this message translates to: /// **'伪装之绿'** String get camouflageGreen; /// No description provided for @coldBlue. /// /// In zh, this message translates to: /// **'冷漠之蓝'** String get coldBlue; /// No description provided for @infiniteBlue. /// /// In zh, this message translates to: /// **'无限之靛'** String get infiniteBlue; /// No description provided for @mysteryPurple. /// /// In zh, this message translates to: /// **'神秘之紫'** String get mysteryPurple; /// No description provided for @destinyBlack. /// /// In zh, this message translates to: /// **'归宿之黑'** String get destinyBlack; /// No description provided for @showBackground. /// /// In zh, this message translates to: /// **'显示背景'** String get showBackground; /// No description provided for @toly. /// /// In zh, this message translates to: /// **'张风捷特烈'** String get toly; /// No description provided for @dartHandbook. /// /// In zh, this message translates to: /// **'Dart 手册'** String get dartHandbook; /// No description provided for @codeCopiedSuccessfully. /// /// In zh, this message translates to: /// **'代码复制成功'** String get codeCopiedSuccessfully; /// No description provided for @favoriteFolderManagement. /// /// In zh, this message translates to: /// **'收藏夹管理'** String get favoriteFolderManagement; /// No description provided for @assembly. /// /// In zh, this message translates to: /// **'组件'** String get assembly; /// No description provided for @draw. /// /// In zh, this message translates to: /// **'绘制'** String get draw; /// No description provided for @knowledge. /// /// In zh, this message translates to: /// **'知识'** String get knowledge; /// No description provided for @collection. /// /// In zh, this message translates to: /// **'收藏'** String get collection; /// No description provided for @my. /// /// In zh, this message translates to: /// **'我的'** String get my; /// No description provided for @picture. /// /// In zh, this message translates to: /// **'幅'** String get picture; /// No description provided for @widgetInn. /// /// In zh, this message translates to: /// **'组件酒肆'** String get widgetInn; /// No description provided for @emptySearch. /// /// In zh, this message translates to: /// **'没数据,哥也没办法\n(≡ _ ≡)/~┴┴'** String get emptySearch; /// No description provided for @searchSomething. /// /// In zh, this message translates to: /// **'哥们,搜点啥...≧◔◡◔≦'** String get searchSomething; /// No description provided for @slogan. /// /// In zh, this message translates to: /// **'Flutter 的联合,编程者的联合'** String get slogan; } class _AppLocalizationsDelegate extends LocalizationsDelegate { const _AppLocalizationsDelegate(); @override Future load(Locale locale) { return SynchronousFuture(lookupAppLocalizations(locale)); } @override bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode); @override bool shouldReload(_AppLocalizationsDelegate old) => false; } AppLocalizations lookupAppLocalizations(Locale locale) { // Lookup logic when only language code is specified. switch (locale.languageCode) { case 'en': return AppLocalizationsEn(); case 'zh': return AppLocalizationsZh(); } throw FlutterError( 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' 'an issue with the localizations generation tool. Please file an issue ' 'on GitHub with a reproducible sample app and the gen-l10n configuration ' 'that was used.' ); } ================================================ FILE: modules/basic_system/l10n/lib/gen_l10n/app_localizations_en.dart ================================================ import 'app_localizations.dart'; // ignore_for_file: type=lint /// The translations for English (`en`). class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override String get widgetCollection => 'Widgets'; @override String get paintCollection => 'Painter'; @override String get knowledgeCollection => 'Knowledge'; @override String get treasureTools => 'Treasure'; @override String get collectCollection => 'Collection'; @override String get essentialCollection => 'KeypointsCollection'; @override String get homeAccount => 'Application'; @override String get homeAccountTabInfo => 'About App'; @override String get messageBoard => 'Message Board'; @override String get homeAccountTabMe => 'Contact Me'; @override String get homeAccountSupport => 'Support Project'; @override String get searchWidget => 'search widget ...'; @override String get stateless => 'Stateless'; @override String get stateful => 'Stateful'; @override String get single => 'Single'; @override String get multi => 'Multi'; @override String get sliver => 'Sliver'; @override String get proxy => 'Proxy'; @override String get other => 'Other'; @override String get homeTabWidget => 'Widget'; @override String get homeTabPaint => 'Paint'; @override String get homeTabKnowledge => 'Knowledge'; @override String get homeTabTools => 'Treasure'; @override String get homeTabMine => 'Mine'; @override String get dataManagement => 'Data management'; @override String get userCollection => 'Collection'; @override String get aboutApplications => 'About Applications'; @override String get contactThisKing => 'Contact this king'; @override String get appSettings => 'Application Setting'; @override String get darkMode => 'Dark Mode '; @override String get themeColorSetting => 'Theme Color'; @override String get fontSetting => 'Font Setting'; @override String get settingLanguageText => 'Setting Language'; @override String get codeHighlightStyle => 'Code Highlight Style'; @override String get versionInformation => 'App Version'; @override String get displayPerformanceFloatingLayer => 'Performance Layer'; @override String get showFloatingTools => 'Show floating tools'; @override String get followSystem => 'Follow system'; @override String get afterOpeningWillFollowTheSystemToOpenOrCloseDarkMode => 'After activation, it will follow the system to turn on or off dark mode'; @override String get manualSetting => 'Manual settings'; @override String get lightMode => 'Light mode'; @override String get settingLanguage => 'Language Setting'; @override String get appDetails => 'Application details'; @override String get checkUpdate => 'Check New Version'; @override String get downloadNewVersion => 'Download New Version'; @override String get downloadingNewVersion => 'Downloading New Version ...'; @override String get currentIsNew => 'There is the latest version of FlutterUnit!'; @override String get checkDatabaseNewVersion => 'Check for new versions of the database'; @override String get viewThisProjectGithubRepository => '《View the Github Repository for this project》'; @override String get favorite => 'Collected'; @override String get enterComponentName => 'Input widget name'; @override String get containerComponents => 'Container components'; @override String get componentTavern => 'Component Tavern'; @override String get cherishedComponents => 'Treasure components'; @override String get textImageCollection => 'TextImageCollection'; @override String get layoutCollection => 'LayoutCollection'; @override String get eventCollection => 'EventCollection'; @override String get animationCollection => 'AnimationCollection'; @override String get slidingCollection => 'SlidingCollection'; @override String get decorationCollection => 'DecorativeCollection'; @override String get assemblyCollection => 'AssemblyCollection'; @override String get functionCollection => 'FeatureCollection'; @override String get popupCollection => 'Pop upCollection'; @override String get themeCollection => 'ThemeCollection'; @override String get derivativeCollection => 'DerivativeCollection'; @override String get hardToCategorize => 'It\'s hard to distinguish'; @override String get basicDrawing => 'Basic drawing'; @override String get basicDrawingDesc => 'Including some basic graphics drawing examples would be very friendly to beginners in programming. Through these examples, one can learn how to draw basic shapes such as points, lines, rectangles, circles, arcs, text, images, etc., and understand the usage of core objects in drawing like Canvas, Paint, Path, etc.'; @override String get animationGesture => 'Animated gestures'; @override String get animationGestureDesc => 'Includes some drawing examples of animation and gestures, which make drawing more interactive. Through these examples, one can learn the usage of animation and gestures, such as sliding, rotating, scaling, moving effects, etc., making drawing not just static presentation.'; @override String get particleDrawing => 'Particle drawing'; @override String get particleDrawingDesc => 'Includes some drawing examples related to particles, which are top-level operations in drawing. Through these examples, one can learn how to use particles to create stunning visual effects, such as particle clocks, particle explosions, particle backgrounds, etc., giving drawing endless possibilities.'; @override String get interestingDrawing => 'Fun drawing'; @override String get interestingDrawingDesc => 'Includes some fun drawing examples, let\'s experience the joy of drawing, programming, and intelligence together here.'; @override String get artGallery => 'Art galleries '; @override String get artGalleryDesc => 'Includes some hall-level drawing examples, which are pinnacle works of drawing. They have no practicality and are not born for any demand. They exist only because they exist, serving as a medium for human wisdom and expression, called art.'; @override String get drawingOfImages => 'This example explains how to draw images: by loading images and drawing image resources to a specified area. Draw a batch of 45 \"angled grid lines on the upper layer to practice drawing the lines '; @override String get digitalDisplayTube => 'This example introduces how to draw LED digital display tubes to practice the use, transformation, combination of path paths, and knowledge of component packaging. It is a very good drawing case '; @override String get pathDrawing => 'This example introduces how to perform simple path drawing, rotate the drawing board, and combine animation to make the windmill rotate. This is a very concise case of combining drawing and animation. '; @override String get gridCoordinateSystem => 'This example explains how to use line diameter and text to draw a grid coordinate system, and encapsulate the drawn objects for easy reuse. The coordinate system also provides reference during drawing, which is essential for beginners.'; @override String get polarCoordinateSystemOfFaces => 'This example explains how to use a polar coordinate system to draw a plane and collect polar coordinates based on a function equation for drawing. '; @override String get drawFunctionCurvesForPathPairs => 'This example explains how to use a path to draw a function curve, fitting a small number of points on the function curve through a Bessel curve. '; @override String get drawRegularPolygons => 'This example introduces how to collect points in a circle and draw regular polygons, which is a good example for practicing drawing and forming paths. \n Special operations:+, - Modify the number of edges'; @override String get randomNumberProcessing => 'This example introduces drawing rectangles and handling random numbers. Determine the rectangular position information through a set of points and draw it. Can practice the ability to control data.'; @override String get clockDrawing => 'This example uses the drawing of a clock to practice the drawing technique of rotating scale types in Flutter, and uses animation to rotate the dial pointer.'; @override String get drawSprings => ' This example introduces how to draw a spring, stretch and compress it vertically through the contact points, and restore the animation when releasing it. It is a good comprehensive small case. Special operation: Drag the telescopic spring up and down '; @override String get theApplicationOfAnglesInDrawing => 'This example explains how to perform rotational motion based on a point as the center. Learn the application of the angle between two points in drawing. \n Special operation: Click to run'; @override String get usingShadersAndFilters => 'This example explains how to use shaders and filters in painting, and achieve a rotating streamer effect through animation with numerical variations.'; @override String get pathDrawingFunctionCurve => 'This example explains how to use path to draw function curves and use path measurement for animation'; @override String get thePathOfBingDwenDwen => 'This sample will draw the path of the mascot Bing Dwen Dwen for the 2022 Beijing Winter Olympics and use path measurement for animation. \n Special operation: Click to run'; @override String get drawCubicBesselCurve => 'This example introduces how to draw a cubic Bezier curve, determine whether a point is activated through the contacts, and use this to control the position of the point to achieve drag control effect. \n Special operation: Click on the drawing point, double-click to clear it'; @override String get theEffectOfAnimationCurve => 'This example provides an intuitive way to examine the effect of animation curves, allowing everyone to have a deeper understanding of animation. \n Special operation: Click to run'; @override String get randomParticlesAndBoundaryBouncing => 'This example introduces how to create random particles and handle boundary bounce logic, which is a great starting point for learning particle motion. Special operation: click to stop running '; @override String get particleCollision => 'This example introduces how to perform collision detection on a particle and split multiple particles, which is an interesting case. \n Special operation: Click Reset'; @override String get particle => 'This example introduces using particles to represent images and animating them to achieve explosive effects. \nSpecial operation: Click to run'; @override String get rectangleAndRandomNumbers => 'This example introduces drawing rectangles and handling random numbers. Determine the rectangular position information through a set of points and draw it. Can practice the ability to control data. \nSpecial operation: Click to randomly generate'; @override String get bingDwenDwen => 'This example is to draw the shape of the mascot Bing Dwen Dwen for the 2022 Beijing Winter Olympics, from which you can learn knowledge such as path drawing and gradient colors.'; @override String get pufengInjectionTest => 'This sample implements the testing process of the Pufeng needle injection test, estimating pi based on probability. You can learn some drawing tips and logical processing of data.'; @override String get ticTacToe => 'This example combines important skills such as gestures, drawing, animation, and verification through the drawing and logical verification of the Chinese checkerboard, making it a very good case study. \n Special operation: Double click to reset'; @override String get tiledLines => 'The root cause of this example comes from generateArchistry.com tiled-lines,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry'; @override String get joyDivision => 'The root cause of this example comes from generateArchistry.com joy-division,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry'; @override String get cubicDisarray => 'The root cause of this example comes from generateArchistry.com cubic-disarray,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry'; @override String get triangularMesh => 'The root cause of this example comes from generateArchistry.com triangular-mesh,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry'; @override String get unDeuxTrois => 'The root cause of this example comes from generateArchistry.com un-deux-trois,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry'; @override String get circlePacking => 'The root cause of this example comes from generateArchistry.com circle-packing,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry'; @override String get hypnoticSquares => 'The root cause of this example comes from generateArchistry.com hypnotic-squares,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry'; @override String get pietMondrian => 'The root cause of this example comes from generateArchistry.com piet-mondrian,Implemented by xrr 2016 using Flutter。Warehouse address:flutter-generative-artistry'; @override String get downloadCompressedPackage => 'Usage: \n1. Select the icon in iconfont.cn, add the project, and download the compressed file. \n2. Select the Flutter project address, configure resource and product file locations. \n3.Click the Generate Code button to generate the relevant code.'; @override String get qAIssues => 'The QA data in the key points collection is included in FlutterUnit\'s issues labeled with points. If data needs to be provided, simply ask and answer in the issues section.'; @override String get tips => 'tips:'; @override String get visualSorting => 'Visual sorting'; @override String get visual => 'Visual sorting'; @override String get insertion => 'Insert sorting'; @override String get bubble => 'Bubble sorting'; @override String get cocktail => 'Cocktail sorting (bidirectional bubble sorting)'; @override String get comb => 'Comb sorting'; @override String get pigeonHole => 'Pigeonhole sorting '; @override String get shell => 'Shell sorting '; @override String get selection => 'Select sorting'; @override String get gnome => 'Dwarf Sorting'; @override String get cycle => 'Circular sorting'; @override String get heap => 'Heap sorting'; @override String get quick => 'Quick sorting'; @override String get merge => 'Merge sorting'; @override String get sortingAlgorithmConfiguration => 'Sorting algorithm configuration'; @override String get dataCount => 'Data quantity (number)'; @override String get timeInterval => 'Time interval (microseconds)'; @override String get randomSeed => 'Random Seed'; @override String get codeGeneration => 'Code generation'; @override String get generateCode => 'Generate Code'; @override String get artifactLocation => 'Product location'; @override String get codeClassLocation => 'Code class storage location'; @override String get resourceDirectory => 'Resource Catalog'; @override String get iconfontResourceLocation => 'iconfont Resource storage location'; @override String get projectPath => 'Project Path'; @override String get inputProjectAddress => 'Please select or enter the project address'; @override String get iconfontCompressedPackagePath => 'Iconfont Compressed package path'; @override String get pleaseSelectOrInputIconfontCompressedPackagePath => 'Please select or enter the compressed file path for iconfont download'; @override String get stayTuned => 'Stay tuned'; @override String get iconFont => 'IconFont'; @override String get dataClass => 'Data class'; @override String get stateManagement => 'State management'; @override String get jsonParsing => 'Json Parsing'; @override String get clickHereToJump => 'Click here to jump to'; @override String get knowledgeTabToly => 'Toly Articles'; @override String get knowledgeTabAlgo => 'Algo Player'; @override String get knowledgeTabLayout => 'Layout Treasury'; @override String get knowledgeTabPoint => 'Key Points'; @override String get knowledgeConstruction => 'In Construction'; @override String get knowledgeToJuejin => 'To Juejin'; @override String get srcPath => 'Source Path'; @override String get widgetsInn => 'Widgets Inn'; @override String get likedWidgets => 'Liked Widgets'; @override String get relatedComponents => 'Related Widgets'; @override String get backupFavoritesCollectionData => 'Backup Collection Data'; @override String get syncFavoritesCollectionData => 'Synchronize collection data'; @override String get favoritesCollectionDataReset => 'Reset Collection Data'; @override String get resetSuccess => 'Reset successful!'; @override String get dataSetBackupSuccess => 'Dataset backup successful!'; @override String get dataSetBackupFailure => 'Dataset backup failed!'; @override String get dataSynchronizationCopySuccess => 'Data synchronization successful!'; @override String get dataSynchronizationCopyFailure => 'Data synchronization failed!'; @override String get destructionRed => 'Destruction Red '; @override String get rageOrange => 'Anger Orange'; @override String get warningYellow => 'Warning Yellow'; @override String get camouflageGreen => 'Disguising Green'; @override String get coldBlue => 'Indifferent Blue'; @override String get infiniteBlue => 'Infinite Indigo'; @override String get mysteryPurple => 'Mysterious Purple'; @override String get destinyBlack => 'Destiny Black'; @override String get showBackground => 'Display Background'; @override String get toly => 'toly'; @override String get dartHandbook => 'Dart Handbook'; @override String get codeCopiedSuccessfully => 'Code copied successfully'; @override String get favoriteFolderManagement => 'Favorite folder management'; @override String get assembly => 'Assembly'; @override String get draw => 'Draw'; @override String get knowledge => 'Knowledge'; @override String get collection => 'Collection'; @override String get my => 'My'; @override String get picture => 'pics'; @override String get widgetInn => 'Widget Collection'; @override String get emptySearch => 'No Result \n(≡ _ ≡)/~┴┴'; @override String get searchSomething => 'Search Something ≧◔◡◔≦'; @override String get slogan => 'The unity of flutter, The unity of coder.'; } ================================================ FILE: modules/basic_system/l10n/lib/gen_l10n/app_localizations_zh.dart ================================================ import 'app_localizations.dart'; // ignore_for_file: type=lint /// The translations for Chinese (`zh`). class AppLocalizationsZh extends AppLocalizations { AppLocalizationsZh([String locale = 'zh']) : super(locale); @override String get widgetCollection => '组件集录'; @override String get paintCollection => '绘制集录'; @override String get knowledgeCollection => '知识集锦'; @override String get treasureTools => '工具宝箱'; @override String get collectCollection => '收藏集录'; @override String get essentialCollection => '要点集录'; @override String get homeAccount => '应用信息'; @override String get homeAccountTabInfo => '关于应用'; @override String get messageBoard => '留言板'; @override String get homeAccountTabMe => '联系本王'; @override String get homeAccountSupport => '支持项目'; @override String get searchWidget => '搜索组件'; @override String get stateless => '无态'; @override String get stateful => '有态'; @override String get single => '单渲'; @override String get multi => '多渲'; @override String get sliver => '滑片'; @override String get proxy => '代理'; @override String get other => '其他'; @override String get homeTabWidget => '组件'; @override String get homeTabPaint => '绘制'; @override String get homeTabKnowledge => '知识'; @override String get homeTabTools => '工具'; @override String get homeTabMine => '我的'; @override String get dataManagement => '数据管理'; @override String get userCollection => '我的收藏'; @override String get aboutApplications => '关于应用'; @override String get contactThisKing => '联系本王'; @override String get appSettings => '应用设置'; @override String get darkMode => '深色模式'; @override String get themeColorSetting => '主题色设置'; @override String get fontSetting => '字体设置'; @override String get settingLanguageText => '多语言'; @override String get codeHighlightStyle => '代码高亮样式'; @override String get versionInformation => '版本信息'; @override String get displayPerformanceFloatingLayer => '显示性能浮层'; @override String get showFloatingTools => '显示浮动工具'; @override String get followSystem => '跟随系统'; @override String get afterOpeningWillFollowTheSystemToOpenOrCloseDarkMode => '开启后,将跟随系统打开或关闭深色模式'; @override String get manualSetting => '手动设置'; @override String get lightMode => '浅色模式'; @override String get settingLanguage => '设置语言'; @override String get appDetails => '应用详情'; @override String get checkUpdate => '检查新版本'; @override String get downloadNewVersion => '下载新版本'; @override String get downloadingNewVersion => '新版本下载中...'; @override String get currentIsNew => '当前应用已是最新版本!'; @override String get checkDatabaseNewVersion => '检查数据库新版本'; @override String get viewThisProjectGithubRepository => '《查看本项目Github仓库》'; @override String get favorite => '已收藏'; @override String get enterComponentName => '输入组件名称'; @override String get containerComponents => '容器组件'; @override String get componentTavern => '组件酒肆'; @override String get cherishedComponents => '珍藏组件'; @override String get textImageCollection => '图文集'; @override String get layoutCollection => '布局集'; @override String get eventCollection => '事件集'; @override String get animationCollection => '动画集'; @override String get slidingCollection => '滑动集'; @override String get decorationCollection => '装饰集'; @override String get assemblyCollection => '组装集'; @override String get functionCollection => '功能集'; @override String get popupCollection => '弹出集'; @override String get themeCollection => '主题集'; @override String get derivativeCollection => '衍生集'; @override String get hardToCategorize => '很难分'; @override String get basicDrawing => '基础绘制'; @override String get basicDrawingDesc => '收录一些基础图形绘制案例,这些案例对初涉绘制的编程者会非常友好。通过这些案例,可以学会点、线、矩形、圆、圆弧、文字、图片等基本图形的绘制方法,了解 Canvas、Paint、Path 等绘制中核心对象的使用。'; @override String get animationGesture => '动画手势'; @override String get animationGestureDesc => '收录一些动画和手势的绘制案例,这些案例会让绘制更具有操作性。通过这些案例,可以学会动画和手势的使用,如滑动、旋转、缩放、移动等效果,让绘制不再只是静态展现。'; @override String get particleDrawing => '粒子绘制'; @override String get particleDrawingDesc => '收录一些粒子相关的绘制案例,这些案例将是绘制的顶级操作。通过这些案例,可以学会如何使用粒子来绘制惊艳的视觉效果,如粒子时钟、粒子爆炸、粒子背景等效果,让绘制拥有无限可能。'; @override String get interestingDrawing => '趣味绘制'; @override String get interestingDrawingDesc => '收录一些比较有趣的绘制案例,让我们一起在这里一起体验绘制的乐趣、编程的乐趣和智慧的乐趣吧。'; @override String get artGallery => '艺术画廊'; @override String get artGalleryDesc => '收录一些殿堂级的绘制案例,这些案例将是绘制的巅峰作品,它们的没有任何的实用性,也不为任何需求而生,它们仅是因为存在而存在,是人类智慧和表达的媒介,称谓艺术。'; @override String get drawingOfImages => '本样例介绍如何进行图片的绘制: 通过加载图片并将图片资源绘制到指定的区域。在上层绘制一批 45”倾角的栅格线,来练习线条的绘制 '; @override String get digitalDisplayTube => '本样例介绍如何绘制 LED 数字显示管,以此练习对路径 Path 的使用、变换、组合,以及组件封装的知识。是一个非常好的绘制案例 '; @override String get pathDrawing => '本样例介绍如何进行简单的路径绘制,以及画板的旋转,再结合动画让风车旋转。这是一个非常精简的绘制与动画结合的案例。 '; @override String get gridCoordinateSystem => '本样例介绍如何使用线路径和文字绘制网格坐标系,并将绘制对象进行封装,方便重用。坐标系也会在绘制时提供参考,入门必备。 '; @override String get polarCoordinateSystemOfFaces => '本样例介绍如何使用绘制平面的极坐标系,并根据函数方程收集极坐标进行绘制。 '; @override String get drawFunctionCurvesForPathPairs => '本样例介绍如何使用路径对函数曲线进行绘制,通过函数曲线上的少量点通过贝塞尔曲线进行拟合。 '; @override String get drawRegularPolygons => '本样例介绍如何在圆中收集点位,绘制正多边形,是练习绘制及形成路径的很好案例。\n特殊操作:+、- 修改边数'; @override String get randomNumberProcessing => '本样例介绍绘制矩形及随机数处理。通过点位集合确定矩形位置信息,将其绘制出来。可以练习对数据的控制能力。'; @override String get clockDrawing => '本样例通过时钟的绘制,练习 Flutter 中旋转刻度类型的绘制技巧,并通过动画使表盘指针转动。'; @override String get drawSprings => ' 本样例介绍如何绘制弹簧,通过触点竖直拖拽拉伸、压缩,放手时进行恢复动画,是一个很好的综合小案例。特殊操作:上下拖拽伸缩弹簧 '; @override String get theApplicationOfAnglesInDrawing => '本样例介绍如何根据以某个点为中心,进行旋转运动。以此学习两点间的角度在绘制中的应用。\n特殊操作:点击运行'; @override String get usingShadersAndFilters => '本样例介绍如何在绘制中使用着色器和过滤器,并通过动画进行数值变化达到旋转流光效果。'; @override String get pathDrawingFunctionCurve => '本样例介绍如何使用路径绘制函数曲线,并使用路径测量进行动画'; @override String get thePathOfBingDwenDwen => '本样例会绘制 2022 年北京冬奥会吉祥物冰墩墩的路径,并使用路径测量进行动画。\n特殊操作:点击运行'; @override String get drawCubicBesselCurve => '本样例介绍如何绘制三次贝塞尔曲线,通过触点判断某点是否激活,据此控制点的位置达到拖动控制效果。\n特殊操作:单击绘点,双击清除'; @override String get theEffectOfAnimationCurve => '本样例通过直观的方式,来查看动画曲线 curve 的作用效果,让大家对动画有更深的理解。\n特殊操作:点击运行'; @override String get randomParticlesAndBoundaryBouncing => ' 本样例介绍如何创建随机粒子及边界反弹逻辑处理,是学习粒子运动非常好的入门案例特殊操作:单击停止/运行 '; @override String get particleCollision => '本样例介绍如何对个粒子进行碰撞检测,并分裂处多个粒子,是一个比较有趣的案例。\n特殊操作:单击重置'; @override String get particle => '本样例介绍将图片使用粒子表示,并对粒子进行动画处理,达到爆炸的效果。\n特殊操作:单击运行'; @override String get rectangleAndRandomNumbers => '本样例介绍绘制矩形及随机数处理。通过点位集合确定矩形位置信息,将其绘制出来。可以练习对数据的控制能力。\n特殊操作:点击随机生成'; @override String get bingDwenDwen => '本样例是绘制 2022 年北京冬奥会吉祥物冰墩墩的形体,从中可以学到路径绘制、渐变色等知识。'; @override String get pufengInjectionTest => '本样实现蒲丰投针试验的测试过程,根据概率来估算圆周率。其中可以学习到一些绘制小技巧已经数据的逻辑处理。'; @override String get ticTacToe => '本例通过井字棋的绘制与逻辑校验,集合了手势、绘制、动画、校验等重要的技能,是一个非常好的联系案例。\n特殊操作:双击重置'; @override String get tiledLines => '本样例根源来自generativeartistry.com的tiled-lines,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'; @override String get joyDivision => '本样例根源来自generativeartistry.com的joy-division,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'; @override String get cubicDisarray => '本样例根源来自generativeartistry.com的cubic-disarray,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'; @override String get triangularMesh => '本样例根源来自generativeartistry.com的triangular-mesh,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'; @override String get unDeuxTrois => '本样例根源来自generativeartistry.com的un-deux-trois,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'; @override String get circlePacking => '本样例根源来自generativeartistry.com的circle-packing,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'; @override String get hypnoticSquares => '本样例根源来自generativeartistry.com的hypnotic-squares,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'; @override String get pietMondrian => '本样例根源来自generativeartistry.com的piet-mondrian,由xrr2016使用Flutter实现。仓库地址:flutter-generative-artistry'; @override String get downloadCompressedPackage => '使用方式:\n1. 在 iconfont.cn 挑选图标,加入项目,下载压缩包。\n2. 选择 Flutter 项目地址,配置资源、产物文件位置。\n3. 点击生成代码按钮,即可生成相关代码。'; @override String get qAIssues => '要点集录中的 QA 数据收录rUnit 以 point 为标签的 issues 中。如果需要提供数据,在 issues 中问答即可。'; @override String get tips => '注:'; @override String get visualSorting => '可视化排序'; @override String get visual => '可视排序'; @override String get insertion => '插入排序'; @override String get bubble => '冒泡排序'; @override String get cocktail => '鸡尾酒排序(双向冒泡排序)'; @override String get comb => '梳排序'; @override String get pigeonHole => '鸽巢排序'; @override String get shell => '希尔排序'; @override String get selection => '选择排序'; @override String get gnome => '侏儒排序'; @override String get cycle => '循环排序'; @override String get heap => '堆排序'; @override String get quick => '快速排序'; @override String get merge => '归并排序'; @override String get sortingAlgorithmConfiguration => '排序算法配置'; @override String get dataCount => '数据数量(个数)'; @override String get timeInterval => '时间间隔(微秒)'; @override String get randomSeed => '随机种子'; @override String get codeGeneration => '代码生成'; @override String get generateCode => '生成代码'; @override String get artifactLocation => '产物位置'; @override String get codeClassLocation => '代码类存放位置'; @override String get resourceDirectory => '资源目录'; @override String get iconfontResourceLocation => 'iconfont 资源存放位置'; @override String get projectPath => '项目路径'; @override String get inputProjectAddress => '请选择或输入项目地址'; @override String get iconfontCompressedPackagePath => 'Iconfont 压缩包路径'; @override String get pleaseSelectOrInputIconfontCompressedPackagePath => '请选择或输入 iconfont 下载的压缩包路径'; @override String get stayTuned => '敬请期待'; @override String get iconFont => 'IconFont'; @override String get dataClass => '数据类'; @override String get stateManagement => '状态管理'; @override String get jsonParsing => 'Json 解析'; @override String get clickHereToJump => '点击这里跳转'; @override String get knowledgeTabToly => '捷特文库'; @override String get knowledgeTabAlgo => '算法演绎'; @override String get knowledgeTabLayout => '布局宝库'; @override String get knowledgeTabPoint => '要点宝库'; @override String get knowledgeConstruction => '正在建设中'; @override String get knowledgeToJuejin => '前往掘金'; @override String get srcPath => '源码地址'; @override String get widgetsInn => '组件酒肆'; @override String get likedWidgets => '珍藏组件'; @override String get relatedComponents => '相关组件'; @override String get backupFavoritesCollectionData => '备份收藏集数据'; @override String get syncFavoritesCollectionData => '同步收藏集数据'; @override String get favoritesCollectionDataReset => '收藏集数据重置'; @override String get resetSuccess => '重置成功!'; @override String get dataSetBackupSuccess => '数据集备份成功!'; @override String get dataSetBackupFailure => '数据集备份失败!'; @override String get dataSynchronizationCopySuccess => '数据同步份成功!'; @override String get dataSynchronizationCopyFailure => '数据同步份失败!'; @override String get destructionRed => '毁灭之红'; @override String get rageOrange => '愤怒之橙'; @override String get warningYellow => '警告之黄'; @override String get camouflageGreen => '伪装之绿'; @override String get coldBlue => '冷漠之蓝'; @override String get infiniteBlue => '无限之靛'; @override String get mysteryPurple => '神秘之紫'; @override String get destinyBlack => '归宿之黑'; @override String get showBackground => '显示背景'; @override String get toly => '张风捷特烈'; @override String get dartHandbook => 'Dart 手册'; @override String get codeCopiedSuccessfully => '代码复制成功'; @override String get favoriteFolderManagement => '收藏夹管理'; @override String get assembly => '组件'; @override String get draw => '绘制'; @override String get knowledge => '知识'; @override String get collection => '收藏'; @override String get my => '我的'; @override String get picture => '幅'; @override String get widgetInn => '组件酒肆'; @override String get emptySearch => '没数据,哥也没办法\n(≡ _ ≡)/~┴┴'; @override String get searchSomething => '哥们,搜点啥...≧◔◡◔≦'; @override String get slogan => 'Flutter 的联合,编程者的联合'; } ================================================ FILE: modules/basic_system/l10n/lib/l10n.dart ================================================ library l10n; export 'ext.dart'; export 'enum/language.dart'; ================================================ FILE: modules/basic_system/l10n/pubspec.yaml ================================================ name: l10n description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/basic_system/l10n/test/l10n_copy.dart ================================================ import 'dart:io'; import 'package:path/path.dart' as path; void main() async { Directory distDir = Directory(path.join(Directory.current.path, '.dart_tool', 'flutter_gen', 'gen_l10n')); if (!distDir.existsSync()) return; Directory srcDir = Directory(path.join(Directory.current.path, 'lib', 'gen_l10n')); if (srcDir.existsSync()) { await srcDir.delete(recursive: true); } else { await srcDir.create(recursive: true); } List entity = distDir.listSync(); for (FileSystemEntity e in entity) { if (e is File) { e.copy(path.join(srcDir.path, path.basename(e.path))); } } } ================================================ FILE: modules/basic_system/l10n/test/l10n_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:l10n/l10n.dart'; void main() { } ================================================ FILE: modules/basic_system/storage/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .packages build/ ================================================ FILE: modules/basic_system/storage/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 channel: stable project_type: package ================================================ FILE: modules/basic_system/storage/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/basic_system/storage/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/basic_system/storage/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/basic_system/storage/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/basic_system/storage/lib/src/app_storage.dart ================================================ import 'package:storage/storage.dart'; import 'db_storage/flutter/article_db_store.dart'; import 'db_storage/flutter/flutter_db_store.dart'; import 'db_storage/flutter_unit/flutter_unit_db_store.dart'; class AppStorage { AppStorage._(); static AppStorage? _instance; factory AppStorage() => _instance ??= AppStorage._(); final FlutterDbStore _flutterDb = FlutterDbStore(); final FlutterUnitDbStore _flutterUnitDb = FlutterUnitDbStore(); final ArticleDbStore _articleDb = ArticleDbStore(); FlutterDbStore get flutter => _flutterDb; ArticleDbStore get article => _articleDb; FlutterUnitDbStore get flutterUnit => _flutterUnitDb; Future init() async { await _flutterDb.open(); await _flutterUnitDb.open(); await _articleDb.open(); } void close() async { await _flutterDb.close(); await _flutterUnitDb.close(); await _articleDb.close(); } } ================================================ FILE: modules/basic_system/storage/lib/src/db_storage/flutter/article_db_store.dart ================================================ import 'dart:async'; import 'package:sqflite/sqflite.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:fx_dao/fx_dao.dart'; import 'package:widget_module/widget_module.dart'; import 'package:artifact/artifact.dart'; class ArticleDbStore extends FxDb { @override String get dbname => 'article.db'; @override int get version => 1; @override Future onCreate(Database db, int version) async {} @override Iterable<(int, MigrationOperation)> get migrations => []; @override Iterable get tables => [ ColumnizeDao(), ArticleDao(), ]; } ================================================ FILE: modules/basic_system/storage/lib/src/db_storage/flutter/flutter_db_store.dart ================================================ import 'dart:async'; import 'package:sqflite/sqflite.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:fx_dao/fx_dao.dart'; import 'package:widget_module/widget_module.dart'; import 'package:widget_repository/widget_repository.dart'; class FlutterDbStore extends FxDb { @override String get dbname => 'flutter.db'; @override int get version => 1; @override Future onCreate(Database db, int version) async {} @override void afterOpen(String dbpath) { super.afterOpen(dbpath); print("===DbPath:$dbpath=============="); } @override Iterable<(int, MigrationOperation)> get migrations => []; @override Iterable get tables => [ CategoryDao(), WidgetDao(), WidgetStatisticsDao(), NodeDao(), LikeDao(), ]; } ================================================ FILE: modules/basic_system/storage/lib/src/db_storage/flutter_unit/dao/cache_dao.dart ================================================ import 'package:fx_dao/fx_dao.dart'; import 'package:sqflite/sqlite_api.dart'; import '../model/cache_po.dart'; class CacheDao with HasDatabase, DbTable{ @override String get createSql =>""" CREATE TABLE IF NOT EXISTS `$name` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `filter` TEXT, `content` TEXT, `update` INTEGER, `create` INTEGER, `type` INTEGER )"""; @override String get name => 'app_cache'; Future insert(CachePo po) => database.insert( name, po.toJson(), conflictAlgorithm: ConflictAlgorithm.replace, ); Future insertOrUpdate(CachePo po) async { bool canUpdate = await shouldUpdate(po.id, po.update); return database.insert( name, po.toJson(), conflictAlgorithm: canUpdate ? ConflictAlgorithm.replace : ConflictAlgorithm.ignore, ); } /// 当前数据是否需要更新 Future shouldUpdate(int id, int updateAt) async { List> data = await database .rawQuery("SELECT `update` FROM $name WHERE id = ?", [id]); // 没有数据,可以更新 if (data.isEmpty) { return true; } // 服务器中数据更新时间,大于本地数据库内容,可以更新 return updateAt > data.first['update']; } Future> query({ required int type, int page = 1, int pageSize = 20, String? filter, }) async { String queryArgs = ''; List args = [type]; queryArgs = "WHERE type = ? "; if(filter!=null){ queryArgs+="AND filter = ? "; args.add(filter); } queryArgs += 'LIMIT ? OFFSET ?'; args.addAll([pageSize, (page - 1) * pageSize]); List> data = await database.rawQuery( "SELECT * FROM $name $queryArgs", args, ); List result = data.map((e) => CachePo.fromJson(e)).toList(); return result; } } ================================================ FILE: modules/basic_system/storage/lib/src/db_storage/flutter_unit/flutter_unit.dart ================================================ export 'dao/cache_dao.dart'; export 'model/cache_po.dart'; ================================================ FILE: modules/basic_system/storage/lib/src/db_storage/flutter_unit/flutter_unit_db_store.dart ================================================ import 'package:fx_dao/fx_dao.dart'; import 'dao/cache_dao.dart'; class FlutterUnitDbStore extends FxDb { @override Iterable get tables => [ CacheDao() ]; @override String get dbname => 'flutter_unit.db'; @override int get version => 1; @override void afterOpen(String dbpath) { super.afterOpen(dbpath); print("====Opend:$dbpath==========="); } @override Iterable<(int, MigrationOperation)> get migrations => []; } ================================================ FILE: modules/basic_system/storage/lib/src/db_storage/flutter_unit/model/cache_po.dart ================================================ class CachePo { // 0 专栏数据缓存 // 1 文章列表数据缓存 final int id; final String filter; final String content; final int type; final int create; final int update; CachePo({ required this.id, required this.filter, required this.content, required this.type, required this.create, required this.update, }); Map toJson() => { "id": id == -1 ? null : id, "filter": filter, "content": content, "type": type, "create": create, "update": update, }; factory CachePo.fromJson(Map map) { return CachePo( id: map['id'], filter: map['filter'], content: map["content"], create: map["create"], type: map["type"], update: map["update"], ); } } ================================================ FILE: modules/basic_system/storage/lib/src/db_storage/storage.dart ================================================ export 'flutter_unit/flutter_unit.dart'; ================================================ FILE: modules/basic_system/storage/lib/src/sp_storage/cao/app_config_cao.dart ================================================ import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:storage/storage.dart'; const String kAppSpKey = 'app-config'; class AppConfigCao{ final SharedPreferences sp; AppConfigCao(this.sp); Future write(AppConfigPo appConfigPo) async { String config = json.encode(appConfigPo); return sp.setString(kAppSpKey, config); } Future read() async { String content = sp.getString(kAppSpKey) ?? "{}"; return AppConfigPo.fromPo(json.decode(content)); } } ================================================ FILE: modules/basic_system/storage/lib/src/sp_storage/exp.dart ================================================ export 'cao/app_config_cao.dart'; export 'models/app_config_po.dart'; export 'sp_storage.dart'; ================================================ FILE: modules/basic_system/storage/lib/src/sp_storage/models/app_config_po.dart ================================================ // 用于维护 App 配置信息的存储类 // 配置信息将通过 sp 存储在 xml 中 class AppConfigPo { final bool showBackGround; final bool showOverlayTool; final bool showPerformanceOverlay; final int fontFamilyIndex; final int languageIndex; final int codeStyleIndex; final int themeModeIndex; final int itemStyleIndex; final int themeColorIndex; AppConfigPo({ this.showBackGround = false, this.showOverlayTool = false, this.showPerformanceOverlay = false, this.fontFamilyIndex = 1, this.languageIndex = 0, this.themeColorIndex = 4, this.codeStyleIndex = 0, this.themeModeIndex = 0, this.itemStyleIndex = 0, }); factory AppConfigPo.fromPo(dynamic map) { return AppConfigPo( showBackGround: map['showBackGround'] ?? false, showOverlayTool: map['showOverlayTool'] ?? false, showPerformanceOverlay: map['showPerformanceOverlay'] ?? false, fontFamilyIndex: map['fontFamilyIndex'] ?? 1, themeColorIndex: map['themeColorIndex'] ?? 5, codeStyleIndex: map['codeStyleIndex'] ?? 0, themeModeIndex: map['themeModeIndex'] ?? 0, itemStyleIndex: map['itemStyleIndex'] ?? 0, languageIndex: map['languageIndex'] ?? 0, ); } Map toJson() => { 'showBackGround': showBackGround, 'showOverlayTool': showOverlayTool, 'showPerformanceOverlay': showPerformanceOverlay, 'fontFamilyIndex': fontFamilyIndex, 'themeColorIndex': themeColorIndex, 'codeStyleIndex': codeStyleIndex, 'themeModeIndex': themeModeIndex, 'itemStyleIndex': itemStyleIndex, 'languageIndex': languageIndex, }; } ================================================ FILE: modules/basic_system/storage/lib/src/sp_storage/sp_storage.dart ================================================ import 'package:shared_preferences/shared_preferences.dart'; import 'cao/app_config_cao.dart'; class SpStorage { SpStorage._(); static final SpStorage _instance = SpStorage._(); factory SpStorage() => _instance; SharedPreferences? _sp; SharedPreferences get spf => _sp!; late AppConfigCao _appConfig; AppConfigCao get appConfig => _appConfig; Future initSp() async { if (_sp != null) return; _sp = _sp ?? await SharedPreferences.getInstance(); _appConfig = AppConfigCao(_sp!); } } ================================================ FILE: modules/basic_system/storage/lib/storage.dart ================================================ library storage; export 'src/db_storage/storage.dart'; export 'src/sp_storage/exp.dart'; export 'src/app_storage.dart'; ================================================ FILE: modules/basic_system/storage/pubspec.yaml ================================================ name: storage description: A new Flutter project. version: 0.0.1 homepage: environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/basic_system/storage/test/db_storage_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); } ================================================ FILE: modules/basic_system/toly_ui/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ build/ ================================================ FILE: modules/basic_system/toly_ui/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" channel: "stable" project_type: package ================================================ FILE: modules/basic_system/toly_ui/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/basic_system/toly_ui/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/basic_system/toly_ui/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/basic_system/toly_ui/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/basic_system/toly_ui/lib/adapter/platform_view_adapter.dart ================================================ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; typedef WidthConditionFun = bool Function(double width); typedef WidgetBuilder = Widget Function(BuildContext context); enum ViewAdapterType { width, platform } class PlatformViewAdapter extends StatelessWidget { final Widget mobile; final Widget desk; final ViewAdapterType type; const PlatformViewAdapter({ super.key, required this.mobile, required this.desk, this.type = ViewAdapterType.width, }); @override Widget build(BuildContext context) { switch (type) { case ViewAdapterType.width: return WidthConditionBuilder( conditionMap: { phoneSize: (_) => mobile, deskSize: (_) => desk, }, ); case ViewAdapterType.platform: bool isDesk = kIsWeb || Platform.isMacOS || Platform.isWindows || Platform.isLinux; return isDesk ? desk : mobile; } } bool phoneSize(double size) { return size > 0 && size <= 500; } bool deskSize(double size) { return size > 500; } } class WidthConditionBuilder extends StatelessWidget { final Map conditionMap; final Widget unMatchWidget; const WidthConditionBuilder({ super.key, required this.conditionMap, this.unMatchWidget = const SizedBox.shrink(), }); @override Widget build(BuildContext context) { return LayoutBuilder(builder: (_, c) { List conditions = conditionMap.keys.toList(); WidthConditionFun? active; for (int i = 0; i < conditions.length; i++) { if (conditions[i](c.maxHeight)) { active = conditions[i]; } } if (active != null) { return conditionMap[active]!(context); } return unMatchWidget; }); } } ================================================ FILE: modules/basic_system/toly_ui/lib/button/feedback_widget.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020-04-10 /// contact me by email 1981462002@qq.com /// 说明: enum FeedMode { scale, fade, rotate, } class FeedbackWidget extends StatefulWidget { final Widget child; final FeedMode mode; final Duration duration; final Function()? onPressed; final Function()? onEnd; final Function()? onLongPressed; final double a; const FeedbackWidget({Key? key, required this.child, this.mode = FeedMode.scale, this.a = 0.9, this.onLongPressed, this.duration = const Duration(milliseconds: 150), this.onPressed, this.onEnd, }) : super(key: key); @override _FeedBackState createState() => _FeedBackState(); } class _FeedBackState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: widget.duration, )..addStatusListener((s) { if (s == AnimationStatus.completed) { _controller.reverse().then((value) { widget.onEnd?.call(); }); } }); } // 当父层状态执行 setState, 当前 State 不会执行 initState,而是 didUpdateWidget, // 因此如果上层状态对某些 widget 配置进行修改,那么当前状态对象便无法知晓,比如 duration 、 // 如果配置不同了需要在 didUpdateWidget 回调中更新 // @override void didUpdateWidget(FeedbackWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.duration != oldWidget.duration) { _controller.duration = widget.duration; } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onLongPress: widget.onLongPressed, behavior: HitTestBehavior.opaque, onTap: () { _controller.forward(); widget.onPressed?.call(); }, child: AnimatedBuilder( animation: _controller, child: widget.child, builder: (ctx, child) => _buildByMode(child, widget.mode), )), ); } Widget _buildByMode(Widget? child, FeedMode mode) { double rate = (widget.a - 1) * _controller.value + 1; switch (mode) { case FeedMode.scale: return Transform.scale(scale: rate, child: widget.child); case FeedMode.fade: return Opacity(opacity: rate, child: widget.child); case FeedMode.rotate: return Transform.rotate(angle: rate * pi * 2, child: widget.child); } } } ================================================ FILE: modules/basic_system/toly_ui/lib/code/code.dart ================================================ export 'code_widget.dart'; export 'high_light_code.dart'; export 'highlighter_style.dart'; export 'language/language.dart'; export 'language/dart_languge.dart'; ================================================ FILE: modules/basic_system/toly_ui/lib/code/code_widget.dart ================================================ /// create by 张风捷特烈 on 2020-04-15 /// contact me by email 1981462002@qq.com /// 说明: import 'package:flutter/material.dart'; import 'high_light_code.dart'; import 'highlighter_style.dart'; import 'language/dart_languge.dart'; class CodeWidget extends StatelessWidget { CodeWidget( {Key? key, required this.code, required this.style, this.fontSize = 13, this.fontFamily}) : super(key: key); final String code; final HighlighterStyle style; final double fontSize; final String? fontFamily; @override Widget build(BuildContext context) { Widget body; Widget codeWidget; try { codeWidget = SelectableText.rich( selectionControls: MaterialTextSelectionControls(), TextSpan( style: TextStyle(fontSize: fontSize, fontFamily: fontFamily), children: [ CodeHighlighter(style: style, language: const DartLanguage()) .format(code) ], ), ); } catch (err) { print(err); codeWidget = SelectableText(code); } body = SingleChildScrollView( child: Container( alignment: Alignment.centerLeft, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: style.backgroundColor ?? const Color(0xffF6F8FA), borderRadius: const BorderRadius.all(Radius.circular(5.0))), child: codeWidget, ), ); return body; } } ================================================ FILE: modules/basic_system/toly_ui/lib/code/high_light_code.dart ================================================ /// create by 张风捷特烈 on 2020-04-15 /// contact me by email 1981462002@qq.com /// 说明: // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:string_scanner/string_scanner.dart'; import 'highlighter_style.dart'; import 'language/dart_languge.dart'; import 'language/language.dart'; /// final SyntaxHighlighterStyle style = SyntaxHighlighterStyle.lightThemeStyle(); /// DartSyntaxHighlighter(style).format(source) abstract class Highlighter { // ignore: one_member_abstracts ProgramLanguage language; Highlighter({required this.language}); TextSpan format(String src); } //暗黑模式下的高亮样式 class CodeHighlighter extends Highlighter { CodeHighlighter( {ProgramLanguage language = const DartLanguage(), HighlighterStyle? style}):super(language: language) { _spans = <_HighlightSpan>[]; _style = style ?? HighlighterStyle.fromColors(HighlighterStyle.lightColor); } late HighlighterStyle _style; String _src=''; late StringScanner _scanner; List<_HighlightSpan> _spans=[]; @override TextSpan format(String src) { _src = src; _scanner = StringScanner(_src); if (_generateSpans()) { // Successfully parsed the code final List formattedText = []; int currentPosition = 0; for (_HighlightSpan span in _spans) { if (currentPosition != span.start) { formattedText .add(TextSpan(text: _src.substring(currentPosition, span.start))); } formattedText.add(TextSpan( style: span.textStyle(_style), text: span.textForSpan(_src))); currentPosition = span.end; } if (currentPosition != _src.length) { formattedText .add(TextSpan(text: _src.substring(currentPosition, _src.length))); } return TextSpan(style: _style.baseStyle, children: formattedText); } else { // Parsing failed, return with only basic formatting return TextSpan(style: _style.baseStyle, text: src); } } bool _generateSpans() { int lastLoopPosition = _scanner.position; while (!_scanner.isDone) { // Skip White space _scanner.scan(RegExp(r'\s+')); // Block comments if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) { _spans.add(_HighlightSpan(_HighlightType.comment, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Line comments if (_scanner.scan(RegExp(r'//'))) { final int startComment = _scanner.lastMatch?.start??0; bool eof = false; int endComment; if (_scanner.scan(RegExp(r'(.*\r\n)|(.*\n)'))) { int? end = _scanner.lastMatch?.end; endComment = end==null?0:end - 1; } else { eof = true; endComment = _src.length; } _spans.add(_HighlightSpan(_HighlightType.comment, startComment, endComment)); if (eof) break; continue; } // Raw r"String" if (_scanner.scan(RegExp(r'r".*"'))) { _spans.add(_HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Raw r'String' if (_scanner.scan(RegExp(r"r'.*'"))) { _spans.add(_HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Multiline """String""" if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) { _spans.add(_HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Multiline '''String''' if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) { _spans.add(_HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // "String" if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) { _spans.add(_HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // 'String' if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) { _spans.add(_HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Double if (_scanner.scan(RegExp(r'\d+\.\d+'))) { _spans.add(_HighlightSpan(_HighlightType.number, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Integer if (_scanner.scan(RegExp(r'\d+'))) { _spans.add(_HighlightSpan(_HighlightType.number, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Punctuation if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) { _spans.add(_HighlightSpan(_HighlightType.punctuation, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Meta data if (_scanner.scan(RegExp(r'@\w+'))) { _spans.add(_HighlightSpan(_HighlightType.keyword, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Words if (_scanner.scan(RegExp(r'\w+'))) { _HighlightType? type; String word = _scanner.lastMatch?[0]??''; if (word.startsWith('_')) word = word.substring(1); if (language.containsKeywords(word)) { type = _HighlightType.keyword; } else if (language.containsInTypes(word)) { type = _HighlightType.keyword; } else if (_firstLetterIsUpperCase(word)) { type = _HighlightType.klass; } else if (word.length >= 2 && word.startsWith('k') && _firstLetterIsUpperCase(word.substring(1))) { type = _HighlightType.constant; } if (type != null) { _spans.add(_HighlightSpan( type, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); } } // Check if this loop did anything if (lastLoopPosition == _scanner.position) { // Failed to parse this file, abort gracefully return false; } lastLoopPosition = _scanner.position; } _simplify(); return true; } void _simplify() { for (int i = _spans.length - 2; i >= 0; i -= 1) { if (_spans[i].type == _spans[i + 1].type && _spans[i].end == _spans[i + 1].start) { _spans[i] = _HighlightSpan(_spans[i].type, _spans[i].start, _spans[i + 1].end); _spans.removeAt(i + 1); } } } bool _firstLetterIsUpperCase(String str) { if (str.isNotEmpty) { final String first = str.substring(0, 1); return first == first.toUpperCase(); } return false; } } enum _HighlightType { number, comment, keyword, string, punctuation, klass, constant } class _HighlightSpan { _HighlightSpan(this.type, this.start, this.end); final _HighlightType type; final int start; final int end; String textForSpan(String src) { return src.substring(start, end); } TextStyle? textStyle(HighlighterStyle? style) { if (type == _HighlightType.number) { return style?.numberStyle; } else if (type == _HighlightType.comment) { return style?.commentStyle; } else if (type == _HighlightType.keyword) { return style?.keywordStyle; } else if (type == _HighlightType.string) { return style?.stringStyle; } else if (type == _HighlightType.punctuation) { return style?.punctuationStyle; } else if (type == _HighlightType.klass) { return style?.classStyle; } else if (type == _HighlightType.constant) { return style?.constantStyle; } else { return style?.baseStyle; } } } ================================================ FILE: modules/basic_system/toly_ui/lib/code/highlighter_style.dart ================================================ /// create by 张风捷特烈 on 2020-04-15 /// contact me by email 1981462002@qq.com /// 说明: import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020-04-11 /// contact me by email 1981462002@qq.com /// 说明: class HighlighterStyle { //句法高亮样式 const HighlighterStyle( { //构造函数 this.baseStyle, //基础样式 this.numberStyle, //数字的样式 this.commentStyle, //注释样式 this.keywordStyle, //关键字样式 this.stringStyle, //字符串样式 this.punctuationStyle, //标点符号样式 this.classStyle, //类名 this.backgroundColor, this.constantStyle}); static List get lightColor => [ 0xFF000000, //基础 0xFF00b0e8, //数字 0xFF9E9E9E, //注释 0xFF9C27B0, //关键 0xFF43A047, //字符串 0xFF000000, //标点符号 0xFF3D62F5, //类名 0xFF795548, //常量 0xffF6F8FA, //背景 ]; static List get darkColor => [ 0xFFFFFFFF, //基础 0xFFDF935F, //数字 0xFF9E9E9E, //注释 0xFF80CBC4, //关键字 0xFFB9CA4A, //字符串 0xFFFFFFFF, //标点符号 0xFF7AA6DA, //类名 0xFF795548, //常量 0xFF1D1F21, //背景 ]; static List get gitHub => [ 0xFF333333, //基础 0xFF008081, //数字 0xFF9D9D8D, //注释 0xFF009999, //关键字 0xFFDD1045, //字符串 0xFF333333, //标点符号 0xFF6F42C1, //类名 0xFF795548, //常量 0xFFF8F8F8, //背景 ]; static List get zenburn => [ 0xFFDCDCDC, //普通字 0xFF87C5C8, //数字 0xFF8F8F8F, //注释 0xFFE4CEAB, //关键字 0xFFCC9493, //字符串 0xFFDCDCDC, //标点符号 0xFFEFEF90, //类名 0xFFEF5350, //常量 0xFF3F3F3F, //背景 ]; static List get mf =>[ 0xFF707D95, //基础 0xFF6897BB, //数字 0xFF629755, //注释 0xFFCC7832, //关键 0xFFF14E9F, //字符串 0xFFFFBB33, //标点符号 0xFF66CCFF, //类名 0xFF9876AA, //常量 0xFF2B2B2B //背景 ]; static List get solarized =>[ 0xFF657B83, // 普通字 0xFFD33682, // 数字 0xFF93A1A1, // 注释 0xFF859900, // 关键字 0xFF2AA198, // 字符串 0xFF859900, // 标点符号 0xFF268BD2, // 类名 0xFF268BD2, //常量 0xFFDDD6C1, // 背景 ]; factory HighlighterStyle.fromColors(List colors) => HighlighterStyle( baseStyle: TextStyle( color: Color(colors[0]), ), numberStyle: TextStyle( color: Color(colors[1]), ), commentStyle: TextStyle( color: Color( colors[2], ), fontStyle: FontStyle.italic), keywordStyle: TextStyle( fontWeight: FontWeight.bold, color: Color( colors[3], ), ), stringStyle: TextStyle( color: Color(colors[4]), ), punctuationStyle: TextStyle( color: Color(colors[5]), ), classStyle: TextStyle( color: Color(colors[6]), ), constantStyle: TextStyle( color: Color(colors[7]), ), backgroundColor: Color(colors[8]), ); final TextStyle? baseStyle; final TextStyle? numberStyle; final TextStyle? commentStyle; final TextStyle? keywordStyle; final TextStyle? stringStyle; final TextStyle? punctuationStyle; final TextStyle? classStyle; final TextStyle? constantStyle; final Color? backgroundColor; } ================================================ FILE: modules/basic_system/toly_ui/lib/code/language/dart_languge.dart ================================================ import 'language.dart'; /// create by 张风捷特烈 on 2021/1/21 /// contact me by email 1981462002@qq.com /// 说明: class DartLanguage extends ProgramLanguage{ const DartLanguage() : super('Dart'); static const List _kDartKeywords = [ 'abstract', 'as', 'assert', 'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'default', 'deferred', 'do', 'dynamic', 'else', 'enum', 'export', 'external', 'extends', 'factory', 'false', 'final', 'finally', 'for', 'get', 'if', 'implements', 'import', 'in', 'is', 'library', 'new', 'null', 'operator', 'part', 'rethrow', 'return', 'set', 'static', 'super', 'switch', 'sync', 'this', 'throw', 'true', 'try', 'typedef', 'var', 'void', 'while', 'with', 'yield' ]; static const List _kDartInTypes = [ 'int', 'double', 'num', 'bool' ]; @override List get keywords => _kDartKeywords; @override List get inTypes => [ 'int', 'double', 'num', 'bool' ]; @override bool containsInTypes(String word) =>_kDartKeywords.contains(word); @override bool containsKeywords(String word)=>_kDartInTypes.contains(word); } ================================================ FILE: modules/basic_system/toly_ui/lib/code/language/language.dart ================================================ /// create by 张风捷特烈 on 2021/1/21 /// contact me by email 1981462002@qq.com /// 说明: abstract class ProgramLanguage { final String name; const ProgramLanguage(this.name); bool containsKeywords(String word); bool containsInTypes(String word); List get keywords; List get inTypes; } ================================================ FILE: modules/basic_system/toly_ui/lib/decorations/round_rect_rab_indicator.dart ================================================ import 'package:flutter/material.dart'; class RoundRectTabIndicator extends Decoration { /// Create an underline style selected tab indicator. /// /// The [borderSide] and [insets] arguments must not be null. const RoundRectTabIndicator({ this.borderSide = const BorderSide(width: 2.0, color: Colors.white), this.insets = EdgeInsets.zero, }) : assert(borderSide != null), assert(insets != null); /// The color and weight of the horizontal line drawn below the selected tab. final BorderSide borderSide; /// Locates the selected tab's underline relative to the tab's boundary. /// /// The [TabBar.indicatorSize] property can be used to define the tab /// indicator's bounds in terms of its (centered) tab widget with /// [TabBarIndicatorSize.label], or the entire tab with /// [TabBarIndicatorSize.tab]. final EdgeInsetsGeometry insets; @override Decoration? lerpFrom(Decoration? a, double t) { if (a is RoundRectTabIndicator) { return RoundRectTabIndicator( borderSide: BorderSide.lerp(a.borderSide, borderSide, t), insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!, ); } return super.lerpFrom(a, t); } @override Decoration? lerpTo(Decoration? b, double t) { if (b is RoundRectTabIndicator) { return RoundRectTabIndicator( borderSide: BorderSide.lerp(borderSide, b.borderSide, t), insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!, ); } return super.lerpTo(b, t); } @override BoxPainter createBoxPainter([VoidCallback? onChanged]) { return _UnderlinePainter(this, onChanged); } Rect _indicatorRectFor(Rect rect, TextDirection textDirection) { assert(rect != null); assert(textDirection != null); final Rect indicator = insets.resolve(textDirection).deflateRect(rect); return Rect.fromLTWH( indicator.left, indicator.bottom - borderSide.width, indicator.width, borderSide.width, ); } @override Path getClipPath(Rect rect, TextDirection textDirection) { return Path()..addRect(_indicatorRectFor(rect, textDirection)); } } class _UnderlinePainter extends BoxPainter { _UnderlinePainter(this.decoration, VoidCallback? onChanged) : assert(decoration != null), super(onChanged); final RoundRectTabIndicator decoration; @override void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { assert(configuration != null); assert(configuration.size != null); final Rect rect = offset & configuration.size!; final TextDirection textDirection = configuration.textDirection!; final Rect indicator = decoration ._indicatorRectFor(rect, textDirection) .deflate(decoration.borderSide.width / 2.0); final Paint paint = decoration.borderSide.toPaint() ..strokeCap = StrokeCap.round; canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint); } } ================================================ FILE: modules/basic_system/toly_ui/lib/default/loading/planet_loading.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import '../../ti/circle.dart'; import '../../ti/math_runner.dart'; /// create by 张风捷特烈 on 2020/10/24 /// contact me by email 1981462002@qq.com /// 说明: class PlateLoading extends StatelessWidget { const PlateLoading({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return SizedBox( width: 150, height: 150, child: Stack(alignment: Alignment.center, children: [ const Text('loading ...'), MathRunner( reverse: false, f: (t) => 0.4*cos(t * pi), g: (t) => 0.7 * sin(t * pi), child: const Circle(color: Colors.blue,radius: 8,)), MathRunner( reverse: false, f: (t) => 0.7 * cos(t * pi), g: (t) => 1 * sin(t * pi), child: const Circle(color: Colors.yellow,radius: 8,)), MathRunner( reverse: false, f: (t) => -0.8 *cos(t * pi), g: (t) => 1 * sin(t * pi), child: const Circle(color: Colors.red,radius: 8,)), MathRunner( reverse: false, f: (t) => 1*cos(t * pi), g: (t) => 0.7 * sin(t * pi), child: const Circle(color: Colors.green,radius: 8,)), MathRunner( reverse: false, f: (t) => 1 * cos(t * pi), g: (t) => -0.7 * sin(t * pi), child: const Circle(color: Colors.purple,radius: 8,)), ]), ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/dialog/alert_conform_dialog.dart ================================================ import 'dart:ui'; import 'package:flutter/material.dart'; class AlertConformDialog extends StatelessWidget { final String title; final String content; final String conformText; final String cancelText; final bool titleDivider; final Function()? onConform; final VoidCallback? onCancel; final Color? conformTextColor; const AlertConformDialog( {this.title = "title", this.content = "content", this.conformText = "确定", this.cancelText = "取消", this.onConform, this.titleDivider = false, this.onCancel, this.conformTextColor, Key? key}) : super(key: key); final TextStyle noticeStyle = const TextStyle(color: Colors.grey, fontSize: 16); final TextStyle cancelTextStyle = const TextStyle( color: Colors.grey, fontSize: 18, fontWeight: FontWeight.bold); final TextStyle subTextStyle = const TextStyle( color: Color(0xff929292), fontSize: 16, fontWeight: FontWeight.bold); @override Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.white, elevation: 0, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(15))), child: SizedBox( // height: 120, width: 320, // color: Colors.green, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(top: titleDivider?10:15,bottom: titleDivider?10:0), child: Center( child: Text( title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), )), ), if(titleDivider) const Divider(height: 1,), Center( child: Padding( padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), child: Text( content, style: noticeStyle, ), ), ), Row( children: [ Expanded( child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { onCancel?.call(); Navigator.of(context).pop(false); }, child: Container( decoration: BoxDecoration( border: Border( top: BorderSide( color: Colors.grey.withAlpha(88), width: 1 / window.devicePixelRatio), right: BorderSide( color: Colors.grey.withAlpha(88), width: 1 / window.devicePixelRatio))), alignment: Alignment.center, height: 50, child: Text( '取消', style: cancelTextStyle, ), ), ), ), Expanded( child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () async { Navigator.of(context).pop(); onConform?.call(); }, child: Container( decoration: BoxDecoration( border: Border( top: BorderSide( color: Colors.grey.withAlpha(88), width: 1 / window.devicePixelRatio), right: BorderSide( color: Colors.grey.withAlpha(88), width: 1 / window.devicePixelRatio))), alignment: Alignment.center, height: 50, child: Text( conformText, style: TextStyle( color: conformTextColor, fontSize: 18, fontWeight: FontWeight.bold), ), ), )), ], ), ], ), ), ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/dialog/delete_message_panel.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; typedef AsyncTask = Future Function(BuildContext context); /// create by 张风捷特烈 on 2020-04-23 /// contact me by email 1981462002@qq.com /// 说明: class DeleteMessagePanel extends StatelessWidget { final String title; final String msg; final String? conformText; final String? cancelText; final AsyncTask task; final Widget? icon; const DeleteMessagePanel({ Key? key, required this.title, required this.msg, required this.task, this.conformText, this.icon, this.cancelText, }) : super(key: key); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; ButtonStyle style = ElevatedButton.styleFrom( backgroundColor: Colors.redAccent, elevation: 0, padding: EdgeInsets.zero, shape: const StadiumBorder(), ); Color? cancelTextColor = isDark?Colors.white:Theme.of(context).textTheme.displayMedium?.color; return SizedBox( width: 350, child: Padding( padding: const EdgeInsets.all(20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ icon?? const Icon( Icons.warning_amber_rounded, color: Colors.orange, ), const SizedBox( width: 20, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold), ), Padding( padding: const EdgeInsets.only( top: 15, bottom: 15, ), child: Text( msg, style: const TextStyle(fontSize: 14), ), ), Row( children: [ const Spacer(), OutlinedButton( onPressed: () { Navigator.of(context).pop(); }, style: OutlinedButton.styleFrom( // backgroundColor: Color(value), elevation: 0, padding: EdgeInsets.zero, shape: const StadiumBorder(), ), child: Text( cancelText?? '取消', style: TextStyle(fontSize: 12, color: cancelTextColor,height: 1), )), const SizedBox( width: 10, ), AsyncButton( conformText: conformText??'删除', task: task, style: style, ), ], ) ], ), ), ], ), ), ); } } class MobileMessagePanel extends StatelessWidget { final String title; final String msg; final String? conformText; final String? cancelText; final Widget? icon; final Color? conformColor; final AsyncTask task; const MobileMessagePanel({ Key? key, required this.title, required this.msg, required this.task, this.conformText, this.conformColor, this.icon, this.cancelText, }) : super(key: key); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; ButtonStyle style = ElevatedButton.styleFrom( backgroundColor: conformColor??Colors.redAccent, elevation: 0, padding: EdgeInsets.zero, minimumSize: Size(70, 35), shape: const StadiumBorder(), ); Color? cancelTextColor =isDark?Colors.white: Theme.of(context).textTheme.displayMedium?.color; return SizedBox( width: 350, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ icon?? const Icon( Icons.warning_amber_rounded, color: Colors.orange, ), const SizedBox( width: 6, ), Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold), )], ), Padding( padding: const EdgeInsets.only( top: 15, bottom: 15, ), child: Text( msg, style: const TextStyle(fontSize: 14), ), ), Row( children: [ const Spacer(), OutlinedButton( onPressed: () { Navigator.of(context).pop(); }, style: OutlinedButton.styleFrom( // backgroundColor: Color(value), elevation: 0, padding: EdgeInsets.zero, minimumSize: Size(70, 35), shape: const StadiumBorder(), ), child: Text( cancelText?? '取消', style: TextStyle(fontSize: 12, color: cancelTextColor,height: 1), )), const SizedBox( width: 10, ), AsyncButton( conformText: conformText??'删除', task: task, style: style, ), ], ) ], ), ), ); } } class AsyncButton extends StatefulWidget { final ButtonStyle? style; final AsyncTask task; final String conformText; const AsyncButton({ super.key, required this.task, this.style, required this.conformText, }); @override State createState() => _AsyncButtonState(); } class _AsyncButtonState extends State { bool _loading = false; @override Widget build(BuildContext context) { return ElevatedButton( onPressed: _loading ? null : _doTask, style: widget.style??ElevatedButton.styleFrom( elevation: 0, padding: EdgeInsets.zero, shape: const StadiumBorder()), child: _loading ? const CupertinoActivityIndicator(radius: 8) :Text( widget.conformText, style: const TextStyle(fontSize: 12,height: 1), )); } void _doTask() async { setState(() { _loading = true; }); await widget.task(context); setState(() { _loading = false; }); } } ================================================ FILE: modules/basic_system/toly_ui/lib/input/edit_panel.dart ================================================ import 'package:flutter/material.dart'; typedef ChangeCallback = void Function(String str); ///输入面板 class EditPanel extends StatefulWidget { const EditPanel( {Key? key, this.backgroundColor = Colors.white, this.color = Colors.lightBlue, this.minLines = 4, this.maxLines = 15, this.fontSize = 14, this.submitClear = true, this.defaultText = "", this.onChange, this.hint = "写点什么..."}) : super(key: key); final Color color; //字颜色 final Color backgroundColor; //背景色颜色 final int minLines; //最小行数 final int maxLines; //最大行数 final double fontSize; //字号 final String hint; //提示字 final bool submitClear; //提交是否清空文字 final ChangeCallback? onChange; //提交监听 final String defaultText; //提交监听 @override _EditPanelState createState() => _EditPanelState(); } class _EditPanelState extends State { Radius _radius=Radius.zero; //边角半径 late TextEditingController _controller; @override void initState() { _radius = Radius.circular(widget.fontSize * 0.618); _controller = TextEditingController(text: widget.defaultText); super.initState(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { Widget panel = TextField( controller: _controller, //输入控制器 keyboardType: TextInputType.text, //键盘类型 textAlign: TextAlign.start, //文字居左 cursorColor: Colors.black, //游标颜色 minLines: widget.minLines, //最小行数 maxLines: widget.maxLines, //最大行数 style: TextStyle( //文字样式 fontSize: widget.fontSize, color: widget.color, backgroundColor: Colors.white), decoration: InputDecoration( //装饰线 filled: true, //是否填充 fillColor: widget.backgroundColor, //填充色 hintText: widget.hint, //提示文字 hintStyle: TextStyle(color: Colors.black26, fontSize: widget.fontSize), //提示文字样式 focusedBorder: UnderlineInputBorder( //聚焦时边线 borderSide: BorderSide(color: widget.backgroundColor), borderRadius: BorderRadius.all(_radius), ), enabledBorder: UnderlineInputBorder( //非聚焦时边线 borderSide: BorderSide(color: widget.backgroundColor), borderRadius: BorderRadius.all(_radius), ), ), onChanged: (str) { //文字变化监听 widget.onChange?.call(str); }, onSubmitted: (str) { //提交监听 FocusScope.of(context).requestFocus(FocusNode()); //收起键盘 if (widget.submitClear) { setState(() { _controller.clear(); }); } }, ); return panel; } } ================================================ FILE: modules/basic_system/toly_ui/lib/input/icon_input.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2021/1/17 /// contact me by email 1981462002@qq.com /// 说明: class IconInput extends StatelessWidget { final Widget textFiled; final IconData icon; const IconInput({Key? key,required this.textFiled,required this.icon}) : super(key: key); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( border: Border.all( color: Colors.grey.withOpacity(0.5), width: 1.0, ), borderRadius: BorderRadius.circular(10.0), ), margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), child: Row( children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), child: Icon( icon, color: Colors.grey, ), ), Container( height: 20.0, width: 1.0, color: Colors.grey.withOpacity(0.5), margin: const EdgeInsets.only(left: 00.0, right: 10.0), ), Expanded( child: textFiled, ) ], ), ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/input/input_button.dart ================================================ import 'package:flutter/material.dart'; typedef SubmitCallback = void Function(String str); class InputButtonConfig { final double height; //高度 final IconData iconData; //图标 final String hint; //提示文字 final double fontSize; //文字大小 final Widget? front; //前面图标 final bool submitClear; //是否提交清空 const InputButtonConfig({ this.height = 36, this.iconData = Icons.add, this.fontSize = 14, this.submitClear = true, this.front, this.hint = "I want to say...", }); } class InputButton extends StatefulWidget { final SubmitCallback? onSubmit; final ValueChanged? onChanged; final VoidCallback? onTap; final InputButtonConfig config; final String defaultText; const InputButton( {Key? key, this.onSubmit, this.onChanged, this.defaultText = '请输入', this.onTap, this.config = const InputButtonConfig()}) : super(key: key); @override _InputButtonState createState() => _InputButtonState(); } class _InputButtonState extends State { double _height = 0; double _fontSize = 0; Radius _radius = Radius.zero; late TextEditingController _controller; @override void initState() { super.initState(); _height = widget.config.height; _fontSize = widget.config.fontSize; _radius = Radius.circular(6); _controller = TextEditingController(text: widget.defaultText); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { Widget textField = TextField( controller: _controller, maxLines: 1, style: TextStyle( fontSize: _fontSize, color: Colors.lightBlue, backgroundColor: Colors.white), decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: widget.config.hint, hintStyle: TextStyle(color: Colors.black26, fontSize: _fontSize), contentPadding: EdgeInsets.only(left: 14.0, top: -_fontSize), focusedBorder: UnderlineInputBorder( borderSide: const BorderSide(color: Colors.white), borderRadius: BorderRadius.only(topLeft: _radius, bottomLeft: _radius), ), enabledBorder: UnderlineInputBorder( borderSide: const BorderSide(color: Colors.white), borderRadius: BorderRadius.only(topLeft: _radius, bottomLeft: _radius), ), ), onChanged: (str) { widget.onChanged?.call(str); }, onTap: widget.onTap, ); Widget btn = ElevatedButton( style: ElevatedButton.styleFrom( elevation: 0, padding: EdgeInsets.zero, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only(topRight: Radius.circular(6),bottomRight: Radius.circular(6))), backgroundColor: const Color(0x99E0E0E0), ), child: Icon(widget.config.iconData,color: Theme.of(context).primaryColor,), onPressed: () { FocusScope.of(context).requestFocus(FocusNode()); //收起键盘 widget.onSubmit?.call(_controller.text); if (widget.config.submitClear) { setState(() { _controller.clear(); }); } }, ); Widget inputBtn = Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: SizedBox( child: textField, height: _height, ), ), ClipRRect( borderRadius: BorderRadius.only( topLeft: Radius.zero, bottomLeft: Radius.zero, topRight: _radius, bottomRight: _radius), child: SizedBox( child: btn, width: _height, height: _height, ), ), ], ); return inputBtn; } } ================================================ FILE: modules/basic_system/toly_ui/lib/markdown/markdown_widget.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'md_text_styles.dart'; import 'syntax_high_lighter.dart'; class MarkdownWidget extends StatelessWidget { static const int kWhite = 0; static const int kDarkLight = 1; static const int kDarkTheme = 2; final String markdownData; final int style; const MarkdownWidget({Key? key, this.markdownData = "", this.style = kWhite}) : super(key: key); MarkdownStyleSheet _getCommonSheet(BuildContext context, Color codeBackground) { MarkdownStyleSheet markdownStyleSheet = MarkdownStyleSheet.fromTheme(Theme.of(context)); return markdownStyleSheet.copyWith( codeblockDecoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(4.0)), color: codeBackground, border: Border.all( color: MdTextStyles.subTextColor, width: 0.3))) .copyWith( blockquoteDecoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(4.0)), color: MdTextStyles.subTextColor, border: Border.all( color: MdTextStyles.subTextColor, width: 0.3)), blockquote: MdTextStyles.smallTextWhite); } _getStyleSheetDark(BuildContext context) { return _getCommonSheet(context, const Color.fromRGBO(40, 44, 52, 1.00)).copyWith( p: MdTextStyles.smallTextWhite, h1: MdTextStyles.largeLargeTextWhite, h2: MdTextStyles.largeTextWhiteBold, h3: MdTextStyles.normalTextMitWhiteBold, h4: MdTextStyles.middleTextWhite, h5: MdTextStyles.smallTextWhite, h6: MdTextStyles.smallTextWhite, em: const TextStyle(fontStyle: FontStyle.italic), strong: MdTextStyles.middleTextWhiteBold, code: MdTextStyles.smallSubText, ); } MarkdownStyleSheet _getStyleSheetWhite(BuildContext context) { return _getCommonSheet(context, const Color.fromRGBO(40, 44, 52, 1.00)).copyWith( p: MdTextStyles.smallText, h1: MdTextStyles.largeLargeText, h2: MdTextStyles.largeTextBold, h3: MdTextStyles.normalTextBold, h4: MdTextStyles.middleText, h5: MdTextStyles.smallText, h6: MdTextStyles.smallText, strong: MdTextStyles.middleTextBold, code: MdTextStyles.smallSubText, ); } _getStyleSheetTheme(BuildContext context) { return _getCommonSheet(context, const Color.fromRGBO(40, 44, 52, 1.00)).copyWith( p: MdTextStyles.smallTextWhite, h1: MdTextStyles.largeLargeTextWhite, h2: MdTextStyles.largeTextWhiteBold, h3: MdTextStyles.normalTextMitWhiteBold, h4: MdTextStyles.middleTextWhite, h5: MdTextStyles.smallTextWhite, h6: MdTextStyles.smallTextWhite, em: const TextStyle(fontStyle: FontStyle.italic), strong: MdTextStyles.middleTextWhiteBold, code: MdTextStyles.smallSubText, ); } _getBackgroundColor(context) { Color background = MdTextStyles.white; switch (style) { case kDarkLight: background = MdTextStyles.primaryLightValue; break; case kDarkTheme: background = Theme.of(context).primaryColor; break; } return background; } _getStyle(BuildContext context) { MarkdownStyleSheet styleSheet = _getStyleSheetWhite(context); switch (style) { case kDarkLight: styleSheet = _getStyleSheetDark(context); break; case kDarkTheme: styleSheet = _getStyleSheetTheme(context); break; } return styleSheet; } _getMarkDownData(String markdownData) { ///优化图片显示 RegExp exp = RegExp(r'!\[.*\]\((.+)\)'); RegExp expImg = RegExp("|/>)"); RegExp expSrc = RegExp("src=['\"]?([^'\"]*)['\"]?"); String mdDataCode = markdownData; try { Iterable tags = exp.allMatches(markdownData); if (tags.isNotEmpty) { for (Match m in tags) { String imageMatch = m.group(0)??''; if (!imageMatch.contains(".svg")) { String match = imageMatch.replaceAll(")", "?raw=true)"); if (!match.contains(".svg") && match.contains("http")) { ///增加点击 String src = match .replaceAll( RegExp(r'!\[.*\]\('), "") .replaceAll(")", ""); String actionMatch = "[$match]($src)"; match = actionMatch; } else { match = ""; } mdDataCode = mdDataCode.replaceAll(m.group(0)??'', match); } } } ///优化img标签的src资源 tags = expImg.allMatches(markdownData); if (tags.isNotEmpty) { for (Match m in tags) { String imageTag = m.group(0)??''; String match = imageTag; Iterable srcTags = expSrc.allMatches(imageTag); for (Match srcMatch in srcTags) { String srcString = srcMatch.group(0)??''; if (srcString.contains("http")) { String newSrc = srcString.substring( srcString.indexOf("http"), srcString.length - 1) + "?raw=true"; match = "[![]($newSrc)]($newSrc)"; } } mdDataCode = mdDataCode.replaceAll(imageTag, match); } } } catch (e) { print(e.toString()); } return mdDataCode; } @override Widget build(BuildContext context) { return Container( color: _getBackgroundColor(context), padding: const EdgeInsets.all(5.0), child: SingleChildScrollView( child: MarkdownBody( styleSheet: _getStyle(context), syntaxHighlighter: Highlighter(), data: _getMarkDownData(markdownData), onTapLink: (String text, String? href, String title) { // CommonUtils.launchUrl(context, source); }, ), ), ); } } class Highlighter extends SyntaxHighlighter { @override TextSpan format(String source) { String showSource = source.replaceAll("<", "<"); showSource = showSource.replaceAll(">", ">"); return DartSyntaxHighlighter().format(showSource); } } ================================================ FILE: modules/basic_system/toly_ui/lib/markdown/md_text_styles.dart ================================================ import 'package:flutter/material.dart'; class MdTextStyles{ static const Color primaryValue = Color(0xFF24292E); static const Color primaryLightValue = Color(0xFF42464b); static const Color primaryDarkValue = Color(0xFF121917); static const Color miWhite = Color(0xffececec); static const Color white = Color(0xFFFFFFFF); static const Color actionBlue = Color(0xff267aff); static const Color subTextColor = Color(0xff959595); static const Color subLightTextColor = Color(0xffc4c4c4); static const Color mainTextColor = primaryDarkValue; static const Color textColorWhite = white; static const lagerTextSize = 30.0; static const bigTextSize = 23.0; static const normalTextSize = 18.0; static const middleTextWhiteSize = 16.0; static const smallTextSize = 14.0; static const minTextSize = 12.0; static const minText = TextStyle( color: subLightTextColor, fontSize: minTextSize, ); static const smallTextWhite = TextStyle( color: textColorWhite, fontSize: smallTextSize, ); static const smallText = TextStyle( color: mainTextColor, fontSize: smallTextSize, ); static const smallTextBold = TextStyle( color: mainTextColor, fontSize: smallTextSize, fontWeight: FontWeight.bold, ); static const smallSubLightText = TextStyle( color: subLightTextColor, fontSize: smallTextSize, ); static const smallActionLightText = TextStyle( color: actionBlue, fontSize: smallTextSize, ); static const smallMiLightText = TextStyle( color: miWhite, fontSize: smallTextSize, ); static const smallSubText = TextStyle( color: subTextColor, fontSize: smallTextSize, ); static const middleText = TextStyle( color: mainTextColor, fontSize: middleTextWhiteSize, ); static const middleTextWhite = TextStyle( color: textColorWhite, fontSize: middleTextWhiteSize, ); static const middleSubText = TextStyle( color: subTextColor, fontSize: middleTextWhiteSize, ); static const middleSubLightText = TextStyle( color: subLightTextColor, fontSize: middleTextWhiteSize, ); static const middleTextBold = TextStyle( color: mainTextColor, fontSize: middleTextWhiteSize, fontWeight: FontWeight.bold, ); static const middleTextWhiteBold = TextStyle( color: textColorWhite, fontSize: middleTextWhiteSize, fontWeight: FontWeight.bold, ); static const middleSubTextBold = TextStyle( color: subTextColor, fontSize: middleTextWhiteSize, fontWeight: FontWeight.bold, ); static const normalText = TextStyle( color: mainTextColor, fontSize: normalTextSize, ); static const normalTextBold = TextStyle( color: mainTextColor, fontSize: normalTextSize, fontWeight: FontWeight.bold, ); static const normalSubText = TextStyle( color: subTextColor, fontSize: normalTextSize, ); static const normalTextWhite = TextStyle( color: textColorWhite, fontSize: normalTextSize, ); static const normalTextMitWhiteBold = TextStyle( color: miWhite, fontSize: normalTextSize, fontWeight: FontWeight.bold, ); static const normalTextActionWhiteBold = TextStyle( color: actionBlue, fontSize: normalTextSize, fontWeight: FontWeight.bold, ); static const normalTextLight = TextStyle( color: primaryLightValue, fontSize: normalTextSize, ); static const largeText = TextStyle( color: mainTextColor, fontSize: bigTextSize, ); static const largeTextBold = TextStyle( color: mainTextColor, fontSize: bigTextSize, fontWeight: FontWeight.bold, ); static const largeTextWhite = TextStyle( color: textColorWhite, fontSize: bigTextSize, ); static const largeTextWhiteBold = TextStyle( color: textColorWhite, fontSize: bigTextSize, fontWeight: FontWeight.bold, ); static const largeLargeTextWhite = TextStyle( color: textColorWhite, fontSize: lagerTextSize, fontWeight: FontWeight.bold, ); static const largeLargeText = TextStyle( color: primaryValue, fontSize: lagerTextSize, fontWeight: FontWeight.bold, ); } ================================================ FILE: modules/basic_system/toly_ui/lib/markdown/syntax_high_lighter.dart ================================================ import 'package:string_scanner/string_scanner.dart'; import 'package:flutter/material.dart'; class SyntaxHighlighterStyle { SyntaxHighlighterStyle( {this.baseStyle, this.numberStyle, this.commentStyle, this.keywordStyle, this.stringStyle, this.punctuationStyle, this.classStyle, this.constantStyle}); //123 static SyntaxHighlighterStyle defaultStyle() { return SyntaxHighlighterStyle( baseStyle: const TextStyle(color: Color.fromRGBO(212, 212, 212, 1.0)), numberStyle: TextStyle(color: Colors.blue[800]), commentStyle: const TextStyle(color: Color.fromRGBO(124, 126, 120, 1.0)), keywordStyle: const TextStyle(color: Color.fromRGBO(228, 125, 246, 1.0)), stringStyle: const TextStyle(color: Color.fromRGBO(150, 190, 118, 1.0)), punctuationStyle: const TextStyle(color: Color.fromRGBO(212, 212, 212, 1.0)), classStyle: const TextStyle(color: Color.fromRGBO(150, 190, 118, 1.0)), constantStyle: TextStyle(color: Colors.brown[500])); } final TextStyle? baseStyle; final TextStyle? numberStyle; final TextStyle? commentStyle; final TextStyle? keywordStyle; final TextStyle? stringStyle; final TextStyle? punctuationStyle; final TextStyle? classStyle; final TextStyle? constantStyle; } abstract class SyntaxCostomHighlighter { TextSpan format(String src); } class DartSyntaxHighlighter extends SyntaxCostomHighlighter { DartSyntaxHighlighter([this._style]) { _spans = <_HighlightSpan>[]; _style ??= SyntaxHighlighterStyle.defaultStyle(); } SyntaxHighlighterStyle? _style; static const List _kKeywords = [ 'abstract', 'as', 'assert', 'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'default', 'deferred', 'do', 'dynamic', 'else', 'enum', 'export', 'external', 'extends', 'factory', 'false', 'final', 'finally', 'for', 'get', 'if', 'implements', 'import', 'in', 'is', 'library', '', 'null', 'operator', 'part', 'rethrow', 'return', 'set', 'static', 'super', 'switch', 'sync', 'this', 'throw', 'true', 'try', 'typedef', 'var', 'void', 'while', 'with', 'yield', 'print', 'function', 'public', 'protected', 'private', 'namespace', 'using', 'extends', 'let', 'export', 'default', 'import', 'from', 'PureCommponent', 'constructor', 'render', '\$sudo', 'console', 'instanceof' ]; static const List _kBuiltInTypes = [ 'int', 'double', 'num', 'bool' ]; String _src=''; late StringScanner _scanner; List<_HighlightSpan> _spans =[]; @override TextSpan format(String src) { _src = src; _scanner = StringScanner(_src); if (_generateSpans()) { // Successfully parsed the code List formattedText = []; int currentPosition = 0; for (_HighlightSpan span in _spans) { if (currentPosition != span.start) { formattedText.add( TextSpan(text: _src.substring(currentPosition, span.start))); } formattedText.add( TextSpan( style: span.textStyle(_style), text: span.textForSpan(_src))); currentPosition = span.end; } if (currentPosition != _src.length) { formattedText.add( TextSpan(text: _src.substring(currentPosition, _src.length))); } return TextSpan(style: _style?.baseStyle, children: formattedText); } else { // Parsing failed, return with only basic formatting return TextSpan(style: _style?.baseStyle, text: src); } } bool _generateSpans() { int lastLoopPosition = _scanner.position; try { while (!_scanner.isDone) { // Skip White space _scanner.scan( RegExp(r"\s+")); // Block comments if (_scanner.scan( RegExp(r"/\*(.|\n)*\*/"))) { _spans.add( _HighlightSpan(_HighlightType.comment, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Line comments if (_scanner.scan("//")) { int startComment = _scanner.lastMatch?.start??0; bool eof = false; int endComment; if (_scanner.scan( RegExp(r".*\n"))) { endComment = _scanner.lastMatch?.end??0 - 1; } else { eof = true; endComment = _src.length; } _spans.add( _HighlightSpan( _HighlightType.comment, startComment, endComment)); if (eof) break; continue; } if (_scanner.scan("#")) { int startComment = _scanner.lastMatch?.start??0; bool eof = false; int endComment; if (_scanner.scan( RegExp(r".*\n"))) { endComment = _scanner.lastMatch?.end??0 - 1; } else { eof = true; endComment = _src.length; } _spans.add( _HighlightSpan( _HighlightType.comment, startComment, endComment)); if (eof) break; continue; } // Raw r"String" if (_scanner.scan( RegExp(r'r".*"'))) { _spans.add( _HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Raw r'String' if (_scanner.scan( RegExp(r"r'.*'"))) { _spans.add( _HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Multiline """String""" if (_scanner.scan( RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) { _spans.add( _HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Multiline '''String''' if (_scanner.scan( RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) { _spans.add( _HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // "String" if (_scanner.scan( RegExp(r'"(?:[^"\\]|\\.)*"'))) { _spans.add( _HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // 'String' if (_scanner.scan( RegExp(r"'(?:[^'\\]|\\.)*'"))) { _spans.add( _HighlightSpan(_HighlightType.string, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Double if (_scanner.scan( RegExp(r"\d+\.\d+"))) { _spans.add( _HighlightSpan(_HighlightType.number, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Integer if (_scanner.scan( RegExp(r"\d+"))) { _spans.add( _HighlightSpan(_HighlightType.number, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Punctuation if (_scanner.scan( RegExp(r"[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]"))) { _spans.add( _HighlightSpan(_HighlightType.punctuation, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } //中文 if (_scanner.scan( RegExp(r"[\u4e00-\u9fa5]"))) { _spans.add( _HighlightSpan(_HighlightType.punctuation, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Metadata if (_scanner.scan( RegExp(r"@\w+"))) { _spans.add( _HighlightSpan(_HighlightType.keyword, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); continue; } // Words if (_scanner.scan( RegExp(r"\w+"))) { _HighlightType? type; String word = _scanner.lastMatch![0]!; if (word.startsWith("_")) word = word.substring(1); if (_kKeywords.contains(word)) { type = _HighlightType.keyword; } else if (_kBuiltInTypes.contains(word)) { type = _HighlightType.keyword; } else if (_firstLetterIsUpperCase(word)) { type = _HighlightType.klass; } else if (word.length >= 2 && word.startsWith("k") && _firstLetterIsUpperCase(word.substring(1))) { type = _HighlightType.constant; } if (type != null) { _spans.add( _HighlightSpan( type, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0)); } } // Check if this loop did anything if (lastLoopPosition == _scanner.position) { // Failed to parse this file, abort gracefully if (_spans.isNotEmpty) { _spans.add( _HighlightSpan(_HighlightType.punctuation, lastLoopPosition, _scanner.string.length - 1)); _simplify(); return true; } return false; } lastLoopPosition = _scanner.position; } } catch (e) { print(e.toString()); } _simplify(); return true; } void _simplify() { for (int i = _spans.length - 2; i >= 0; i -= 1) { if (_spans[i].type == _spans[i + 1].type && _spans[i].end == _spans[i + 1].start) { _spans[i] = _HighlightSpan( _spans[i].type, _spans[i].start, _spans[i + 1].end); _spans.removeAt(i + 1); } } } bool _firstLetterIsUpperCase(String str) { if (str.isNotEmpty) { String first = str.substring(0, 1); return first == first.toUpperCase(); } return false; } } enum _HighlightType { number, comment, keyword, string, punctuation, klass, constant } class _HighlightSpan { _HighlightSpan(this.type, this.start, this.end); final _HighlightType type; final int start; final int end; String textForSpan(String src) { return src.substring(start, end); } TextStyle? textStyle(SyntaxHighlighterStyle? style) { if (type == _HighlightType.number) { return style?.numberStyle; } else if (type == _HighlightType.comment) { return style?.commentStyle; } else if (type == _HighlightType.keyword) { return style?.keywordStyle; } else if (type == _HighlightType.string) { return style?.stringStyle; } else if (type == _HighlightType.punctuation) { return style?.punctuationStyle; } else if (type == _HighlightType.klass) { return style?.classStyle; } else if (type == _HighlightType.constant) { return style?.constantStyle; } else { return style?.baseStyle; } } } ================================================ FILE: modules/basic_system/toly_ui/lib/object/windmill.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; class WindmillWidget extends StatelessWidget { final double rotate; final double radius; const WindmillWidget({Key? key, this.rotate=0,this.radius = 60}) : super(key: key); @override Widget build(BuildContext context) { return CustomPaint( size: Size(radius, radius), painter: WindmillPainter(rotate), ); } } const List kColors = [ Color(0xffE74437), Color(0xffFBBD19), Color(0xff3482F0), Color(0xff30A04C) ]; class WindmillPainter extends CustomPainter { final double rotate; WindmillPainter(this.rotate); @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); double d = size.width * 0.5; canvas.rotate(rotate); Paint paint = Paint(); for (Color color in kColors) { Path path1 = Path() ..moveTo(0, -d * 46 / 203) ..lineTo(0, -d * 203 / 203) ..lineTo(102 / 203 * d, -102 / 203 * d) ..lineTo(12 / 203 * d, -12 / 203 * d) ..close(); canvas.drawPath(path1, paint..color = color); Path path2 = Path() ..moveTo(12 / 203 * d, -12 / 203 * d) ..lineTo(102 / 203 * d, -102 / 203 * d) ..lineTo(102 / 203 * d, 0) ..lineTo(46 / 203 * d, 0) ..close(); canvas.drawPath(path2, paint..color = color.withOpacity(0.2)); canvas.rotate(pi / 2); } } @override bool shouldRepaint(covariant WindmillPainter oldDelegate) => oldDelegate.rotate!=rotate; } ================================================ FILE: modules/basic_system/toly_ui/lib/popable/drop_selectable_widget.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; typedef OnDropSelected = void Function(int index); class DropSelectableWidget extends StatefulWidget { final List data; final OnDropSelected? onDropSelected; final Color disableColor; final double iconSize; final double height; final double width; final double fontSize; const DropSelectableWidget( {Key? key, this.data = const [], this.onDropSelected, this.disableColor = Colors.black, this.iconSize = 24, this.height = 30, this.width = 200, this.fontSize = 14, }) : super(key: key); @override _DropSelectableWidgetState createState() => _DropSelectableWidgetState(); } class _DropSelectableWidgetState extends State with SingleTickerProviderStateMixin { late FocusNode _node; bool _focused = false; late FocusAttachment _nodeAttachment; OverlayEntry? _overlayEntry; late AnimationController _ctrl; late Animation animation; final LayerLink layerLink = LayerLink(); int _selectedIndex = 0; @override void initState() { super.initState(); _ctrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 200), ); animation = Tween(begin: 0, end: pi).animate(_ctrl); _node = FocusNode() ..addListener(() { if (_node.hasFocus != _focused) { if (!_focused) { _ctrl.forward(); _showOverlay(); } else { _hideOverlay(); _ctrl.reverse(); } setState(() { _focused = _node.hasFocus; }); } }); _nodeAttachment = _node.attach(context); } @override void dispose() { _node.dispose(); _ctrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { _nodeAttachment.reparent(); return TapRegion( groupId: 'selector', onTapOutside: (_){ _node.unfocus(); }, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { if (_focused) { _node.unfocus(); } else { _node.requestFocus(); } }, child: CompositedTransformTarget( link: layerLink, child: buildTarget(), ), ), ); } void _showOverlay() { _overlayEntry = _createOverlayEntry(); Overlay.of(context)?.insert(_overlayEntry!); } void _hideOverlay() { _overlayEntry?.remove(); } Widget buildTarget() { return Container( width: widget.width, height: widget.height, padding: const EdgeInsets.only(left: 10, right: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), border: Border.all( color: _focused ? Colors.blue : widget.disableColor, )), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( widget.data.isNotEmpty ? widget.data[_selectedIndex] : "暂无数据",style: TextStyle( height: 1, fontSize: widget.fontSize ),), AnimatedBuilder( animation: animation, builder: (_, child) => Transform.rotate( angle: animation.value, child: child, ), child: Icon( Icons.keyboard_arrow_down, size: widget.iconSize, ), ), ], ), ); } OverlayEntry _createOverlayEntry() => OverlayEntry( builder: (BuildContext context) => UnconstrainedBox( child: CompositedTransformFollower( link: layerLink, targetAnchor: Alignment.bottomCenter, followerAnchor: Alignment.topCenter, child: Padding( padding: const EdgeInsets.only(top: 4.0), child: Material( shape: const RoundedRectangleBorder( side: BorderSide.none, borderRadius: BorderRadius.all(Radius.circular(5))), elevation: 1, child: ClipRRect( borderRadius: BorderRadius.circular(5), child: Container( height: 200, // alignment: Alignment.center, decoration: const BoxDecoration( color: Color(0xffDAE3FF), ), // padding: const EdgeInsets.only(top: 5), width: widget.width, child: CupertinoScrollbar( child: ListView.builder( padding: EdgeInsets.zero, // shrinkWrap: true, itemCount: widget.data.length, itemBuilder: _buildItem), ), ), ), ), ), ), ), ); Widget _buildItem(BuildContext context, int index) { return TapRegion( groupId: 'selector', child: Material( child: InkWell( onTap: () { if (_selectedIndex != index) widget.onDropSelected?.call(index); _selectedIndex = index; _overlayEntry?.markNeedsBuild(); _node.unfocus(); }, child: Container( padding: const EdgeInsets.all(8), color: index == _selectedIndex ? Colors.blue.withOpacity(0.2) : Colors.transparent, child: Text(widget.data[index],style: TextStyle(fontSize: widget.fontSize),)), ), ), ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/selector/burst_menu.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/11/17 /// contact me by email 1981462002@qq.com /// 说明: enum BurstType { circle, topLeft, bottomLeft, topRight, bottomRight, halfCircle, } typedef BurstMenuItemClick = bool Function(int index); class BurstMenu extends StatefulWidget { final List menus; final Widget center; final double radius; final double startAngle; final double swapAngle; final double hideOpacity; final Duration duration; final BurstType burstType; final Curve curve; final BurstMenuItemClick? burstMenuItemClick; const BurstMenu({ Key? key, required this.menus, required this.center, this.radius = 100, this.swapAngle = 120, this.startAngle = -60, this.hideOpacity = 0, this.curve = Curves.ease, this.duration = const Duration(milliseconds: 300), this.burstType = BurstType.circle, this.burstMenuItemClick, }) : super(key: key); const BurstMenu.topLeft({Key? key, required this.menus, this.burstMenuItemClick, this.radius = 100, required this.center, this.hideOpacity = 0, this.curve = Curves.ease, this.duration = const Duration(milliseconds: 300), this.burstType = BurstType.topLeft, this.swapAngle = 90, this.startAngle = 0, }) : super(key: key); const BurstMenu.bottomLeft({Key? key, required this.menus, this.burstMenuItemClick, this.radius = 100, required this.center, this.hideOpacity = 0, this.curve = Curves.ease, this.duration = const Duration(milliseconds: 300), this.burstType = BurstType.bottomLeft, this.swapAngle = 90, this.startAngle = -90, }) : super(key: key); const BurstMenu.topRight({Key? key, required this.menus, this.burstMenuItemClick, this.radius = 100, required this.center, this.hideOpacity = 0, this.curve = Curves.ease, this.duration = const Duration(milliseconds: 500), this.burstType = BurstType.topRight, this.swapAngle = -90, this.startAngle = 180, }) : super(key: key); const BurstMenu.bottomRight({Key? key, required this.menus, this.burstMenuItemClick, this.radius = 100, required this.center, this.hideOpacity = 0, this.curve = Curves.ease, this.duration = const Duration(milliseconds: 300), this.burstType = BurstType.bottomRight, this.swapAngle = 90, this.startAngle = 180, }) : super(key: key); @override BurstMenuState createState() => BurstMenuState(); } class BurstMenuState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; // 是否已关闭 bool _closed = true; late Animation curveAnim; // 1.定义曲线动画 @override void initState() { super.initState(); _controller = AnimationController( duration: widget.duration, vsync: this, ); curveAnim = CurvedAnimation(parent: _controller, curve: widget.curve); //<--2.创建曲线动画 } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( width: widget.radius * 2, height: widget.burstType == BurstType.halfCircle ? widget.radius : widget.radius * 2, alignment: Alignment.center, // color: Colors.orange.withOpacity(0.5), child: Flow( delegate: _CircleFlowDelegate(curveAnim, startAngle: widget.startAngle, hideOpacity: widget.hideOpacity, swapAngle: widget.swapAngle, burstType: widget.burstType), children: [ ...widget.menus.asMap().keys.map((int index) => GestureDetector( onTap: () => _handleItemClick(index), child: widget.menus[index], )), GestureDetector(onTap: toggle, child: widget.center) ], )); } void _handleItemClick(int index) { if (widget.burstMenuItemClick == null) { toggle(); return; } bool close = widget.burstMenuItemClick?.call(index) ?? false; if (close) toggle(); } @override void didUpdateWidget(BurstMenu oldWidget) { super.didUpdateWidget(oldWidget); if (widget.duration != oldWidget.duration) { _controller.dispose(); _controller = AnimationController(duration: widget.duration, vsync: this); } if (widget.curve != oldWidget.curve || widget.duration != oldWidget.duration) { curveAnim = CurvedAnimation(parent: _controller, curve: widget.curve); } } void toggle() { print("--$_closed------------"); if (_closed) { _controller.forward(); } else { _controller.reverse(); } _closed = !_closed; } } class _CircleFlowDelegate extends FlowDelegate { // 菜单圆弧的扫描角度 final double swapAngle; // 菜单圆弧的起始角度 final double startAngle; final double hideOpacity; final BurstType burstType; final Animation animation; _CircleFlowDelegate( this.animation, { this.swapAngle = 120, this.hideOpacity = 0.3, this.startAngle = -60, this.burstType = BurstType.circle, }) : super(repaint: animation); //绘制孩子的方法 @override void paintChildren(FlowPaintingContext context) { double radius = context.size.shortestSide / 2; final double halfCenterSize = context.getChildSize(context.childCount - 1)?.width ?? 0 / 2; switch (burstType) { case BurstType.circle: paintWithOffset(context, Offset.zero); break; case BurstType.topLeft: Offset centerOffset = Offset(-radius + halfCenterSize, -radius + halfCenterSize); paintWithOffset(context, centerOffset); break; case BurstType.bottomLeft: Offset centerOffset = Offset(-radius + halfCenterSize, radius - halfCenterSize); paintWithOffset(context, centerOffset); break; case BurstType.topRight: Offset centerOffset = Offset(radius - halfCenterSize, -radius + halfCenterSize); paintWithOffset(context, centerOffset); break; case BurstType.bottomRight: Offset centerOffset = Offset(radius - halfCenterSize, radius - halfCenterSize); paintWithOffset(context, centerOffset); break; case BurstType.halfCircle: Offset centerOffset = Offset(radius, radius - halfCenterSize); paintWithOffset(context, centerOffset); break; } } void paintWithOffset(FlowPaintingContext context, Offset centerOffset) { final double radius = context.size.shortestSide / 2; final int count = context.childCount - 1; final double perRad = swapAngle / 180 * pi / (count - 1); final double rotate = startAngle / 180 * pi; if (animation.value > hideOpacity) { for (int i = 0; i < count; i++) { Size? size = context.getChildSize(i); if(size == null) continue; final double cSizeX = context.getChildSize(i)!.width/ 2; final double cSizeY = context.getChildSize(i)!.height/ 2; final double beforeRadius = (radius - cSizeX); final double now = beforeRadius + centerOffset.dy.abs(); final swapRadius = (radius - cSizeX) / beforeRadius * now; final double offsetX = animation.value * swapRadius * cos(i * perRad + rotate) + radius + centerOffset.dx; final double offsetY = animation.value * swapRadius * sin(i * perRad + rotate) + radius + centerOffset.dy; context.paintChild( i, transform: Matrix4.translationValues( offsetX - cSizeX, offsetY - cSizeY, 0.0, ), opacity: animation.value, ); } } Size? size = context.getChildSize(context.childCount - 1); if (size == null) return; context.paintChild( context.childCount - 1, transform: Matrix4.translationValues( radius - size.width / 2 + centerOffset.dx, radius - size.height / 2 + centerOffset.dy, 0.0, ), ); } @override bool shouldRepaint(FlowDelegate oldDelegate) => false; } ================================================ FILE: modules/basic_system/toly_ui/lib/selector/color_chooser.dart ================================================ import 'package:flutter/material.dart'; import '../button/feedback_widget.dart'; typedef CheckCallback = void Function(T color); class ColorChooser extends StatefulWidget { const ColorChooser( {Key? key, this.defaultIndex=0, this.radius = 10, required this.colors, required this.onChecked}) : super(key: key); final double radius; final List colors; final Function(Color)? onChecked; final int defaultIndex; @override _ColorChooserState createState() => _ColorChooserState(); } class _ColorChooserState extends State { List _checkLi=[]; int _perPosition = 0; @override void initState() { _perPosition=widget.defaultIndex; _checkLi = List.generate(widget.colors.length, (_) => false); _checkLi[_perPosition] = true; super.initState(); } @override Widget build(BuildContext context) { List li = []; for (int i = 0; i < widget.colors.length; i++) { li.add(FeedbackWidget( a: 0.8, onPressed: () { _checkLi[_perPosition] = false; _perPosition = i; _checkLi[i] = true; widget.onChecked?.call(widget.colors[i]); setState(() {}); }, child: CircleAvatar( backgroundColor: widget.colors[i], radius: widget.radius, child: _checkLi[i] ? const Icon( Icons.star, size: 15, color: Colors.white, ) : const SizedBox.shrink(), // checked: _checkLi[i] ))); } return Wrap(spacing: 10, runSpacing: 10, children: li); } } class IconChooser extends StatefulWidget { const IconChooser( {Key? key, this.radius = 20, required this.icons, required this.onChecked, this.initialIndex = 0}) : super(key: key); final double radius; final List icons; final int initialIndex; final CheckCallback onChecked; @override _IconChooserState createState() => _IconChooserState(); } class _IconChooserState extends State { List _checkLi=[]; int _perPosition = 0; @override void initState() { _checkLi = List.generate(widget.icons.length, (_) => false); _checkLi[widget.initialIndex] = true; super.initState(); } @override Widget build(BuildContext context) { List li = []; for (int i = 0; i < widget.icons.length; i++) { li.add(GestureDetector( onTap: () { _checkLi[_perPosition] = false; _perPosition = i; _checkLi[i] = true; widget.onChecked(i); setState(() {}); }, child: buildIcon(checked: _checkLi[i], icon: widget.icons[i]))); } return Wrap( alignment: WrapAlignment.center, runSpacing: 10, spacing: 25, children: li); } Widget buildIcon({bool checked = false, IconData? icon}) { Color defaultColor = Colors.black26; Color activeColor = Colors.blue; return Icon( icon, color: checked ? activeColor : defaultColor, size: 35, ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/selector/multi_chip_filter.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020-04-07 /// contact me by email 1981462002@qq.com /// 说明: typedef BoolWidgetBuilder = Widget Function(BuildContext context, bool selected); class MultiChipFilter extends StatefulWidget { final List data; final BoolWidgetBuilder labelBuilder; final IndexedWidgetBuilder? avatarBuilder; final Function(List) onChange; const MultiChipFilter({Key? key, required this.data,required this.labelBuilder,this.avatarBuilder,required this.onChange}) : super(key: key); @override _MultiChipFilterState createState() => _MultiChipFilterState(); } class _MultiChipFilterState extends State> { final List _selected = []; @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: widget.data.map((e) => _buildChild(context,widget.data.indexOf(e))).toList(), ); } Widget _buildChild(BuildContext context,int index) { bool selected = _selected.contains(index); return FilterChip( selectedColor: Colors.orange.withAlpha(55), labelPadding: const EdgeInsets.only(left: 5,right: 5), selectedShadowColor: Colors.blue, shadowColor: Colors.orangeAccent, pressElevation: 5, elevation: 3, avatar: widget.avatarBuilder==null?null:widget.avatarBuilder!(context,index), label: widget.labelBuilder(context,selected), selected: selected, onSelected: (bool value) { setState(() { if (value) { _selected.add(index); } else { _selected.removeWhere((i) => i == index); } widget.onChange(_selected); }); }, ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/sliver_header/sliver_pinned_header.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; class SliverPinnedHeader extends StatelessWidget { final PreferredSizeWidget child; final Color color; SliverPinnedHeader({required this.child, this.color = Colors.white}); @override Widget build(BuildContext context) { return SliverPersistentHeader( pinned: true, delegate: _SliverPinnedHeaderDelegate( child: child, color: color ), ); } } class _SliverPinnedHeaderDelegate extends SliverPersistentHeaderDelegate { final PreferredSizeWidget child; final Color color; _SliverPinnedHeaderDelegate({required this.child, required this.color}); @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return ColoredBox( color: color, child: child); } @override double get maxExtent => child.preferredSize.height; @override double get minExtent => child.preferredSize.height; @override bool shouldRebuild(covariant _SliverPinnedHeaderDelegate oldDelegate) { return oldDelegate.child != child ||oldDelegate.color != color; } } ================================================ FILE: modules/basic_system/toly_ui/lib/sliver_header/sliver_snap_header.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; class SliverSnapHeader extends StatefulWidget { final PreferredSizeWidget child; const SliverSnapHeader({ Key? key, required this.child, }) : super(key: key); @override State createState() => _SliverSnapHeaderState(); } class _SliverSnapHeaderState extends State with TickerProviderStateMixin { FloatingHeaderSnapConfiguration? _snapConfiguration; PersistentHeaderShowOnScreenConfiguration? _showOnScreenConfiguration; void _initSnapConfiguration() { _snapConfiguration = FloatingHeaderSnapConfiguration( curve: Curves.easeOut, duration: const Duration(milliseconds: 200), ); _showOnScreenConfiguration = const PersistentHeaderShowOnScreenConfiguration( minShowOnScreenExtent: double.infinity); } @override void initState() { super.initState(); _initSnapConfiguration(); } @override Widget build(BuildContext context) { return SliverPersistentHeader( floating: true, pinned: false, delegate: _SliverSnapHeaderDelegate( vsync: this, child: widget.child, snapConfiguration: _snapConfiguration, showOnScreenConfiguration: _showOnScreenConfiguration), ); } } class _SliverSnapHeaderDelegate extends SliverPersistentHeaderDelegate { final PreferredSizeWidget child; @override final TickerProvider vsync; _SliverSnapHeaderDelegate({ required this.child, required this.snapConfiguration, required this.vsync, required this.showOnScreenConfiguration, }); @override final FloatingHeaderSnapConfiguration? snapConfiguration; @override final PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration; @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { return child; } @override double get maxExtent => child.preferredSize.height; @override double get minExtent => child.preferredSize.height; @override bool shouldRebuild(covariant _SliverSnapHeaderDelegate oldDelegate) { return oldDelegate.child != child || vsync != oldDelegate.vsync || snapConfiguration != oldDelegate.snapConfiguration || showOnScreenConfiguration != oldDelegate.showOnScreenConfiguration; } } ================================================ FILE: modules/basic_system/toly_ui/lib/ti/circle.dart ================================================ import 'package:flutter/material.dart'; class Circle extends StatelessWidget { final Color color; final double radius; final bool showShadow; final Widget? child; const Circle({Key? key, this.color=Colors.blue, this.radius=6,this.showShadow=true,this.child}) : super(key: key); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, child: child ?? const SizedBox.shrink(), width: 2*radius, height: 2*radius, decoration: BoxDecoration( color: color, shape: BoxShape.circle, boxShadow: [ if (showShadow) const BoxShadow( color: Colors.grey, offset: Offset(.5,.5), blurRadius: .5, )] ), ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/ti/circle_image.dart ================================================ import 'package:flutter/material.dart'; import 'package:storage/storage.dart'; import 'package:widget_module/widget_module.dart'; class CircleImage extends StatelessWidget { const CircleImage({ Key? key, this.borderSize = 3, required this.image, this.size = 70, this.shadowColor, this.roundColor, }) : super(key: key); final ImageProvider image; //图片 final double size; //大小 final Color? shadowColor; //阴影颜色 final Color? roundColor; //边框颜色 final double borderSize; @override Widget build(BuildContext context) { return Container( width: size, height: size, decoration: BoxDecoration( shape: BoxShape.circle, color: roundColor ?? Colors.white, boxShadow: [ BoxShadow( color: shadowColor ?? Colors.grey.withOpacity(0.3), offset: const Offset(0.0, 0.0), blurRadius: 3.0, spreadRadius: 0.0, ), ], ), child: Padding( padding: EdgeInsets.all(borderSize), child: GestureDetector( onTap: () async{ var data = await AppStorage().flutter().queryWidgetByName('Container'); print(data); }, child: DecoratedBox( decoration: BoxDecoration( image: DecorationImage( image: image, fit: BoxFit.cover, filterQuality: FilterQuality.low), shape: BoxShape.circle, ), ), ), ), ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/ti/circle_text.dart ================================================ import 'package:flutter/material.dart'; class CircleText extends StatelessWidget { const CircleText({ Key? key, required this.text, this.size = 70, this.fontSize = 24, this.color = Colors.white, this.shadowColor, this.backgroundColor, this.roundColor, }) : super(key: key); final String text; //图片 final double size; //大小 final double fontSize; //大小 final Color? shadowColor; //阴影颜色 final Color color; //阴影颜色 final Color? roundColor; //边框颜色 final Color? backgroundColor; //边框颜色 @override Widget build(BuildContext context) { Widget headIcon = Container( width: size, height: size, decoration: BoxDecoration( shape: BoxShape.circle, //圆形装饰线 color: roundColor ?? Colors.white, boxShadow: [ BoxShadow( //阴影 color: shadowColor ?? Colors.grey.withOpacity(0.3), offset: const Offset(0.0, 0.0), blurRadius: 3.0, spreadRadius: 0.0, ), ], ), child: Padding( padding: const EdgeInsets.all(3), child: Container( alignment: Alignment.center, width: size, height: size, decoration: BoxDecoration( shape: BoxShape.circle, //圆形装饰线 color: backgroundColor ?? const Color(0xffD8F5FF), ), child: Text( text.length > 2 ? text.substring(0, 2) : text, style: TextStyle( fontSize: fontSize, color: color, fontWeight: FontWeight.bold, shadows: const [ Shadow( //阴影 color: Colors.grey, offset: Offset(1.0, 1.0), blurRadius: 1.0, ) ], ), )), ), ); return headIcon; } } ================================================ FILE: modules/basic_system/toly_ui/lib/ti/color_wrapper.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/9/3 /// contact me by email 1981462002@qq.com /// 说明: class WrapColor extends StatelessWidget { final Widget? child; final Color? color; final double radius; final EdgeInsetsGeometry padding; const WrapColor( {Key? key, this.child, this.color, this.radius = 5, this.padding = const EdgeInsets.only(left: 4, right: 4, top: 0, bottom: 0)}) : super(key: key); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, child: child, padding: padding, decoration: BoxDecoration( color: color??Theme.of(context).primaryColor, borderRadius: BorderRadius.all(Radius.circular(radius))), ); } } class Circled extends StatelessWidget { final Widget child; final Color color; final double radius; const Circled({Key? key, required this.child, this.color = Colors.blue, this.radius = 15}) : super(key: key); @override Widget build(BuildContext context) { return Container( width: radius * 2, height: radius * 2, child: child, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.all(Radius.circular(radius))), ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/ti/math_runner.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/10/24 /// contact me by email 1981462002@qq.com /// 说明: typedef FunNum1 = Function(double t); class MathRunner extends StatefulWidget { const MathRunner( {Key? key, this.child, required this.f, required this.g, this.reverse = true}) : super(key: key); final Widget? child; final FunNum1 f; final FunNum1 g; final bool reverse; @override _MathRunnerState createState() => _MathRunnerState(); } class _MathRunnerState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation animationX; double _x = -1.0; double _y = 0; @override void initState() { _controller = AnimationController( vsync: this, duration: const Duration(seconds: 3), )..repeat(reverse: widget.reverse); animationX = Tween(begin: -1.0, end: 1.0).animate(_controller) ..addListener(() { setState(() { _x = widget.f(animationX.value); _y = widget.g(animationX.value); }); }); super.initState(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Align( alignment: Alignment(_x, _y), child: widget.child, ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/ti/panel.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class Panel extends StatelessWidget { final double radius; final Color? color; final Widget? child; final EdgeInsetsGeometry? margin; final BoxConstraints? constraints; final AlignmentGeometry? alignment; const Panel( {Key? key, this.radius = 5.0, this.color, this.child, this.margin = const EdgeInsets.all(10), this.constraints, this.alignment}) : super(key: key); @override Widget build(BuildContext context) { return Container( alignment: alignment ?? Alignment.centerLeft, padding: margin, constraints: constraints, decoration: BoxDecoration( color: color ?? const Color(0xffF6F8FA), borderRadius: BorderRadius.all(Radius.circular(radius))), child: child, ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/ti/tag.dart ================================================ import 'package:flutter/material.dart'; class Tag extends StatelessWidget { final Size size; final double shadowHeight; final double tranRate; final Color color; const Tag({Key? key, this.size = const Size(100, 150),this.shadowHeight=9.0,this.tranRate=0.25,this.color=Colors.red}) : super(key: key); @override Widget build(BuildContext context) { return SizedBox( width: size.width, height: size.height, child: CustomPaint( painter: _TagPaint( color: color, shadowHeight: shadowHeight, tranRate: tranRate, ), ), ); } } class _TagPaint extends CustomPainter { Path path = Path(); Path shadowPath = Path(); final Paint _paint; final double tranRate; final double shadowHeight; final Color color; final rate = 0.5; _TagPaint({this.tranRate=0,required this.color ,required this.shadowHeight}) : _paint = Paint()..color = color; @override void paint(Canvas canvas, Size size) { canvas.clipRect(Offset.zero & size); shadowPath.reset(); path.reset(); path.moveTo(0, 0); path.relativeLineTo(size.width-shadowHeight*rate, 0); path.relativeLineTo(0, size.height); path.relativeLineTo(-(size.width-shadowHeight*rate) / 2, -tranRate * size.height); path.relativeLineTo(-(size.width-shadowHeight*rate) / 2, tranRate * size.height); path.close(); canvas.drawPath(path, _paint..color = color); shadowPath.moveTo(size.width-shadowHeight*rate, 0); shadowPath.relativeLineTo(0, shadowHeight); shadowPath.relativeLineTo(shadowHeight*rate, 0); shadowPath.close(); canvas.drawPath(shadowPath, _paint..color=color.withAlpha(88)); } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } } ================================================ FILE: modules/basic_system/toly_ui/lib/ti/text_typer.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; class TextTyper extends StatefulWidget { final String text; final Duration duration; final TextStyle textStyle; const TextTyper( {required this.text, Key? key, this.textStyle = const TextStyle(fontWeight: FontWeight.w600, fontSize: 20.0), this.duration = const Duration(milliseconds: 150)}) : super(key: key); @override _TextTyperState createState() => _TextTyperState(); } class _TextTyperState extends State { final ValueNotifier data = ValueNotifier(""); Timer? _timer; @override void initState() { super.initState(); _timer = Timer.periodic(widget.duration, _type); } int currentIndex = 0; String get currentText => widget.text.substring(0, currentIndex); void _type(Timer timer) { if (currentIndex < widget.text.length) { currentIndex++; data.value = currentText; } else { _timer?.cancel(); _timer = null; } } @override void dispose() { _timer?.cancel(); data.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: data, builder: _buildByAnim, ); } Widget _buildByAnim(BuildContext context, Widget? child) => Text( data.value, style: widget.textStyle, ); } ================================================ FILE: modules/basic_system/toly_ui/lib/ti/toly_switch_list_tile.dart ================================================ // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; // Examples can assume: // void setState(VoidCallback fn) { } // bool _isSelected = true; enum _SwitchListTileType { material, adaptive } /// A [ListTile] with a [Switch]. In other words, a switch with a label. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=0igIjvtEWNU} /// /// The entire list tile is interactive: tapping anywhere in the tile toggles /// the switch. Tapping and dragging the [Switch] also triggers the [onChanged] /// callback. /// /// To ensure that [onChanged] correctly triggers, the state passed /// into [value] must be properly managed. This is typically done by invoking /// [State.setState] in [onChanged] to toggle the state value. /// /// The [value], [onChanged], [activeColor], [activeThumbImage], and /// [inactiveThumbImage] properties of this widget are identical to the /// similarly-named properties on the [Switch] widget. /// /// The [title], [subtitle], [isThreeLine], and [dense] properties are like /// those of the same name on [ListTile]. /// /// The [selected] property on this widget is similar to the [ListTile.selected] /// property. This tile's [activeColor] is used for the selected item's text color, or /// the theme's [SwitchThemeData.overlayColor] if [activeColor] is null. /// /// This widget does not coordinate the [selected] state and the /// [value]; to have the list tile appear selected when the /// switch button is on, use the same value for both. /// /// The switch is shown on the right by default in left-to-right languages (i.e. /// in the [ListTile.trailing] slot) which can be changed using [controlAffinity]. /// The [secondary] widget is placed in the [ListTile.leading] slot. /// /// This widget requires a [Material] widget ancestor in the tree to paint /// itself on, which is typically provided by the app's [Scaffold]. /// The [tileColor], and [selectedTileColor] are not painted by the /// [TolySwitchListTile] itself but by the [Material] widget ancestor. In this /// case, one can wrap a [Material] widget around the [TolySwitchListTile], e.g.: /// /// {@tool snippet} /// ```dart /// Container( /// color: Colors.green, /// child: Material( /// child: SwitchListTile( /// tileColor: Colors.red, /// title: const Text('SwitchListTile with red background'), /// value: true, /// onChanged:(bool? value) { }, /// ), /// ), /// ) /// ``` /// {@end-tool} /// /// ## Performance considerations when wrapping [TolySwitchListTile] with [Material] /// /// Wrapping a large number of [TolySwitchListTile]s individually with [Material]s /// is expensive. Consider only wrapping the [TolySwitchListTile]s that require it /// or include a common [Material] ancestor where possible. /// /// To show the [TolySwitchListTile] as disabled, pass null as the [onChanged] /// callback. /// /// {@tool dartpad} /// ![SwitchListTile sample](https://flutter.github.io/assets-for-api-docs/assets/material/switch_list_tile.png) /// /// This widget shows a switch that, when toggled, changes the state of a [bool] /// member field called `_lights`. /// /// ** See code in examples/api/lib/material/switch_list_tile/switch_list_tile.0.dart ** /// {@end-tool} /// /// ## Semantics in SwitchListTile /// /// Since the entirety of the SwitchListTile is interactive, it should represent /// itself as a single interactive entity. /// /// To do so, a SwitchListTile widget wraps its children with a [MergeSemantics] /// widget. [MergeSemantics] will attempt to merge its descendant [Semantics] /// nodes into one node in the semantics tree. Therefore, SwitchListTile will /// throw an error if any of its children requires its own [Semantics] node. /// /// For example, you cannot nest a [RichText] widget as a descendant of /// SwitchListTile. [RichText] has an embedded gesture recognizer that /// requires its own [Semantics] node, which directly conflicts with /// SwitchListTile's desire to merge all its descendants' semantic nodes /// into one. Therefore, it may be necessary to create a custom radio tile /// widget to accommodate similar use cases. /// /// {@tool dartpad} /// ![Switch list tile semantics sample](https://flutter.github.io/assets-for-api-docs/assets/material/switch_list_tile_semantics.png) /// /// Here is an example of a custom labeled radio widget, called /// LinkedLabelRadio, that includes an interactive [RichText] widget that /// handles tap gestures. /// /// ** See code in examples/api/lib/material/switch_list_tile/switch_list_tile.1.dart ** /// {@end-tool} /// /// ## SwitchListTile isn't exactly what I want /// /// If the way SwitchListTile pads and positions its elements isn't quite what /// you're looking for, you can create custom labeled switch widgets by /// combining [Switch] with other widgets, such as [Text], [Padding] and /// [InkWell]. /// /// {@tool dartpad} /// ![Custom switch list tile sample](https://flutter.github.io/assets-for-api-docs/assets/material/switch_list_tile_custom.png) /// /// Here is an example of a custom LabeledSwitch widget, but you can easily /// make your own configurable widget. /// /// ** See code in examples/api/lib/material/switch_list_tile/switch_list_tile.2.dart ** /// {@end-tool} /// /// See also: /// /// * [ListTileTheme], which can be used to affect the style of list tiles, /// including switch list tiles. /// * [CheckboxListTile], a similar widget for checkboxes. /// * [RadioListTile], a similar widget for radio buttons. /// * [ListTile] and [Switch], the widgets from which this widget is made. class TolySwitchListTile extends StatelessWidget { /// Creates a combination of a list tile and a switch. /// /// The switch tile itself does not maintain any state. Instead, when the /// state of the switch changes, the widget calls the [onChanged] callback. /// Most widgets that use a switch will listen for the [onChanged] callback /// and rebuild the switch tile with a new [value] to update the visual /// appearance of the switch. /// /// The following arguments are required: /// /// * [value] determines whether this switch is on or off. /// * [onChanged] is called when the user toggles the switch on or off. const TolySwitchListTile({ super.key, required this.value, required this.onChanged, this.tileColor, this.activeColor, this.activeTrackColor, this.inactiveThumbColor, this.inactiveTrackColor, this.activeThumbImage, this.inactiveThumbImage, this.title, this.subtitle, this.isThreeLine = false, this.dense, this.contentPadding, this.secondary, this.selected = false, this.autofocus = false, this.controlAffinity = ListTileControlAffinity.platform, this.shape, this.selectedTileColor, this.visualDensity, this.focusNode, this.onFocusChange, this.enableFeedback, this.hoverColor, }); /// Whether this switch is checked. /// /// This property must not be null. final bool value; /// Called when the user toggles the switch on or off. /// /// The switch passes the new value to the callback but does not actually /// change state until the parent widget rebuilds the switch tile with the /// new value. /// /// If null, the switch will be displayed as disabled. /// /// The callback provided to [onChanged] should update the state of the parent /// [StatefulWidget] using the [State.setState] method, so that the parent /// gets rebuilt; for example: /// /// {@tool snippet} /// ```dart /// SwitchListTile( /// value: _isSelected, /// onChanged: (bool newValue) { /// setState(() { /// _isSelected = newValue; /// }); /// }, /// title: const Text('Selection'), /// ) /// ``` /// {@end-tool} final ValueChanged? onChanged; /// The color to use when this switch is on. /// /// Defaults to accent color of the current [Theme]. final Color? activeColor; /// The color to use on the track when this switch is on. /// /// Defaults to [ThemeData.toggleableActiveColor] with the opacity set at 50%. /// /// Ignored if created with [SwitchListTile.adaptive]. final Color? activeTrackColor; /// The color to use on the thumb when this switch is off. /// /// Defaults to the colors described in the Material design specification. /// /// Ignored if created with [SwitchListTile.adaptive]. final Color? inactiveThumbColor; /// The color to use on the track when this switch is off. /// /// Defaults to the colors described in the Material design specification. /// /// Ignored if created with [SwitchListTile.adaptive]. final Color? inactiveTrackColor; /// {@macro flutter.material.ListTile.tileColor} final Color? tileColor; /// An image to use on the thumb of this switch when the switch is on. final ImageProvider? activeThumbImage; /// An image to use on the thumb of this switch when the switch is off. /// /// Ignored if created with [SwitchListTile.adaptive]. final ImageProvider? inactiveThumbImage; /// The primary content of the list tile. /// /// Typically a [Text] widget. final Widget? title; /// Additional content displayed below the title. /// /// Typically a [Text] widget. final Widget? subtitle; /// A widget to display on the opposite side of the tile from the switch. /// /// Typically an [Icon] widget. final Widget? secondary; /// Whether this list tile is intended to display three lines of text. /// /// If false, the list tile is treated as having one line if the subtitle is /// null and treated as having two lines if the subtitle is non-null. final bool isThreeLine; /// Whether this list tile is part of a vertically dense list. /// /// If this property is null then its value is based on [ListTileThemeData.dense]. final bool? dense; /// The tile's internal padding. /// /// Insets a [TolySwitchListTile]'s contents: its [title], [subtitle], /// [secondary], and [Switch] widgets. /// /// If null, [ListTile]'s default of `EdgeInsets.symmetric(horizontal: 16.0)` /// is used. final EdgeInsetsGeometry? contentPadding; /// Whether to render icons and text in the [activeColor]. /// /// No effort is made to automatically coordinate the [selected] state and the /// [value] state. To have the list tile appear selected when the switch is /// on, pass the same value to both. /// /// Normally, this property is left to its default value, false. final bool selected; /// {@macro flutter.widgets.Focus.autofocus} final bool autofocus; /// Defines the position of control and [secondary], relative to text. /// /// By default, the value of [controlAffinity] is [ListTileControlAffinity.platform]. final ListTileControlAffinity controlAffinity; /// {@macro flutter.material.ListTile.shape} final ShapeBorder? shape; /// If non-null, defines the background color when [TolySwitchListTile.selected] is true. final Color? selectedTileColor; /// Defines how compact the list tile's layout will be. /// /// {@macro flutter.material.themedata.visualDensity} final VisualDensity? visualDensity; /// {@macro flutter.widgets.Focus.focusNode} final FocusNode? focusNode; /// {@macro flutter.material.inkwell.onFocusChange} final ValueChanged? onFocusChange; /// {@macro flutter.material.ListTile.enableFeedback} /// /// See also: /// /// * [Feedback] for providing platform-specific feedback to certain actions. final bool? enableFeedback; /// The color for the tile's [Material] when a pointer is hovering over it. final Color? hoverColor; @override Widget build(BuildContext context) { final Widget control = Transform.scale( scale: 0.8, child: CupertinoSwitch( value: value, activeColor: activeColor, // thumbColor: activet, trackColor: activeTrackColor, onChanged:onChanged )); Widget? leading, trailing; switch (controlAffinity) { case ListTileControlAffinity.leading: leading = control; trailing = secondary; break; case ListTileControlAffinity.trailing: case ListTileControlAffinity.platform: leading = secondary; trailing = control; break; } final ThemeData theme = Theme.of(context); final SwitchThemeData switchTheme = SwitchTheme.of(context); final Set states = { if (selected) MaterialState.selected, }; final Color effectiveActiveColor = activeColor ?? switchTheme.thumbColor?.resolve(states) ?? theme.colorScheme.secondary; return MergeSemantics( child: ListTile( selectedColor: effectiveActiveColor, leading: leading, title: title, subtitle: subtitle, trailing: trailing, isThreeLine: isThreeLine, dense: dense, contentPadding: contentPadding, enabled: onChanged != null, onTap: onChanged != null ? () { onChanged!(!value); } : null, selected: selected, selectedTileColor: selectedTileColor, autofocus: autofocus, shape: shape, tileColor: tileColor, visualDensity: visualDensity, focusNode: focusNode, onFocusChange: onFocusChange, enableFeedback: enableFeedback, hoverColor: hoverColor, ), ); } } ================================================ FILE: modules/basic_system/toly_ui/lib/toly_ui.dart ================================================ export 'code/code.dart'; export 'ti/circle_image.dart'; export 'input/edit_panel.dart'; export 'input/icon_input.dart'; export 'input/input_button.dart'; export 'ti/circle_text.dart'; export 'ti/tag.dart'; export 'ti/text_typer.dart'; export 'ti/circle.dart'; export 'ti/color_wrapper.dart'; export 'ti/math_runner.dart'; export 'ti/panel.dart'; export 'button/feedback_widget.dart'; export 'popable/drop_selectable_widget.dart'; export 'selector/color_chooser.dart'; export 'selector/multi_chip_filter.dart'; export 'selector/burst_menu.dart'; export 'decorations/round_rect_rab_indicator.dart'; export 'dialog/alert_conform_dialog.dart'; export 'dialog/delete_message_panel.dart'; export 'object/windmill.dart'; export 'sliver_header/sliver_pinned_header.dart'; export 'sliver_header/sliver_snap_header.dart'; export 'ti/toly_switch_list_tile.dart'; export 'markdown/markdown_widget.dart' hide Highlighter; export 'adapter/platform_view_adapter.dart'; ================================================ FILE: modules/basic_system/toly_ui/pubspec.yaml ================================================ name: toly_ui description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/basic_system/toly_ui/test/toly_ui_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:toly_ui/toly_ui.dart'; void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); } ================================================ FILE: modules/basic_system/unit_env/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies build/ ================================================ FILE: modules/basic_system/unit_env/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "d8a9f9a52e5af486f80d932e838ee93861ffd863" channel: "stable" project_type: package ================================================ FILE: modules/basic_system/unit_env/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/basic_system/unit_env/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/basic_system/unit_env/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/basic_system/unit_env/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/basic_system/unit_env/lib/src/host.dart ================================================ import 'package:fx_dio/fx_dio.dart'; class UnitEnv { static String? userName; } class Unit3Host extends Host { const Unit3Host(); @override Map get value => { HostEnv.release: 'toly1994.com', HostEnv.dev: '192.168.1.61', }; @override HostConfig get config => const HostConfig( scheme: 'http', port: 3000, apiNest: '/api/v1', ); @override HostEnv get env => HostEnv.release; } mixin ScienceHostMixin { Host get host => FxDio()(); } ================================================ FILE: modules/basic_system/unit_env/lib/unit_env.dart ================================================ export 'src/host.dart'; export 'package:fx_dio/fx_dio.dart'; ================================================ FILE: modules/basic_system/unit_env/pubspec.yaml ================================================ name: unit_env description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ^3.6.0 flutter: ">=1.17.0" dependencies: flutter: sdk: flutter fx_dio: path: /Volumes/Toly1T/Project/toly1994/Flutter/fx/modules/fx_dio dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 dependency_overrides: fx_dio: path: /Volumes/Toly1T/Project/toly1994/Flutter/fx/modules/fx_dio # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/to/asset-from-package # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/to/font-from-package ================================================ FILE: modules/basic_system/unit_env/test/unit_env_test.dart ================================================ ================================================ FILE: modules/basic_system/utils/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .packages build/ ================================================ FILE: modules/basic_system/utils/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 channel: stable project_type: package ================================================ FILE: modules/basic_system/utils/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/basic_system/utils/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/basic_system/utils/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/basic_system/utils/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/basic_system/utils/lib/src/color_utils.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'random_provider.dart'; class ColorUtils { static Color randomColor({ int limitA = 120, int limitR = 0, int limitG = 0, int limitB = 0, }) { Random random = RandomProvider.random; int a = limitA + random.nextInt(256 - limitA); //透明度值 int r = limitR + random.nextInt(256 - limitR); //红值 int g = limitG + random.nextInt(256 - limitG); //绿值 int b = limitB + random.nextInt(256 - limitB); //蓝值 return Color.fromARGB(a, r, g, b); //生成argb模式的颜色 } /// 使用方法: /// var color1=ColorUtils.parse("#33428A43"); /// var color2=ColorUtils.parse("33428A43"); /// var color3=ColorUtils.parse("#428A43"); ///var color4=ColorUtils.parse("428A43"); /// static Color parse(String code) { Color result =Colors.red; int value = 0 ; if (code.contains("#")) { try { value = int.parse(code.substring(1), radix: 16); } catch (e) { print(e); } switch (code.length) { case 7://6位 result = Color(value + 0xFF000000); break; case 9://8位 result = Color(value); break; default: result =Colors.red; } }else { try { value = int.parse(code, radix: 16); } catch (e) { print(e); } switch (code.length) { case 6: result = Color(value + 0xFF000000); break; case 8: result = Color(value); break; default: result =Colors.red; } } return result; } static String colorString(Color color) => "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; } ================================================ FILE: modules/basic_system/utils/lib/src/convert_man.dart ================================================ /// create by 张风捷特烈 on 2020/6/17 /// contact me by email 1981462002@qq.com /// 说明: const double _kMillisLimit = 1000.0; const double _kSecondsLimit = 60 * _kMillisLimit; const double _kMinutesLimit = 60 * _kSecondsLimit; const double _kHourLimit = 24 * _kMinutesLimit; const double _kDaysLimit = 30 * _kHourLimit; class ConvertMan { ///日期格式转换 static String time2string(DateTime date, {bool just = false}) { if (just) { return _getDateStr(date); } int subTimes = DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch; if (subTimes < _kMillisLimit) { return "刚刚"; } else if (subTimes < _kSecondsLimit) { return (subTimes / _kMillisLimit).round().toString() + " 秒前"; } else if (subTimes < _kMinutesLimit) { return (subTimes / _kSecondsLimit).round().toString() + "分钟前"; } else if (subTimes < _kHourLimit) { return (subTimes / _kMinutesLimit).round().toString() + "小时前"; } else if (subTimes < _kDaysLimit) { return (subTimes / _kHourLimit).round().toString() + "天前"; } else { return _getDateStr(date); } } static String _getDateStr(DateTime date) { if (date.toString().length < 10) { return date.toString(); } return date.toString().substring(0, 10); } } ================================================ FILE: modules/basic_system/utils/lib/src/http_utils/http_util.dart ================================================ import 'package:dio/dio.dart'; import 'token_interceptor.dart'; const Duration _kReceiveTimeout = Duration(milliseconds: 5000); const Duration _kSendTimeout = Duration(milliseconds: 5000); const Duration _kConnectTimeout = Duration(milliseconds: 5000); class HttpUtil { TokenInterceptors? tokenInterceptors; static final HttpUtil _instance = HttpUtil._internal(); Dio? _dio; static HttpUtil get instance => _instance; ///通用全局单例,第一次使用时初始化 HttpUtil._internal() { _dio ??= Dio( BaseOptions( connectTimeout: _kConnectTimeout, sendTimeout: _kSendTimeout, receiveTimeout: _kReceiveTimeout), ); // _dio!.interceptors.add(LogsInterceptors()); // _dio.interceptors.add(ResponseInterceptors()); } Dio get client => _dio!; void setToken(String token) { print('---token---$token-------'); tokenInterceptors = TokenInterceptors(token: token); _dio!.interceptors.add(tokenInterceptors!); } void deleteToken() { _dio!.interceptors.remove(tokenInterceptors); } void rebase(String baseIp) { _dio!.options.baseUrl = baseIp; } } ================================================ FILE: modules/basic_system/utils/lib/src/http_utils/http_utils.dart ================================================ export 'http_util.dart'; export 'task_result.dart'; ================================================ FILE: modules/basic_system/utils/lib/src/http_utils/logs_interceptor.dart ================================================ import 'package:dio/dio.dart'; class LogsInterceptors extends InterceptorsWrapper { @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { // print("==========请求baseUrl:${options.baseUrl} =========="); // print("==========请求url:${options.path} =========="); // print('==========请求头: ' + options.headers.toString()+"=========="); // if (options.data != null) { // print('==========请求参数: ' + options.data.toString()+"=========="); // } return handler.next(options); } @override void onError(DioError err, ErrorInterceptorHandler handler) { print('==========请求异常: ' + err.toString()+"=========="); if(err.response!=null){ print('==========请求异常信息: ' + err.response.toString()+"=========="); } return handler.next(err); } @override void onResponse(Response response, ResponseInterceptorHandler handler) { return handler.next(response); // continue } } ================================================ FILE: modules/basic_system/utils/lib/src/http_utils/response_interceptor.dart ================================================ // import 'package:dio/dio.dart'; // import 'task_result.dart'; // // // 拦截返回数据,进行统一处理 // class ResponseInterceptors extends InterceptorsWrapper { // @override // onResponse(Response response) async { // RequestOptions option = response.request; // // if (option.contentType != null && option.contentType.contains("text")) { // return ResultBean(data: response.data, status: true, msg: ''); // } // // ///一般只需要处理200的情况,300、400、500保留错误信息,外层为http协议定义的响应码 // if (response.statusCode == 200 || response.statusCode == 201) { // ///内层需要根据公司实际返回结构解析,一般会有code,data,msg字段 // bool status = response.data["status"]; // String msg = response.data["msg"]; // var result = response.data; // return ResultBean( // data: result, // status: status, // msg: msg, // ); // } // } // } ================================================ FILE: modules/basic_system/utils/lib/src/http_utils/task_result.dart ================================================ class TaskResult { final T? data; final bool success; final String msg; final int count; TaskResult({this.data, this.success=false, this.msg='',this.count=0}); @override String toString() { return 'RepResult{data: $data, status: $success, msg:$msg}'; } const TaskResult.error({required this.msg}) : success = false, data = null, count = 0 ; const TaskResult.success({ this.data, this.msg = '', this.count = 0, }) : success = true; } ================================================ FILE: modules/basic_system/utils/lib/src/http_utils/token_interceptor.dart ================================================ import 'package:dio/dio.dart'; import 'package:jwt_decoder/jwt_decoder.dart'; const String _kTokenKey = 'Authorization'; const String _kTokenPrefix = 'Bearer '; class TokenInterceptors extends InterceptorsWrapper { String token; TokenInterceptors({this.token = ''}); void Function()? onTokenDisabled; @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { if (token != '') { bool disable = JwtDecoder.isExpired(token); if (disable) { onTokenDisabled?.call(); } } if (token.isNotEmpty) { options.headers[_kTokenKey] = '$_kTokenPrefix$token'; } return handler.next(options); } } ================================================ FILE: modules/basic_system/utils/lib/src/random_provider.dart ================================================ import 'dart:math'; class RandomProvider{ RandomProvider._();//私有化构造 static final _random= Random(); static Random get random =>_random; } ================================================ FILE: modules/basic_system/utils/lib/src/toast.dart ================================================ import 'package:flutter/material.dart'; class Toast { static toast(BuildContext context, String msg, {duration = const Duration(milliseconds: 600), Color? color, SnackBarAction? action}) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(msg), duration: duration, action: action, backgroundColor: color??Theme.of(context).primaryColor, )); } static void error(BuildContext context,String msg){ toast(context,msg, color:Colors.red, ); } static void warning(BuildContext context,String msg){ toast(context,msg, color:Colors.orange, ); } static void success(BuildContext context,String msg){ toast(context,msg, color:Theme.of(context).primaryColor, ); } static void green(BuildContext context,String msg){ toast(context,msg, color:Colors.green, ); } } ================================================ FILE: modules/basic_system/utils/lib/utils.dart ================================================ library utils; export 'src/color_utils.dart'; export 'src/http_utils/http_utils.dart'; export 'src/toast.dart'; export 'src/convert_man.dart'; ================================================ FILE: modules/basic_system/utils/pubspec.yaml ================================================ name: utils description: utils version: 0.0.1 homepage: environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/basic_system/utils/test/utils_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; void main() { } ================================================ FILE: modules/knowledge_system/algorithm/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .packages build/ ================================================ FILE: modules/knowledge_system/algorithm/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 channel: stable project_type: package ================================================ FILE: modules/knowledge_system/algorithm/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/knowledge_system/algorithm/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/knowledge_system/algorithm/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/knowledge_system/algorithm/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/knowledge_system/algorithm/lib/algorithm.dart ================================================ library algorithm; export 'src/sort/sort_parper.dart'; export 'src/sort/top_bar/sort_bar.dart'; export 'src/sort/top_bar/sort_button.dart'; export 'src/sort/sort_setting.dart'; export 'src/sort/sort_page.dart'; export 'src/data_scope/sort_config.dart'; export 'src/data_scope/state.dart'; export 'src/views/algo_page.dart'; ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/finding/functions/AStar.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-07-07 // Contact Me: 1981462002@qq.com import '../../../finding/data_scope/finding_state.dart'; import '../functions.dart'; /// AStar算法 Future findingAStar(FindingState state) async { XY start = state.start; XY end = state.end; // 创建优先队列作为开放列表 PriorityQueue openList = PriorityQueue(); openList.add(Node(start, 0, heuristic(start, end))); var (row, col) = state.config.size; // 初始化路径记录和访问标记 state.visitedList = List.generate(col, (_) => List.filled(row, false)); state.pathList = List.generate(col, (_) => List.filled(row, false)); while (!openList.isEmpty) { // 从开放列表中移除f值最小的节点作为当前节点 Node current = openList.removeFirst(); int x = current.p.$1; int y = current.p.$2; // 检查当前节点是否超出地图范围 if (!state.isInMap(x, y)) { throw "坐标越界"; } // 如果当前节点已经访问过,则继续下一个节点 if (state.visitedList[x][y]) continue; state.visitedList[x][y] = true; state.tick(); // 检查是否到达终点 if (x == state.end.$1 && y == state.end.$2) { print("找到路径了"); // 回溯路径 List> path = []; Node? curNode = current; while (curNode != null) { path.add([curNode.p.$1, curNode.p.$2]); curNode = curNode.parent; } // 将路径标记到pathList中,并显示路径动画 state.pathList = List.generate(col, (_) => List.filled(row, false)); for (var point in path) { state.setPath(point[0], point[1], true); await Future.delayed(const Duration(milliseconds: 50)); } return true; } // 遍历当前节点的四个方向 for (int i = 0; i < 4; i++) { int newX = x + state.direction[i][0]; int newY = y + state.direction[i][1]; // 如果新节点在地图范围内且是可通行的空地且未被访问过 if (state.isInMap(newX, newY) && state.blockList[newX][newY] == state.road && !state.visitedList[newX][newY]) { int g = current.g + 1; // 更新新节点的g值 double h = heuristic((newX, newY), end); // 计算新节点的启发值h openList.add(Node((newX, newY), g, h, current)); // 将新节点加入开放列表 state.tick(); await Future.delayed(const Duration(milliseconds: 50)); // 延迟显示效果 } } } return false; // 开放列表为空,未找到路径,搜索失败 } // 估价函数 double heuristic(XY start, XY end) { return ((start.$1 - end.$1).abs() + (start.$2 - end.$2).abs()).toDouble(); } class Node { XY p; // 坐标 int g; // 节点的坐标和当前路径长度g值 double h; // 启发值h Node? parent; // 父节点 Node(this.p, this.g, this.h, [this.parent]); double get f => g + h; // 计算节点的总代价f值 } class PriorityQueue { final List _queue = []; // 优先队列使用的列表 bool get isEmpty => _queue.isEmpty; // 判断队列是否为空 void add(Node node) { _queue.add(node); // 添加节点到队列 _queue.sort((a, b) => a.f.compareTo(b.f)); // 根据f值排序,f值小的在前面 } Node removeFirst() { return _queue.removeAt(0); // 移除并返回队列中f值最小的节点 } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/finding/functions/BFS.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-07-07 // Contact Me: 1981462002@qq.com import 'dart:collection'; import '../../../finding/data_scope/finding_state.dart'; import '../functions.dart'; /// 广度优先搜索(BFS)寻找路径 Future findingBFS(FindingState state) async { XY start = state.start; XY end = state.end; // 创建队列并添加起始位置 Queue> queue = Queue(); queue.add([start.$1, start.$2]); var (row,col) = state.config.size; // 初始化访问标记、路径记录和父节点记录 state.visitedList = List.generate(col, (_) => List.filled(row, false)); state.pathList = List.generate(col, (_) => List.filled(row, false)); List>> parent = List.generate(col, (_) => List.generate(row, (_) => [-1, -1])); // 开始广度优先搜索 while (queue.isNotEmpty) { List current = queue.removeFirst(); int x = current[0]; int y = current[1]; // 检查当前位置是否越界 if (!state.isInMap(x, y)) { throw "坐标越界"; } // 如果已经访问过当前位置,则继续下一个循环 if (state.visitedList[x][y]) continue; state.visitedList[x][y] = true; // 标记当前位置为已访问 state.tick(); // 检查是否到达终点 if (x == state.end.$1 && y == state.end.$2) { print("找到路径了"); // 回溯路径并记录到 path 列表中 List> path = []; int curX = x; int curY = y; while (curX != -1 && curY != -1) { path.add([curX, curY]); List prev = parent[curX][curY]; curX = prev[0]; curY = prev[1]; } // 清空原路径记录,并设置新的路径记录为找到的路径 state.pathList = List.generate(col, (_) => List.filled(row, false)); for (var point in path) { state.setPath(point[0], point[1], true); // 设置路径标记 await Future.delayed(const Duration(milliseconds: 50)); } return true; // 返回找到路径的结果 } // 遍历当前位置的四个方向 for (int i = 0; i < 4; i++) { int newX = x + state.direction[i][0]; int newY = y + state.direction[i][1]; // 检查新位置是否在地图范围内,并且是可通过的道路,并且未访问过 if (state.isInMap(newX, newY) && state.blockList[newX][newY] == state.road && !state.visitedList[newX][newY]) { queue.add([newX, newY]); // 将新位置添加到队列中 parent[newX][newY] = [x, y]; // 记录新位置的父节点 state.tick(); await Future.delayed(const Duration(milliseconds: 50)); // 等待一段时间,以便显示搜索过程(如果在 Flutter 中) } } } return false; // 如果队列为空仍未找到路径,则返回 false } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/finding/functions/BestFS.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-07-07 // Contact Me: 1981462002@qq.com import 'dart:math'; import '../../../finding/data_scope/finding_state.dart'; import '../functions.dart'; import 'AStar.dart'; /// BestFS算法 Future findingBestFS(FindingState state) async { XY start = state.start; XY end = state.end; // 初始化优先队列、起始节点和父节点数组 PriorityQueue priorityQueue = PriorityQueue(); var (row,col) = state.config.size; Node startNode = Node(start, 0, euclideanDistance(start.$1, start.$2, end.$1, end.$2)); priorityQueue.add(startNode); // 初始化 visitedList、pathList 和 parent // 初始化路径记录和访问标记 state.visitedList = List.generate(col, (_) => List.filled(row, false)); state.pathList = List.generate(col, (_) => List.filled(row, false)); List> parent = List.generate(col, (_) => List.filled(row, -1)); while (!priorityQueue.isEmpty) { Node currentNode = priorityQueue.removeFirst(); int x = currentNode.p.$1; int y = currentNode.p.$2; // 如果当前节点已访问过,则跳过 if (state.visitedList[x][y]) continue; state.visitedList[x][y] = true; state.tick(); // 检查是否到达终点 if (x == state.end.$1 && y == state.end.$2) { print("找到路径了"); // 回溯路径 List> path = []; int curX = x; int curY = y; while (curX != -1 && curY != -1) { path.add([curX, curY]); int prevX = parent[curX][curY] ~/ row; int prevY = parent[curX][curY] % row; curX = prevX; curY = prevY; print("curX:$curX,curY:$curY"); // 防止出现死循环,检查是否回溯到起点 if (curX == start.$1 && curY == start.$2) { break; } } // path = path.reversed.toList(); // 绘制路径到界面上 state.pathList = List.generate(col, (_) => List.filled(row, false)); for (var point in path) { state.setPath(point[0], point[1], true); await Future.delayed(const Duration(milliseconds: 50)); } return true; // 找到路径并绘制完成,返回true } // 探索当前节点的邻居节点 for (int i = 0; i < 4; i++) { int newX = x + state.direction[i][0]; int newY = y + state.direction[i][1]; // 如果邻居节点在地图范围内且是可通行的道路且未访问过 if (state.isInMap(newX, newY) && state.blockList[newX][newY] == state.road && !state.visitedList[newX][newY]) { Node newNode = Node((newX, newY), currentNode.g + 1, euclideanDistance(newX, newY, state.end.$1, state.end.$2)); priorityQueue.add(newNode); parent[newX][newY] = x * row + y; // 记录父节点 state.tick(); await Future.delayed(const Duration(milliseconds: 50)); } } } return false; // 未找到路径,返回false } double euclideanDistance(int x, int y, int endX, int endY) { return sqrt(pow((x - endX).toDouble(), 2) + pow((y - endY).toDouble(), 2)); } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/finding/functions/DFS.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-07-07 // Contact Me: 1981462002@qq.com import '../../../finding/data_scope/finding_state.dart'; import '../functions.dart'; /// 深度优先搜索(DFS)寻找路径 Future findingDFS(FindingState state) async { XY start = state.start; XY end = state.end; var (row,col) = state.config.size; // 初始化访问标记、路径记录和父节点记录 state.visitedList = List.generate(col, (_) => List.filled(row, false)); state.pathList = List.generate(col, (_) => List.filled(row, false)); List>> parent = List.generate(col, (_) => List.generate(row, (_) => [-1, -1])); // 定义DFS递归函数 Future dfs(int x, int y) async { // 检查当前位置是否越界 if (!state.isInMap(x, y)) { throw "坐标越界"; } // 如果已经访问过当前位置,则返回 false if (state.visitedList[x][y]) return false; state.visitedList[x][y] = true; // 标记当前位置为已访问 state.tick(); // 检查是否到达终点 if (x == state.end.$1 && y == state.end.$2) { print("找到路径了"); // 回溯路径并记录到 path 列表中 List> path = []; int curX = x; int curY = y; while (curX != -1 && curY != -1) { path.add([curX, curY]); List prev = parent[curX][curY]; curX = prev[0]; curY = prev[1]; } // 清空原路径记录,并设置新的路径记录为找到的路径 state.pathList = List.generate(col, (_) => List.filled(row, false)); for (var point in path) { state.setPath(point[0], point[1], true); // 设置路径标记 await Future.delayed(const Duration(milliseconds: 50)); } return true; // 返回找到路径的结果 } // 遍历当前位置的四个方向 for (int i = 0; i < 4; i++) { int newX = x + state.direction[i][0]; int newY = y + state.direction[i][1]; // 检查新位置是否在地图范围内,并且是可通过的道路,并且未访问过 if (state.isInMap(newX, newY) && state.blockList[newX][newY] == state.road && !state.visitedList[newX][newY]) { parent[newX][newY] = [x, y]; // 记录新位置的父节点 state.tick(); // 更新界面状态(如果在 Flutter 中) await Future.delayed(const Duration(milliseconds: 50)); if (await dfs(newX, newY)) { return true; // 如果找到路径,则返回 true } } } return false; // 如果当前位置无法继续搜索,则返回 false } // 调用DFS函数并返回最终结果 return await dfs(start.$1, start.$2); } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/finding/functions/dijkstra.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-07-07 // Contact Me: 1981462002@qq.com import '../../../finding/data_scope/finding_state.dart'; import '../functions.dart'; import 'AStar.dart'; /// Dijkstra算法 Future findingDijkstra(FindingState state) async { XY start = state.start; XY end = state.end; var (row, col) = state.config.size; // 初始化优先队列、距离数组和父节点数组 PriorityQueue priorityQueue = PriorityQueue(); List> distance = List.generate(col, (_) => List.filled(row, -1)); List> parent = List.generate(col, (_) => List.filled(row, -1)); state.visitedList = List.generate(col, (_) => List.filled(row, false)); state.pathList = List.generate(col, (_) => List.filled(row, false)); // 将起始节点加入优先队列并初始化距离 priorityQueue.add(Node(start, 0, 0.0)); distance[start.$1][start.$2] = 0; while (!priorityQueue.isEmpty) { // 从优先队列中取出当前节点 Node currentNode = priorityQueue.removeFirst(); int x = currentNode.p.$1; int y = currentNode.p.$2; int currentDistance = currentNode.g; // 如果当前节点已访问过,则跳过 if (state.visitedList[x][y]) continue; state.visitedList[x][y] = true; state.tick(); // 检查是否到达终点 if (x == state.end.$1 && y == state.end.$2) { print("找到路径了"); // 回溯路径 List> path = []; int curX = x; int curY = y; while (curX != -1 && curY != -1) { path.add([curX, curY]); int prevX = parent[curX][curY] ~/ row; int prevY = parent[curX][curY] % row; curX = prevX; curY = prevY; print("curX:$curX,curY:$curY"); // 防止出现死循环,检查是否回溯到起点 if (curX == start.$1 && curY == start.$2) { break; } } // path = path.reversed.toList(); // 绘制路径到界面上 state.pathList = List.generate(col, (_) => List.filled(row, false)); for (var point in path) { state.setPath(point[0], point[1], true); await Future.delayed(const Duration(milliseconds: 50)); } return true; // 找到路径并绘制完成,返回true } // 探索当前节点的邻居节点 for (int i = 0; i < 4; i++) { int newX = x + state.direction[i][0]; int newY = y + state.direction[i][1]; // 如果邻居节点在地图范围内且是可通行的道路且未访问过 if (state.isInMap(newX, newY) && state.blockList[newX][newY] == state.road && !state.visitedList[newX][newY]) { int newDistance = currentDistance + 1; // 更新距离 // 如果是第一次访问或找到更短的路径,则更新距离和父节点,并加入优先队列 if (distance[newX][newY] == -1 || newDistance < distance[newX][newY]) { distance[newX][newY] = newDistance; Node newNode = Node((newX, newY), newDistance, 0.0); // Dijkstra没有启发式函数,h设为0 priorityQueue.add(newNode); parent[newX][newY] = x * row + y; // 记录父节点 state.tick(); await Future.delayed(const Duration(milliseconds: 50)); } } } } return false; // 未找到路径,返回false } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/finding/functions.dart ================================================ import '../../finding/data_scope/finding_state.dart'; import 'functions/AStar.dart'; import 'functions/BestFS.dart'; import 'functions/BFS.dart'; import 'functions/DFS.dart'; import 'functions/dijkstra.dart'; typedef XY = (int,int); typedef FindFunction = Future Function(FindingState state); enum FindingAlgo { bfs('BFS'), dfs('DFS'), aStar('AStar'), bestFS('BestFS'), dijkstra('dijkstra'), ; final String path; const FindingAlgo(this.path); FindFunction get function{ return switch(this){ FindingAlgo.bfs => findingBFS, FindingAlgo.dfs => findingDFS, FindingAlgo.aStar => findingAStar, FindingAlgo.bestFS => findingBestFS, FindingAlgo.dijkstra => findingDijkstra, }; } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/bubble.dart ================================================ import '../functions.dart'; ///冒泡排序 Future bubbleSort(List src, SortCallback callback ) async{ //控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。 for (int i = 0; i < src.length; ++i) { //遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。 for (int j = 0; j < src.length - i - 1; ++j) { //如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。 if (src[j] > src[j + 1]) { int temp = src[j]; src[j] = src[j + 1]; src[j + 1] = temp; } //实现一个延迟,以便在ui上展示排序的动画效果 await callback(src); } } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/cocktail.dart ================================================ import '../functions.dart'; ///鸡尾酒排序(双向冒泡排序) Future cocktailSort(List src, SortCallback callback ) async { bool swapped = true; // 表示是否进行了交换 int start = 0; // 当前未排序部分的起始位置 int end = src.length; // 当前未排序部分的结束位置 // 开始排序循环,只有当没有进行交换时才会退出循环 while (swapped == true) { swapped = false; // 从左往右遍历需要排序的部分 for (int i = start; i < end - 1; ++i) { // 对每两个相邻元素进行比较 if (src[i] > src[i + 1]) { // 如果前面的元素大于后面的元素,则交换它们的位置 int temp = src[i]; src[i] = src[i + 1]; src[i + 1] = temp; swapped = true; // 进行了交换 } // 实现动画效果,延迟一段时间后更新数组状态 await callback(src); } // 如果没有进行交换,则说明已经排好序,退出循环 if (swapped == false) break; // 重设为false,准备进行下一轮排序 swapped = false; // 将end设置为上一轮排序的最后一个元素的位置 end = end - 1; // 从右往左遍历需要排序的部分 for (int i = end - 1; i >= start; i--) { // 对每两个相邻元素进行比较 if (src[i] > src[i + 1]) { // 如果前面的元素大于后面的元素,则交换它们的位置 int temp = src[i]; src[i] = src[i + 1]; src[i + 1] = temp; swapped = true; // 进行了交换 } // 实现动画效果,延迟一段时间后更新数组状态 await callback(src); } // 将start向右移一位,准备下一轮排序 start = start + 1; } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/comb.dart ================================================ import '../functions.dart'; ///梳排序(Comb Sort) Future combSort(List src, SortCallback callback) async{ int gap = src.length; bool swapped = true; // 当间隔不为1或存在交换时执行循环 while (gap != 1 || swapped == true) { // 通过缩小间隔来逐步将元素归位 gap = getNextGap(gap); swapped = false; for (int i = 0; i < src.length - gap; i++) { // 如果当前元素大于间隔位置上的元素,则交换它们的位置 if (src[i] > src[i + gap]) { int temp = src[i]; src[i] = src[i + gap]; src[i + gap] = temp; swapped = true; } // 实现一个延迟,以便在 UI 上展示排序的动画效果。 await callback(src); } } } int getNextGap(int gap) { // 根据当前间隔值计算下一个间隔值 gap = (gap * 10) ~/ 13; if (gap < 1) return 1; return gap; } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/cycle.dart ================================================ import '../functions.dart'; ///循环排序 Future cycleSort(List src, SortCallback callback) async { int writes = 0; for (int cycleStart = 0; cycleStart <= src.length - 2; cycleStart++) { int item = src[cycleStart]; int pos = cycleStart; // 在未排序部分中寻找比当前元素小的元素个数 for (int i = cycleStart + 1; i < src.length; i++) { if (src[i] < item) pos++; } // 如果当前元素已经在正确位置上,则跳过此次迭代 if (pos == cycleStart) { continue; } // 将当前元素放置到正确的位置上,并记录写操作次数 while (item == src[pos]) { pos += 1; } if (pos != cycleStart) { int temp = item; item = src[pos]; src[pos] = temp; writes++; } // 循环将位于当前位置的元素放置到正确的位置上 while (pos != cycleStart) { pos = cycleStart; // 继续在未排序部分中寻找比当前元素小的元素个数 for (int i = cycleStart + 1; i < src.length; i++) { if (src[i] < item) pos += 1; } // 将当前元素放置到正确的位置上,并记录写操作次数 while (item == src[pos]) { pos += 1; } if (item != src[pos]) { int temp = item; item = src[pos]; src[pos] = temp; writes++; } // 添加延迟操作以展示排序过程 await callback(src); } } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/gnome.dart ================================================ import '../functions.dart'; ///地精排序 (侏儒排序) Future gnomeSort(List src, SortCallback callback) async { int index = 0; while (index < src.length) { // 当 index 小于数组长度时执行循环 if (index == 0) index++; if (src[index] >= src[index - 1]) { // 如果当前元素大于等于前面的元素,则将 index 加1 index++; } else { // 否则,交换这两个元素,并将 index 减1(使得元素可以沉到正确位置) int temp = src[index]; src[index] = src[index - 1]; src[index - 1] = temp; index--; } await callback(src); } return; } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/heap.dart ================================================ import '../functions.dart'; ///堆排序 Future heapSort(List src, SortCallback callback) async { // 从最后一个非叶子节点开始,构建最大堆 for (int i = src.length ~/ 2; i >= 0; i--) { await heapify(src,callback, src.length, i); } // 依次取出最大堆的根节点(最大值),并进行堆化 for (int i = src.length - 1; i >= 0; i--) { int temp = src[0]; src[0] = src[i]; src[i] = temp; await heapify(src, callback,i, 0); } } Future heapify(List src, SortCallback callback, int n, int i) async{ int largest = i; int l = 2 * i + 1; // 左子节点索引 int r = 2 * i + 2; // 右子节点索引 // 如果左子节点存在并且大于父节点,则更新最大值索引 if (l < n && src[l] > src[largest]) largest = l; // 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引 if (r < n && src[r] > src[largest]) largest = r; // 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化 if (largest != i) { int temp = src[i]; src[i] = src[largest]; src[largest] = temp; heapify(src,callback, n, largest); } await callback(src); } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/insertion.dart ================================================ import '../functions.dart'; ///插入排序 Future insertionSort(List src, SortCallback callback) async { for (int i = 1; i < src.length; i++) { int temp = src[i]; // 将当前元素存储到临时变量 temp 中 int j = i - 1; // j 表示已排序部分的最后一个元素的索引 // 在已排序部分从后往前查找,找到合适位置插入当前元素 while (j >= 0 && temp < src[j]) { src[j + 1] = src[j]; // 当前元素比已排序部分的元素小,将元素后移一位 --j; // 向前遍历 // 更新排序结果回调 await callback(src); } src[j + 1] = temp; // 插入当前元素到已排序部分的正确位置 await callback(src); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/merge.dart ================================================ import '../functions.dart'; //快速排序 Future mergeSort(List src, SortCallback callback) async { await _mergeSort(src,callback,0,src.length-1); } ///归并排序 Future _mergeSort(List src, SortCallback callback,int leftIndex, int rightIndex) async { // 定义一个名为 merge 的异步函数,用于合并两个有序子数组 Future merge(int leftIndex, int middleIndex, int rightIndex) async { // 计算左侧子数组和右侧子数组的大小 int leftSize = middleIndex - leftIndex + 1; int rightSize = rightIndex - middleIndex; // 创建左侧子数组和右侧子数组 List leftList = List.generate(leftSize, (index) => 0); List rightList = List.generate(rightSize, (index) => 0); // 将原始数组中的元素分别复制到左侧子数组和右侧子数组中 for (int i = 0; i < leftSize; i++) { leftList[i] = src[leftIndex + i]; } for (int j = 0; j < rightSize; j++) { rightList[j] = src[middleIndex + j + 1]; } // 初始化游标和索引 int i = 0, j = 0; int k = leftIndex; // 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中 while (i < leftSize && j < rightSize) { if (leftList[i] <= rightList[j]) { src[k] = leftList[i]; i++; } else { src[k] = rightList[j]; j++; } await callback(src); k++; } // 将左侧子数组或右侧子数组中剩余的元素放入原始数组中 while (i < leftSize) { src[k] = leftList[i]; i++; k++; await callback(src); } while (j < rightSize) { src[k] = rightList[j]; j++; k++; await callback(src); } } // 如果左索引小于右索引,则递归地对数组进行归并排序 if (leftIndex < rightIndex) { // 计算中间索引位置 int middleIndex = (rightIndex + leftIndex) ~/ 2; // 分别对左侧子数组和右侧子数组进行归并排序 await _mergeSort(src,callback,leftIndex, middleIndex); await _mergeSort(src,callback,middleIndex + 1, rightIndex); await callback(src); // 合并两个有序子数组 await merge(leftIndex, middleIndex, rightIndex); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/oddEven.dart ================================================ import '../functions.dart'; ///奇偶排序(Odd-Even Sort) Future oddEvenSort(List src, SortCallback callback) async { bool isSorted = false; while (!isSorted) { // 当 isSorted 为 false 时执行循环 isSorted = true; // 先假设数组已经排好序 for (int i = 1; i <= src.length - 2; i = i + 2) { // 对奇数索引位置进行比较 if (src[i] > src[i + 1]) { // 如果当前元素大于后面的元素,则交换它们的值 int temp = src[i]; src[i] = src[i + 1]; src[i + 1] = temp; isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false await callback(src); } } for (int i = 0; i <= src.length - 2; i = i + 2) { // 对偶数索引位置进行比较 if (src[i] > src[i + 1]) { // 如果当前元素大于后面的元素,则交换它们的值 int temp = src[i]; src[i] = src[i + 1]; src[i + 1] = temp; isSorted = false; await callback(src); } } } return; } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/pigeonHole.dart ================================================ import '../functions.dart'; ///鸽巢排序 Future pigeonHoleSort(List src, SortCallback callback ) async{ int min = src[0]; int max = src[0]; int range, i, j, index; // 找到数组中的最大值和最小值 for (int a = 0; a < src.length; a++) { if (src[a] > max) max = src[a]; if (src[a] < min) min = src[a]; } // 计算鸽巢的个数 range = max - min + 1; List p = List.generate(range, (i) => 0); // 将数字分配到各个鸽巢中 for (i = 0; i < src.length; i++) { p[src[i] - min]++; } index = 0; // 将鸽巢中的数字取出,重新放回到数组中 for (j = 0; j < range; j++) { while (p[j]-- > 0) { src[index++] = j + min; await callback(src); } } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/quick.dart ================================================ import '../functions.dart'; //快速排序 Future quickSort(List src, SortCallback callback) async { await _quickSort(src,callback,0,src.length-1); } ///快速排序 Future _quickSort(List src, SortCallback callback,int leftIndex,int rightIndex) async { // 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置 Future _partition(int left, int right) async { // 选择中间位置的元素作为基准元素 int p = (left + (right - left) / 2).toInt(); // 交换基准元素和最右边的元素 var temp = src[p]; src[p] = src[right]; src[right] = temp; await callback(src); // 初始化游标 cursor int cursor = left; // 遍历数组并根据基准元素将元素交换到左侧或右侧 for (int i = left; i < right; i++) { if (cf(src[i], src[right]) <= 0) { // 如果当前元素小于等于基准元素,则交换它和游标位置的元素 var temp = src[i]; src[i] = src[cursor]; src[cursor] = temp; cursor++; await callback(src); } } // 将基准元素放置在游标位置 temp = src[right]; src[right] = src[cursor]; src[cursor] = temp; await callback(src); return cursor; // 返回基准元素的索引位置 } // 如果左索引小于右索引,则递归地对数组进行快速排序 if (leftIndex < rightIndex) { int p = await _partition(leftIndex, rightIndex); await _quickSort(src,callback,leftIndex, p - 1); // 对基准元素左侧的子数组进行快速排序 await _quickSort(src,callback, p + 1, rightIndex); // 对基准元素右侧的子数组进行快速排序 } } // 比较函数,用于判断两个元素的大小关系 cf(int a, int b) { if (a < b) { return -1; // 若 a 小于 b,则返回 -1 } else if (a > b) { return 1; // 若 a 大于 b,则返回 1 } else { return 0; // 若 a 等于 b,则返回 0 } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/selection.dart ================================================ import '../functions.dart'; ///选择排序 Future selectionSort(List src, SortCallback callback ) async { for (int i = 0; i < src.length; i++) { for (int j = i + 1; j < src.length; j++) { // 遍历未排序部分,内层循环控制变量 j if (src[i] > src[j]) { // 判断当前元素是否比后续元素小 int temp = src[j]; // 交换当前元素和后续较小的元素 src[j] = src[i]; src[i] = temp; } await callback(src); } } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions/shell.dart ================================================ import '../functions.dart'; ///希尔排序 Future shellSort(List src, SortCallback callback) async{ //定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。 for (int gap = src.length ~/ 2; gap > 0; gap ~/= 2) { //遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap,以 gap 为步长逐个遍历每个子序列。 for (int i = gap; i < src.length; i += 1) { //将当前遍历到的元素赋值给它 int temp = src[i]; //内部使用一个 for 循环来实现插入排序。 //循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。 int j; for (j = i; j >= gap && src[j - gap] > temp; j -= gap) { src[j] = src[j - gap]; } src[j] = temp; await callback(src); } } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/algorithm/sort/functions.dart ================================================ import 'functions/bubble.dart'; import 'functions/cocktail.dart'; import 'functions/comb.dart'; import 'functions/cycle.dart'; import 'functions/gnome.dart'; import 'functions/heap.dart'; import 'functions/insertion.dart'; import 'functions/merge.dart'; import 'functions/pigeonHole.dart'; import 'functions/quick.dart'; import 'functions/selection.dart'; import 'functions/shell.dart'; typedef SortFunction = Future Function(List src, SortCallback callback); typedef SortCallback = Future Function(List dist); Map sortFunctionMap = { 'insertion': insertionSort, 'bubble': bubbleSort, 'cocktail': cocktailSort, 'comb': combSort, 'pigeonHole': pigeonHoleSort, 'shell': shellSort, 'selection': selectionSort, 'gnome': gnomeSort, 'cycle': cycleSort, 'heap': heapSort, 'quick': quickSort, 'merge': mergeSort, }; Map sortNameMap = { 'insertion': '插入排序', 'bubble': '冒泡排序', 'cocktail': '鸡尾酒排序(双向冒泡排序)', 'comb': '梳排序', 'pigeonHole': '鸽巢排序', 'shell': '希尔排序', 'selection': '选择排序', 'gnome': '侏儒排序', 'cycle': '循环排序', 'heap': '堆排序', 'quick': '快速排序', 'merge': '归并排序', }; ================================================ FILE: modules/knowledge_system/algorithm/lib/src/data_scope/sort_config.dart ================================================ class SortConfig { final int count; final int seed; final Duration duration; final String name; SortConfig({ this.count = 100, this.duration = const Duration(microseconds: 1500), this.seed = -1, this.name = 'insertion', }); SortConfig copyWith({ int? count, int? seed, Duration? duration, String? name, }) => SortConfig( count:count??this.count, seed:seed??this.seed, duration:duration??this.duration, name:name??this.name, ); } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/data_scope/state.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; import '../algorithm/sort/functions.dart'; import 'sort_config.dart'; enum SortStatus{ none, // 未操作 sorting, // 排序中 sorted, // 排序完成 } class SortState with ChangeNotifier{ SortState(){ reset(); } SortStatus status = SortStatus.none; List data = []; SortConfig _config = SortConfig(); SortConfig get config => _config; Random random = Random(); set config(SortConfig config){ _config = config; reset(); notifyListeners(); } void reset(){ data.clear(); status = SortStatus.none; notifyListeners(); int count = config.count; if(config.seed!=-1){ random = Random(config.seed); } for (int i = 0; i < count; i++) { data.add(random.nextInt(1000)); } } void sort() async{ status = SortStatus.sorting; notifyListeners(); SortFunction? sortFunction = sortFunctionMap[config.name]; print(config.name); if(sortFunction!=null){ await sortFunction(data,(arr) async { await Future.delayed(config.duration); notifyListeners(); }); } status = SortStatus.sorted; notifyListeners(); } } class SortStateScope extends InheritedNotifier { const SortStateScope({ required super.notifier, required super.child, super.key, }); static SortState of(BuildContext context) => context.dependOnInheritedWidgetOfExactType()!.notifier!; } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/finding/data_scope/finding_config.dart ================================================ import '../../algorithm/finding/functions.dart'; class FindingConfig { final XY size; final int seed; final FindingAlgo algo; FindingConfig({ this.seed = -1, this.algo = FindingAlgo.bfs, this.size = (31, 31), }); FindingConfig copyWith({ XY? size, int? seed, FindingAlgo? algo, }) => FindingConfig( size: size ?? this.size, seed: seed ?? this.seed, algo: algo ?? this.algo, ); } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/finding/data_scope/finding_state.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-07-07 // Contact Me: 1981462002@qq.com import 'dart:math'; import 'package:flutter/material.dart'; import '../../algorithm/finding/functions.dart'; import 'finding_config.dart'; import 'position.dart'; import 'random_queue.dart'; enum FindingStatus{ none, // 未操作 finding, // 寻路中 find, // 寻路完成 } class FindingState with ChangeNotifier { int road = 1; //路 int wall = 0; //墙 bool lockMap = false; //路径是否被访问过 List> visitedList = []; //迷宫地图 List> blockList = []; //寻找到的正确路线 List> pathList = []; //方向 List> direction = [ [-1, 0], [0, -1], [1, 0], [0, 1] ]; FindingState() { reset(); } List> cacheBlockList = [[]]; void lock(){ print("============lock====="); lockMap=true; cacheBlockList.clear(); var (row,col) = _config.size; List.generate(row, (i) { List tempMazeList = []; List.generate(col, (j) { tempMazeList.add(blockList[i][j]); //设置为墙 }); cacheBlockList.add(tempMazeList); }); notifyListeners(); } void unlock(){ lockMap=false; cacheBlockList.clear(); notifyListeners(); } void tick(){ notifyListeners(); } FindingStatus status = FindingStatus.none; FindingConfig _config = FindingConfig(); FindingConfig get config => _config; late XY start; late XY end; void reset(){ status = FindingStatus.none; if(lockMap){ resetLocMap(); notifyListeners(); return; } var (row,col) = _config.size; if (row % 2 == 0 || col % 2 == 0) { throw "地图行数和列数不能为偶数"; } start = (1,0); end = (row-2,col-1); blockList = []; visitedList = []; pathList = []; //初始化迷宫遍历的方向(上、左、右、下)顺序(迷宫趋势) //随机遍历顺序,提高迷宫生成的随机性(共12种可能性) List.generate(direction.length, (index) { int random = Random().nextInt(direction.length); List temp = direction[random]; direction[random] = direction[index]; direction[index] = temp; }); List.generate(row, (i) { List tempVisited = []; List tempMazeList = []; List tempPath = []; List.generate(col, (j) { //行和列为奇数都设置为路,否则设置为墙 if (i % 2 == 1 && j % 2 == 1) { tempMazeList.add(1); //设置为路 } else { tempMazeList.add(0); //设置为墙 } //初始化访问,所有都没有访问过 tempVisited.add(false); tempPath.add(false); }); visitedList.add(tempVisited); blockList.add(tempMazeList); pathList.add(tempPath); }); blockList[start.$1][start.$2] = 1; blockList[end.$1][end.$2] = 1; createMap(start.$1,start.$2+1); status = FindingStatus.none; notifyListeners(); } ///设置为正确的路径 void setPath(int x, int y, bool isPath) { if (isInMap(x, y)) { pathList[x][y] = isPath; } tick(); } void changeAlgo(String name){ FindingAlgo algo = FindingAlgo.values.firstWhere((e)=>e.path==name); _config = config.copyWith(algo: algo); if(lockMap){ } status = FindingStatus.none; notifyListeners(); } void resetLocMap(){ cacheBlockList = cacheBlockList; visitedList = visitedList.map((innerList) => innerList.map((element) => false).toList()).toList(); pathList = pathList.map((innerList) => innerList.map((element) => false).toList()).toList(); } void run() async{ status = FindingStatus.finding; await config.algo.function(this); status = FindingStatus.find; notifyListeners(); } /// 创建地图并使用随机队列生成地图结构 void createMap(int startX, int startY) { RandomQueue randomQueue = RandomQueue(); // 创建一个随机队列实例 Position start = Position(startX, startY); // 创建起始位置 randomQueue.addRandomQueue(start); // 将起始位置加入随机队列 visitedList[startX][startY] = true; // 标记起始位置为已访问 // 使用随机队列生成地图 while (randomQueue.getSize() != 0) { Position position = randomQueue.removeRandomQueue(); // 移除队列中的一个位置 // 生成四个方向的新位置 List.generate(4, (i) { int newX = position.x! + direction[i][0] * 2; int newY = position.y! + direction[i][1] * 2; // 检查新位置是否在地图内且未被访问过 if (isInMap(newX, newY) && !visitedList[newX][newY]) { // 将新位置加入随机队列,并记录为已访问 randomQueue.addRandomQueue(Position(newX, newY, prePosition: position)); visitedList[newX][newY] = true; // 设置新位置与当前位置之间的路径或道路 setWithRoad(position.x! + direction[i][0], position.y! + direction[i][1]); } }); } // 把visitedList全部设置为没有访问 visitedList = visitedList.map((innerList) => innerList.map((element) => false).toList()).toList(); } ///设置为路 void setWithRoad(int x, int y) { blockList[x][y] = road; } ///用来判断blockList[i][j]是否在地图内 bool isInMap(int i, int j) { return i >= 0 && i < config.size.$1 && j >= 0 && j < config.size.$2; } Color getBoxColor(int i, int j) { Color color = Colors.white; if (blockList[i][j] == 0) { color = Colors.black54; } else if (start.$1 == i && start.$2 == j) { color = Colors.blue; } else if (end.$1 == i && end.$2 == j) { color = Colors.red; } else if (visitedList[i][j]) { color = Colors.blue.shade200; } if (pathList[i][j]) { color = Colors.orange; } return color; } } /// Provides the current [FindingState] to descendant widgets in the tree. class FindingStateScope extends InheritedNotifier { const FindingStateScope({ required super.notifier, required super.child, super.key, }); static FindingState of(BuildContext context) => context.dependOnInheritedWidgetOfExactType()!.notifier!; static FindingState read(BuildContext context) => context.getInheritedWidgetOfExactType()!.notifier!; } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/finding/data_scope/position.dart ================================================ import 'dart:collection'; base class Position extends LinkedListEntry { int? x, y; Position? prePosition; Position(int x, int y, {this.prePosition}) { this.x = x; this.y = y; } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/finding/data_scope/random_queue.dart ================================================ import 'dart:collection'; import 'dart:math'; import 'position.dart'; ///借助LinkedList链表实现随机队列 class RandomQueue { LinkedList _queue = LinkedList(); RandomQueue() { _queue = LinkedList(); } void addRandomQueue(Position position) { if (Random().nextInt(100) < 50) { _queue.addFirst(position);// 插入到队列头部 } else { _queue.add(position);// 插入到队列尾部 } } ///返回随机队列中的一个元素 Position removeRandomQueue() { if (_queue.isEmpty) { throw "数组为空"; } else { if (Random().nextInt(100) < 50) { Position position = _queue.first; _queue.remove(position); return position; } else { Position position = _queue.last; _queue.remove(position); return position; } } } //返回随机队列元素数量 int getSize() { return _queue.length; } //判断随机队列是否为空 bool isEmpty() { return _queue.isEmpty; } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/finding/view/board.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-07-07 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; import '../data_scope/finding_state.dart'; class MazeBoard extends StatelessWidget { const MazeBoard({super.key}); @override Widget build(BuildContext context) { FindingState state = FindingStateScope.of(context); return LayoutBuilder( builder: (ctx,cts){ double side = cts.biggest.shortestSide*0.9; double cellSize = side / state.config.size.$1; List rowList = []; List.generate(state.blockList.length, (i) { List columnList = []; List.generate(state.blockList[i].length, (j) { columnList.add(Container( width: cellSize, height: cellSize, decoration: BoxDecoration( color: state.getBoxColor(i, j), ), )); }); rowList.add(Row( mainAxisAlignment: MainAxisAlignment.center, children: columnList, )); }); return Column( mainAxisAlignment: MainAxisAlignment.center, children: rowList, ); }, ); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/finding/view/finding_button.dart ================================================ import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import '../data_scope/finding_state.dart'; class FindingButton extends StatelessWidget { const FindingButton({super.key}); @override Widget build(BuildContext context) { FindingState state = FindingStateScope.of(context); VoidCallback? action; IconData icon; String? tip; Color color; switch (state.status) { case FindingStatus.none: icon = Icons.not_started_outlined; color = Colors.green; action = state.run; tip = '运行'; break; case FindingStatus.finding: icon = Icons.stop_circle_outlined; color = Colors.grey; tip = '排序中'; action = null; break; case FindingStatus.find: icon = Icons.refresh; color = Colors.black; action = state.reset; tip = '重置'; break; } return TolyAction( onTap: action, tooltip: tip, child: Icon(icon, color: color, size: 20), ); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/finding/view/finding_page.dart ================================================ import 'package:algorithm/src/finding/view/board.dart'; import 'package:flutter/material.dart'; import 'finding_tool_bar.dart'; class FindingPage extends StatelessWidget { const FindingPage({super.key}); @override Widget build(BuildContext context) { return const Scaffold( appBar: FindingToolBar(), body: Column( children: [ Expanded(child: MazeBoard()), ], )); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/finding/view/finding_tool_bar.dart ================================================ import 'package:algorithm/algorithm.dart'; import 'package:algorithm/src/algorithm/sort/functions.dart'; import 'package:algorithm/src/finding/view/finding_button.dart'; import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:tolyui/basic/basic.dart'; import 'package:tolyui/tolyui.dart'; import '../data_scope/finding_state.dart'; class FindingToolBar extends StatelessWidget implements PreferredSizeWidget{ const FindingToolBar({super.key}); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; FindingState state = FindingStateScope.of(context); String name = state.config.algo.name; return DragToMoveWrapper( child: Container( padding: EdgeInsets.symmetric(horizontal: 10), height: 42, color: isDark ? const Color(0xff2C3036) : Colors.white, child: Row( children: [ const FindingButton(), TolyAction( tooltip: '更新地图', onTap: () { if(state.status==FindingStatus.finding){ $message.warning(message: '正在寻路中,请稍后'); return; } state.reset(); // Scaffold.of(context).openEndDrawer(); }, child: const Icon( Icons.refresh, color: Colors.orange, size: 20, )), TolyAction( selected: state.lockMap, tooltip: state.lockMap?'解除锁定':'锁定地图', onTap: () { if(state.status==FindingStatus.finding){ $message.warning(message: '正在寻路中,请稍后'); return; } if(state.lockMap){ state.unlock(); }else{ state.lock(); } // Scaffold.of(context).openEndDrawer(); }, child: const Icon( Icons.lock, color: Colors.blue, size: 20, )), Spacer(), TolyLink( href: 'https://github.com/toly1994328/FlutterUnit/blob/master/packages/algorithm/lib/src/algorithm/finding/functions/${name}.dart', text: '查看[$name 寻路]源码', hoverColor: Colors.blue, style: TextStyle(fontSize: 12,fontFamily: '宋体'), onTap: jumpURL), const SizedBox(width: 10), // TolyAction( // tooltip: '设置', // onTap: () { // Scaffold.of(context).openEndDrawer(); // }, // child: const Icon( // Icons.settings, // size: 20, // )), ], ), ), ); } @override // TODO: implement preferredSize Size get preferredSize => Size.fromHeight(42); } class SortBar extends StatelessWidget { const SortBar({super.key}); @override Widget build(BuildContext context) { return Row( children: [ const SortButton(), const SizedBox( width: 10, ), const SortSelector(), const SizedBox( width: 10, ), GestureDetector( onTap: () { Scaffold.of(context).openEndDrawer(); }, child: const Icon(Icons.settings)) ], ); } } class SortSelector extends StatelessWidget { const SortSelector({super.key}); @override Widget build(BuildContext context) { return DropSelectableWidget( fontSize: 12, data: sortNameMap.values.toList(), iconSize: 20, height: 28, width: 200, disableColor: const Color(0xff1F425F), onDropSelected: (int index) async { SortState state = SortStateScope.of(context); state.config = state.config.copyWith(name: sortNameMap.keys.toList()[index]); }, ); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/navigation/menu/algo_menu.dart ================================================ import 'sort.dart'; import 'finding.dart'; Map get algoMenus => { 'children': [ findingMenus, sortMenus, ] }; ================================================ FILE: modules/knowledge_system/algorithm/lib/src/navigation/menu/finding.dart ================================================ import 'package:flutter/material.dart'; Map get findingMenus => { 'path': '/finding', 'icon': Icons.pages_outlined, 'label': '寻路算法', 'children': [ { 'path': '/BFS', 'label': '广度优先搜索', }, { 'path': '/DFS', 'label': '深度优先搜索', }, { 'path': '/AStar', 'label': 'A* 寻路算法', }, { 'path': '/BestFS', 'label': '最佳优先算法', }, { 'path': '/dijkstra', 'label': 'Dijkstra 算法', }, ] }; ================================================ FILE: modules/knowledge_system/algorithm/lib/src/navigation/menu/sort.dart ================================================ import 'package:flutter/material.dart'; Map get sortMenus => { 'path': '/sort', 'icon': Icons.sort, 'label': '排序算法', 'children': [ { 'path': '/insertion', 'label': '插入排序', }, { 'path': '/bubble', 'label': '冒泡排序', }, { 'path': '/cocktail', 'label': '鸡尾酒排序', }, { 'path': '/comb', 'label': '梳排序', }, { 'path': '/pigeonHole', 'label': '鸽巢排序', }, { 'path': '/shell', 'label': '希尔排序', }, { 'path': '/selection', 'label': '选择排序', }, { 'path': '/gnome', 'label': '侏儒排序', }, { 'path': '/cycle', 'label': '循环排序', }, { 'path': '/heap', 'label': '堆排序', }, { 'path': '/quick', 'label': '快速排序', }, { 'path': '/merge', 'label': '归并排序', }, ] }; ================================================ FILE: modules/knowledge_system/algorithm/lib/src/navigation/router/router.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-07-07 // Contact Me: 1981462002@qq.com import 'package:algorithm/algorithm.dart'; import 'package:algorithm/src/finding/view/finding_page.dart'; import 'package:flutter/material.dart'; import 'package:fx_go_router_ext/fx_go_router_ext.dart'; import '../../sort/sort_page.dart'; import '../view/algo_desk_navigation.dart'; RouteBase get algoRoutes => GoRoute( path: '/', redirect: (_, __) => null, routes: [ ShellRoute( builder: (BuildContext context, GoRouterState state, Widget child) { return AppDeskNavigation(content: child); }, routes: [ GoRoute( path: 'sort/:name', builder: (BuildContext context, GoRouterState state) { return const DeskSortPage(); }, ), GoRoute( path: 'finding/:name', builder: (BuildContext context, GoRouterState state) { return const FindingPage(); }, ), ]) ], ); ================================================ FILE: modules/knowledge_system/algorithm/lib/src/navigation/view/algo_desk_navigation.dart ================================================ import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import 'algo_menu_tree.dart'; class AppDeskNavigation extends StatelessWidget { final Widget content; const AppDeskNavigation({super.key, required this.content}); @override Widget build(BuildContext context) { Color backgroundColor = context.isDark ? Color(0xff001529) : Colors.white; return Scaffold( backgroundColor: backgroundColor, body: Row( children: [ const AppMenuTree(), Expanded(child: content), ], ), ); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/navigation/view/algo_menu_cell.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-05-23 // Contact Me: 1981462002@qq.com import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; class AlgoMenuMetaExt extends Extra { final String? subtitle; final String? tag; final bool? isFlutter; const AlgoMenuMetaExt({ required this.subtitle, required this.tag, required this.isFlutter, }); factory AlgoMenuMetaExt.fromMap(Map map) { return AlgoMenuMetaExt( subtitle: map['subtitle'], tag: map['tag'], isFlutter: map['isFlutter'], ); } } class AlgoMenuCell extends StatelessWidget { final MenuNode menuNode; final DisplayMeta display; final MenuTreeCellStyle? style; const AlgoMenuCell({ super.key, required this.menuNode, required this.display, this.style, }); MenuTreeCellStyle get effectStyle => style ?? (display.isDark ? MenuTreeCellStyle.dark() : MenuTreeCellStyle.light()); Color? effectForegroundColor(MenuTreeCellStyle style) { if (display.selected) { return display.isDark ? Colors.white : style.activeForegroundColor; } if (display.hovered) { return display.isDark ? Colors.white : style.hoverForegroundColor; } return style.inactiveForegroundColor; } double get anim => display.anima ?? 1; Color? backgroundColor(MenuTreeCellStyle style) { if (hasChild) return null; if (selectOrPlaying) { return style.activeBackgroundColor.withOpacity(anim); } if (display.hovered) { return style.hoverBackgroundColor; } return null; } bool get selectOrPlaying => (display.selected || display.playing); bool get hasChild => menuNode.children.isNotEmpty; @override Widget build(BuildContext context) { MenuTreeCellStyle effectStyle = style ?? (display.isDark ? MenuTreeCellStyle.dark() : MenuTreeCellStyle.light()); Color? bgColor = backgroundColor(effectStyle); Color? fgColor = effectForegroundColor(effectStyle); EdgeInsets padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 2); IconData? icon; if (menuNode.data is IconMenu) { icon = (menuNode.data as IconMenu).icon; } Widget cell = DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), color: bgColor, ), child: Row( children: [ Expanded( child: Container( alignment: Alignment.centerLeft, padding: EdgeInsets.only( left: 12.0 + (28 * menuNode.depth), top: ext?.subtitle == null ? 8 : 8, bottom: ext?.subtitle == null ? 8 : 8, ), child: Wrap( crossAxisAlignment: WrapCrossAlignment.center, children: [ if (icon != null) Padding( padding: const EdgeInsets.only(right: 8.0), child: Icon(icon, size: 20, color: fgColor), ), _buildTitle(fgColor) ], ), ), ), if (ext?.tag != null) _buildTag(ext), if (menuNode.children.isNotEmpty) _buildExpandIndicator(display.expanded, fgColor) ], ), ); if (selectOrPlaying && effectStyle.showIndicator && !hasChild) { cell = Stack( alignment: Alignment.centerLeft, children: [ cell, LineIndicator(progress: anim, color: fgColor), ], ); } return Padding(padding: padding, child: cell); } AlgoMenuMetaExt? get ext => menuNode.data.ext?.me(); Widget _buildTitle(Color? fgColor) { TextStyle subStyle = const TextStyle(fontSize: 12, color: Colors.grey); TextStyle titleStyle = TextStyle(color: fgColor); Widget child = Text( menuNode.data.label, overflow: TextOverflow.ellipsis, maxLines: 1, style: titleStyle, ); if (ext?.isFlutter ?? false) { child = Wrap( spacing: 4, crossAxisAlignment: WrapCrossAlignment.center, children: [child, const FlutterLogo(size: 14)], ); } if (ext?.subtitle != null) { child = Column( crossAxisAlignment: CrossAxisAlignment.start, children: [child, Text(ext!.subtitle!, style: subStyle)], ); } return child; } Widget _buildTag(AlgoMenuMetaExt? ext) { TextStyle tagStyle = const TextStyle(color: Colors.white, height: 1, fontSize: 10); Widget child = Text('${ext?.tag}', overflow: TextOverflow.ellipsis, maxLines: 1, style: tagStyle); return Padding( padding: const EdgeInsets.only(right: 8.0), child: Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), decoration: BoxDecoration( color: Colors.blue.withOpacity(0.8), borderRadius: BorderRadius.circular(4)), child: child), ); } Widget _buildExpandIndicator(bool expanded, Color? color) { return Padding( padding: const EdgeInsets.only(right: 8.0), child: Transform.rotate( angle: display.rate * pi, child: Icon(CupertinoIcons.chevron_down, size: 16, color: color))); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/navigation/view/algo_menu_tree.dart ================================================ import 'package:algorithm/algorithm.dart'; import 'package:flutter/material.dart'; import 'package:fx_go_router_ext/fx_go_router_ext.dart'; import 'package:tolyui/tolyui.dart'; import '../../finding/data_scope/finding_state.dart'; import '../menu/algo_menu.dart'; import 'package:path/path.dart' as p ; import 'algo_menu_cell.dart'; class AppMenuTree extends StatefulWidget { const AppMenuTree({super.key}); @override State createState() => _AppMenuTreeState(); } class _AppMenuTreeState extends State with RouterChangeListenerMixin { late MenuTreeMeta _menuMeta; @override void initState() { super.initState(); _initTreeMeta(); } void _initTreeMeta() { MenuNode root = MenuNode.fromMap(algoMenus); List parts = Uri.parse(path).pathSegments; String parentPath = parts.sublist(0, parts.length - 1).join('/'); _menuMeta = MenuTreeMeta( expandMenus: ['/$parentPath'], activeMenu: root.find(path), root: root, ); } @override Widget build(BuildContext context) { Color expandBackgroundColor = context.isDark ? Colors.black : Colors.transparent; Color backgroundColor = context.isDark ? Color(0xff001529) : Colors.white; return TolyRailMenuTree( enableWidthChange: true, maxWidth: 360, width: 190, meta: _menuMeta, builder: (node, display) => AlgoMenuCell( menuNode: node, display: display, ), backgroundColor: backgroundColor, expandBackgroundColor: expandBackgroundColor, onSelect: _onSelect, ); } void _onSelect(MenuNode menu) { if (menu.isLeaf) { FindingState state = FindingStateScope.read(context); if(state.status==FindingStatus.finding){ $message.warning(message: '正在寻路中,请稍后'); return; } context.go(menu.id); } else { _menuMeta = _menuMeta.select(menu, singleExpand: true); setState(() {}); } } @override void reassemble() { MenuNode root = MenuNode.fromMap(algoMenus); _menuMeta = _menuMeta.copyWith(root: root); super.reassemble(); } @override void onChangeRoute(String path) { if(path.startsWith('/sort')){ SortState state = SortStateScope.of(context); state.config = state.config.copyWith(name: p.basename(path)); print("=====path:${p.basename(path)}==${state.config.name}======"); }else{ FindingState state = FindingStateScope.read(context); state.changeAlgo(p.basename(path)); } _menuMeta = _menuMeta.selectPath(path, singleExpand: true); setState(() {}); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/sort/data_painter.dart ================================================ import 'package:flutter/material.dart'; class DataPainter extends CustomPainter { final List data; DataPainter({required this.data}); @override void paint(Canvas canvas, Size size) { canvas.clipRect(Offset.zero & size); double itemWidth = size.width / data.length; Paint paint = Paint(); paint.strokeWidth = itemWidth; paint.strokeCap = StrokeCap.round; for (int i = 0; i < data.length; i++) { int value = data[i]; if (value < 1000 * .10) { paint.color = Colors.blue.shade100; } else if (value < 1000 * .20) { paint.color = Colors.blue.shade200; } else if (value < 1000 * .30) { paint.color = Colors.blue.shade300; } else if (value < 1000 * .40) { paint.color = Colors.blue.shade400; } else if (value < 1000 * .50) { paint.color = Colors.blue.shade500; } else if (value < 1000 * .60) { paint.color = Colors.blue.shade600; } else if (value < 1000 * .70) { paint.color = Colors.blue.shade700; } else if (value < 1000 * .80) { paint.color = Colors.blue.shade800; } else if (value < 1000 * .90) { paint.color = Colors.blue.shade900; } else { paint.color = const Color(0xFF011E51); } canvas.drawLine( Offset(i * itemWidth + itemWidth / 2, 0), Offset( i * itemWidth + itemWidth / 2, size.height * (value / 1000), ), paint); } } @override bool shouldRepaint(covariant DataPainter oldDelegate) { return true; } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/sort/sort_page.dart ================================================ import 'package:flutter/material.dart'; import 'sort_parper.dart'; import 'sort_setting.dart'; import 'top_bar/sort_bar.dart'; class DeskSortPage extends StatelessWidget{ const DeskSortPage({super.key}); @override Widget build(BuildContext context) { return const Scaffold( endDrawer: Drawer( child: SortSettings(), ), body: Column( children: [ DeskSortBar(), Expanded(child: SortPaper()), ], ), ); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/sort/sort_parper.dart ================================================ import 'package:flutter/material.dart'; import '../../algorithm.dart'; import 'data_painter.dart'; class SortPaper extends StatelessWidget{ const SortPaper({super.key}); @override Widget build(BuildContext context) { SortState state = SortStateScope.of(context); List numbers = state.data; return CustomPaint( painter: DataPainter(data: numbers), child: ConstrainedBox(constraints: BoxConstraints.expand()), ); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/sort/sort_setting.dart ================================================ import 'package:algorithm/algorithm.dart'; import 'package:flutter/material.dart'; class SortSettings extends StatefulWidget { const SortSettings({super.key,}); @override State createState() => _SortSettingsState(); } class _SortSettingsState extends State { late TextEditingController _count = TextEditingController(); late TextEditingController _duration = TextEditingController(); late TextEditingController _seed = TextEditingController(); @override void initState() { super.initState(); } @override void didChangeDependencies() { super.didChangeDependencies(); SortState state = SortStateScope.of(context); _count.text = state.config.count.toString(); _duration.text = state.config.duration.inMicroseconds.toString(); _seed.text = state.config.seed.toString(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, leading: BackButton(), actions: [ IconButton(onPressed: (){ SortState state = SortStateScope.of(context); state.config =state.config.copyWith( count: int.parse(_count.text), duration: Duration( microseconds: int.parse(_duration.text), ), seed: int.parse(_seed.text) ); Navigator.of(context).pop(); }, icon: Icon(Icons.check))], iconTheme: IconThemeData(color: Colors.black), titleTextStyle: TextStyle( color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold, ), centerTitle: true, title: Text('排序算法配置'), ), body: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Row( children: [ Text('数据数量(个数):'), const SizedBox( width: 20, ), Expanded( child: TextField( controller: _count, )), ], ), Row( children: [ Text('时间间隔(微秒):'), const SizedBox( width: 20, ), Expanded( child: TextField( controller: _duration, )), ], ), Row( children: [ Text('随机种子:'), const SizedBox( width: 20, ), Expanded( child: TextField( controller: _seed, )), ], ), Spacer(), // ElevatedButton( // onPressed: () { // SortState state = SortStateScope.of(context); // state.config =state.config.copyWith( // count: int.parse(_count.text), // duration: Duration( // microseconds: int.parse(_duration.text), // ), // seed: int.parse(_seed.text) // ); // Navigator.of(context).pop(); // }, // child: Text('确定设置')) ], ), ), ); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/sort/top_bar/sort_bar.dart ================================================ import 'package:algorithm/algorithm.dart'; import 'package:algorithm/src/algorithm/sort/functions.dart'; import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:tolyui/basic/basic.dart'; import 'sort_button.dart'; class DeskSortBar extends StatelessWidget { const DeskSortBar({super.key}); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; String name = SortStateScope.of(context).config.name; return DragToMoveWrapper( child: Container( padding: EdgeInsets.symmetric(horizontal: 10), height: 42, color: isDark ? const Color(0xff2C3036) : Colors.white, child: Row( children: [ const SortButton(), Spacer(), TolyLink( href: 'https://github.com/toly1994328/FlutterUnit/blob/master/packages/algorithm/lib/src/algorithm/sort/functions/${name}.dart', text: '查看排序源码', hoverColor: Colors.blue, style: TextStyle(fontSize: 12, fontFamily: '宋体'), onTap: jumpURL), const SizedBox(width: 10), TolyAction( tooltip: '设置', onTap: Scaffold.of(context).openEndDrawer, child: const Icon(Icons.settings, size: 20)), ], ), ), ); } } class SortBar extends StatelessWidget { const SortBar({super.key}); @override Widget build(BuildContext context) { return Row( children: [ const SortButton(), const SizedBox( width: 10, ), const SortSelector(), const SizedBox( width: 10, ), GestureDetector( onTap: () { Scaffold.of(context).openEndDrawer(); }, child: const Icon(Icons.settings)) ], ); } } class SortSelector extends StatelessWidget { const SortSelector({super.key}); @override Widget build(BuildContext context) { return DropSelectableWidget( fontSize: 12, data: sortNameMap.values.toList(), iconSize: 20, height: 28, width: 200, disableColor: const Color(0xff1F425F), onDropSelected: (int index) async { SortState state = SortStateScope.of(context); state.config = state.config.copyWith(name: sortNameMap.keys.toList()[index]); }, ); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/sort/top_bar/sort_button.dart ================================================ import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import '../../../algorithm.dart'; class SortButton extends StatelessWidget { const SortButton({super.key}); @override Widget build(BuildContext context) { SortState state = SortStateScope.of(context); VoidCallback? action; IconData icon; String? tip; Color color; switch (state.status) { case SortStatus.none: icon = Icons.not_started_outlined; color = Colors.green; action = state.sort; tip = '运行'; break; case SortStatus.sorting: icon = Icons.stop_circle_outlined; color = Colors.grey; tip = '寻路中'; action = null; break; case SortStatus.sorted: icon = Icons.refresh; color = Colors.black; action = state.reset; tip = '重置'; break; } return TolyAction( onTap: action, tooltip: tip, child: Icon(icon, color: color, size: 20), ); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/views/algo_page.dart ================================================ import 'package:algorithm/src/finding/data_scope/finding_state.dart'; import 'package:algorithm/src/navigation/router/router.dart'; import 'package:flutter/material.dart'; import 'package:fx_go_router_ext/fx_go_router_ext.dart'; import '../../algorithm.dart'; class AlgoScope extends StatelessWidget { final Widget child; const AlgoScope({super.key, required this.child}); @override Widget build(BuildContext context) { print("=====build======="); return SortStateScope( notifier: SortState(), child: FindingStateScope( notifier: FindingState(), child: child, ), ); } } class AlgoRouterPage extends StatefulWidget { const AlgoRouterPage({super.key}); @override State createState() => _AlgoRouterPageState(); } class _AlgoRouterPageState extends State { final GoRouter _router = GoRouter( initialLocation: '/finding/BFS', routes: [algoRoutes], onException: (BuildContext ctx, GoRouterState state, GoRouter router) { router.go('/404', extra: state.uri.toString()); }, ); // late final DisplayLogic logic; @override void initState() { // logic = DisplayLogic(DisplayState( // router: '/base/size', // activeIndex: 0, // total: kDisplayMap['/base/size']!.length, // )); super.initState(); } @override Widget build(BuildContext context) { return AlgoScope( child: Column( children: [ const Divider(), Expanded( child: Router.withConfig(config: _router), ), ], ), ); } } ================================================ FILE: modules/knowledge_system/algorithm/lib/src/views/desktop/desk_algo_panel.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Author: 张风捷特烈 // CreateTime: 2024-07-07 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; // class LayoutRouterPage extends StatefulWidget { // LayoutRouterPage({super.key}); // // @override // State createState() => _LayoutRouterPageState(); // } // // class _LayoutRouterPageState extends State { // final GoRouter _router = GoRouter( // initialLocation: '/base/size', // routes: [layoutRoutes], // onException: (BuildContext ctx, GoRouterState state, GoRouter router) { // router.go('/404', extra: state.uri.toString()); // }, // ); // // late final DisplayLogic logic; // // @override // void initState() { // logic = DisplayLogic(DisplayState( // router: '/base/size', // activeIndex: 0, // total: kDisplayMap['/base/size']!.length, // )); // super.initState(); // } // // @override // Widget build(BuildContext context) { // return DisplayScope( // notifier: logic, // child: Column( // children: [ // const Divider(), // Expanded( // child: Router.withConfig(config: _router), // ), // ], // ), // ); // } // } class DeskAlgoPanel extends StatelessWidget { const DeskAlgoPanel({super.key}); @override Widget build(BuildContext context) { return Row( ); } } ================================================ FILE: modules/knowledge_system/algorithm/pubspec.yaml ================================================ name: algorithm description: algorithm version: 0.0.1 homepage: publish_to: none environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/knowledge_system/algorithm/test/utils_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; void main() { } ================================================ FILE: modules/knowledge_system/artifact/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .packages build/ ================================================ FILE: modules/knowledge_system/artifact/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 channel: stable project_type: package ================================================ FILE: modules/knowledge_system/artifact/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/knowledge_system/artifact/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/knowledge_system/artifact/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/knowledge_system/artifact/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/knowledge_system/artifact/lib/artifact.dart ================================================ library artifact; export 'src/articles/view/artifact_page.dart'; export 'src/points/exp.dart'; export 'src/articles/data/exp.dart'; export 'src/articles/view/desk_artifact_page.dart'; ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/bloc/article/bloc.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../data/exp.dart'; import '../../data/repository/article_repository.dart'; class ArticleBloc extends Cubit { ArticleBloc( this.repository, { this.pageSize = 20, this.groupId, }) : super(ArticleState.empty()); final int pageSize; final int? groupId; final ArticleRepository repository; /// 初始化时,加载 [pageSize] 条记录 /// void init() { _loadDataFromDb(1, pageSize, filter: groupId?.toString(), requestNet: true); } Future loadNextPageMore() async { int curPage = state.data.length ~/ pageSize; int nextPage = curPage + 1; ArticleFilter filter = ArticleFilter( page: nextPage, pageSize: pageSize, groupId: groupId, ); List
result = await repository.queryByDb(filter); int count = await repository.total(filter); emit(ArticleWithData( data: state.data + result, total: count, )); } Future _loadDataFromDb( int page, int pageSize, { bool requestNet = false, String? filter, }) async { ArticleFilter filter = ArticleFilter( page: page, pageSize: pageSize, groupId: groupId, ); List
data = await repository.queryByDb(filter); // 没有内存缓存 并且数据库有数据 if (data.isNotEmpty) { emit(ArticleWithData(data: data, total: data.length)); } } } sealed class ArticleState { final List
data; const ArticleState({this.data = const []}); factory ArticleState.empty() => const ArticleWithData(); } class ArticleLoading extends ArticleState { const ArticleLoading({super.data}); } class ArticleWithData extends ArticleState { final int total; const ArticleWithData({super.data, this.total = 0}); } class ArticleFailed extends ArticleState { final String error; const ArticleFailed(this.error, {super.data}); } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/bloc/columnize/bloc.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../data/exp.dart'; import '../../data/repository/columnize_repository.dart'; class ColumnizeBloc extends Cubit { final ColumnizeRepository repository; ColumnizeBloc(this.repository) : super(ColumnizeState.initial()); void init() { _loadDataFromDb(requestNet: true); } Future _loadDataFromDb({bool requestNet = false}) async { /// List data = await repository.queryByDb(); // if (data.isNotEmpty) { emit(ColumnizeState(data)); } } } class ColumnizeState { List data; ColumnizeState(this.data); factory ColumnizeState.initial() => ColumnizeState([ Columnize(title: '-', url: '-'), Columnize(title: '-', url: '-'), Columnize(title: '-', url: '-'), Columnize(title: '-', url: '-'), Columnize(title: '-', url: '-'), Columnize(title: '-', url: '-'), Columnize(title: '-', url: '-'), ]); } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/bloc/exp.dart ================================================ export 'article/bloc.dart'; export 'columnize/bloc.dart'; ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/data/dao/article_dao.dart ================================================ import 'package:fx_dao/fx_dao.dart'; import 'package:sqflite/sqflite.dart'; import '../model/article.dart'; class ArticleDao extends Dao { @override String get createSql => ''; @override String get name => 'article'; Future insert(Article po) => database.insert( name, po.toJson(), conflictAlgorithm: ConflictAlgorithm.replace, ); Future insertOrUpdate(Article po) async { bool canUpdate = await shouldUpdate(po.id, po.update); return database.insert( name, po.toJson(), conflictAlgorithm: canUpdate ? ConflictAlgorithm.replace : ConflictAlgorithm.ignore, ); } /// 当前数据是否需要更新 Future shouldUpdate(int id, int updateAt) async { List> data = await database .rawQuery("SELECT `update` FROM $name WHERE id = ?", [id]); // 没有数据,可以更新 if (data.isEmpty) { return true; } // 服务器中数据更新时间,大于本地数据库内容,可以更新 return updateAt > data.first['update']; } Future> query(ArticleFilter filter) async { String queryArgs = ''; List args = []; if (filter.filter != null) { queryArgs += "AND filter = ? "; args.add(filter.filter); } if (filter.groupId != null) { if (queryArgs.isEmpty) { queryArgs += 'WHERE groupId = ? '; } else { queryArgs += "AND groupId = ? "; } args.add(filter.groupId); } queryArgs += 'LIMIT ? OFFSET ?'; args.addAll([filter.pageSize, filter.offset]); List> data = await database.rawQuery( "SELECT * FROM $name $queryArgs", args, ); List
result = data.map(Article.fromDb).toList(); return result; } Future total(ArticleFilter filter) async { bool hasGroupId = filter.groupId != null; String familySql = hasGroupId ? 'WHERE groupId = ?' : ''; List familyArg = hasGroupId ? [filter.groupId!] : []; String sql = "SELECT count(id) as `count` FROM article $familySql"; List> result = await database.rawQuery(sql, familyArg); if (result.isNotEmpty) { return result.first['count'] as int ?? 0; } return 0; } @override Convertor
get convertor => Article.fromDb; @override Future update(String id, Article frame) async { return 0; } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/data/dao/columnize_dao.dart ================================================ import 'package:fx_dao/fx_dao.dart'; import '../model/columnize.dart'; class ColumnizeDao extends Dao { @override String get createSql => ''; @override String get name => 'columnize'; Future insert(Columnize po) => database.insert( name, po.toJson(), conflictAlgorithm: ConflictAlgorithm.replace, ); Future insertOrUpdate(Columnize po) async { bool canUpdate = await shouldUpdate(po.id, po.update); return database.insert( name, po.toJson(), conflictAlgorithm: canUpdate ? ConflictAlgorithm.replace : ConflictAlgorithm.ignore, ); } /// 当前数据是否需要更新 Future shouldUpdate(int id, int updateAt) async { List> data = await database .rawQuery("SELECT `update` FROM $name WHERE id = ?", [id]); // 没有数据,可以更新 if (data.isEmpty) { return true; } // 服务器中数据更新时间,大于本地数据库内容,可以更新 return updateAt > data.first['update']; } Future> query({ int page = 1, int pageSize = 20, String? filter, }) async { String queryArgs = ''; List args = []; if (filter != null) { queryArgs += "AND filter = ? "; args.add(filter); } queryArgs += 'LIMIT ? OFFSET ?'; args.addAll([pageSize, (page - 1) * pageSize]); List> data = await database.rawQuery( "SELECT * FROM $name $queryArgs", args, ); List result = data.map(Columnize.fromDb).toList(); return result; } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/data/exp.dart ================================================ export 'repository/article_repository.dart'; export 'repository/columnize_repository.dart'; export 'model/article.dart'; export 'model/columnize.dart'; export 'dao/article_dao.dart'; export 'dao/columnize_dao.dart'; ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/data/model/article.dart ================================================ import 'package:fx_dao/fx_dao.dart'; class Article implements Po{ final String? username; final String title; final String? subtitle; final String url; final String? cover; final int create; final int update; final int id; final int userId; final int groupId; Article({ this.username = '', required this.title, this.subtitle = '', required this.url, this.cover = '', this.update = 0, this.create = 0, required this.userId, this.id = -1, required this.groupId, }); Map toJson() => { "id": id, "userId": userId, "groupId": groupId, "username": username, "title": title, "createAt": create, "subtitle": subtitle, "url": url, "cover": cover, "updateAt": update, }; factory Article.fromMap(dynamic map)=> Article( id: map['articleId'] ?? '', username: map['userName'] ?? '', userId: map['userId'] ?? '', title: map['title'] ?? '', create: DateTime.parse(map['createAt']).millisecondsSinceEpoch, update: DateTime.parse(map['updateAt']).millisecondsSinceEpoch, subtitle: map['subtitle'] ?? '', url: map['url'] ?? '', groupId: map['groupId'] ?? 1, cover: map['caver'] ?? '', ); factory Article.fromDb(dynamic map)=> Article( id: map['id'] ?? '', username: map['username'] ?? '', userId: map['userId'] ?? '', title: map['title'] ?? '', create: map['createAt'] ?? 0 , update: map['updateAt'] ?? 0, subtitle: map['subtitle'] ?? '', url: map['url'] ?? '', groupId: map['groupId'] ?? 1, cover: map['cover'] ?? '', ); } class ArticleFilter{ final String? filter; final int? groupId; final int page; final int pageSize; const ArticleFilter({ this.filter, this.groupId, this.page = 1, this.pageSize = 20, }); int get offset =>pageSize*(page-1); ArticleFilter copyWith({ String? filter, int? groupId, int? page, }) { return ArticleFilter( filter: filter ?? this.filter, groupId: groupId ?? this.groupId, page: page ?? this.page, pageSize: pageSize ); } @override String toString() { return 'ArticleFilter{filter: $filter, groupId: $groupId, page: $page, pageSize: $pageSize}'; } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/data/model/columnize.dart ================================================ class Columnize { final String? username; final String title; final String subtitle; final String? url; final String? cover; final int create; final int update; final int count; final int userId; final int id; Columnize({ this.username = '', required this.title, this.subtitle = '', required this.url, this.cover = '', this.update = 0, this.create = 0, this.count = 0, this.id = -1, this.userId = -1, }); Map toJson() => { "username": username, "id": id, "title": title, "createAt": create, "subtitle": subtitle, "userId": userId, "url": url, "cover": cover, "updateAt": update, "count": count, }; factory Columnize.fromMap(dynamic map) => Columnize( username: map['userName'] ?? '', title: map['title'] ?? '', create: DateTime.parse(map['createAt']).millisecondsSinceEpoch, update: DateTime.parse(map['updateAt']).millisecondsSinceEpoch, subtitle: map['subtitle'] ?? '', url: map['url'] ?? '', cover: map['caver'] ?? '', count: map['count'] ?? 0, userId: map['userId'] ?? 0, id: map['columnizeId'] ?? -1, ); factory Columnize.fromDb(dynamic map)=> Columnize( id: map['id'] ?? '', username: map['username'] ?? '', userId: map['userId'] ?? '', title: map['title'] ?? '', create: map['createAt'] ?? 0 , count: map['count'] ?? 0 , update: map['updateAt'] ?? 0, subtitle: map['subtitle'] ?? '', url: map['url'] ?? '', cover: map['cover'] ?? '', ); @override String toString() { return 'Columnize{username: $username, title: $title, subtitle: $subtitle, url: $url, cover: $cover, create: $create, update: $update, count: $count}'; } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/data/repository/article_repository.dart ================================================ import 'package:storage/storage.dart'; import '../dao/article_dao.dart'; import '../exp.dart'; // 仓储: 提供数据 class ArticleRepository { const ArticleRepository(); ArticleDao get dao => AppStorage().article(); // 从数据库加载资源 Future> queryByDb(ArticleFilter filter) async { List
caches = await dao.query(filter); return caches; } Future total(ArticleFilter filter) => dao.total(filter); } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/data/repository/columnize_repository.dart ================================================ import 'package:storage/storage.dart'; import '../exp.dart'; // 仓储: 提供数据 class ColumnizeRepository { const ColumnizeRepository(); ColumnizeDao get dao => AppStorage().article(); // 从数据库加载资源 Future> queryByDb({ int page = 1, int pageSize = 20, }) async { List caches = await dao.query( page: page, pageSize: pageSize, ); return caches; } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/view/article/article_detail_page.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:webview_flutter/webview_flutter.dart'; import '../../data/exp.dart'; class ArticleDetailPage extends StatefulWidget { final Article article; const ArticleDetailPage({Key? key, required this.article}) : super(key: key); @override State createState() => _ArticleDetailPageState(); } class _ArticleDetailPageState extends State { late WebViewController controller; int progress = 0; @override void initState() { print(widget.article.url); super.initState(); controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..loadRequest(Uri.parse('https://juejin.cn${widget.article.url}')) ..setNavigationDelegate( NavigationDelegate( onProgress: (int progress) { print(progress); this.progress =progress; setState(() { }); }, onPageStarted: (String url) { }, onPageFinished: (String url) { }, )); } _launchURL(String url) async { Uri uri = Uri.parse(url); if (await canLaunchUrl(Uri.parse(url))) { await launchUrl(uri,mode: LaunchMode.externalApplication); } else { debugPrint('Could not launch $url'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.article.title),actions: [ IconButton(onPressed: (){ _launchURL('https://juejin.cn${widget.article.url}'); }, icon: Icon(TolyIcon.icon_artifact,size: 20,)) ],), body: Stack( alignment: Alignment.center, children: [ WebViewWidget(controller: controller), if(progress!=100) Center( child: CupertinoActivityIndicator(), ) ], ), ); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/view/article/column_detail_page.dart ================================================ import 'package:flutter/material.dart'; import '../../data/exp.dart'; import 'sliver_article.dart'; class ColumnDetailPage extends StatelessWidget { final Columnize columnize; const ColumnDetailPage({Key? key, required this.columnize}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(columnize.title), ), body: CustomScrollView( slivers: [ SliverPadding( padding: EdgeInsets.only(top: 8), sliver: SliverArticlePanel()) // SliverAppBar( // title: Text(columnize.title), // ), ], ), ); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/view/article/columnize_page_view.dart ================================================ import 'dart:math'; import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import '../../bloc/article/bloc.dart'; import '../../bloc/exp.dart'; import '../../data/exp.dart'; import '../../data/repository/article_repository.dart'; import 'column_detail_page.dart'; DateFormat formatLong = DateFormat('yyyy-MM-dd HH:mm:ss'); const colors = [ Color(0xFF0829FB), Color(0xFFF60C0C), Color(0xFFE7F716), Color(0xFFF3B913), Color(0xFF3DF30B), Color(0xFFB709F4), Color(0xFF0DF6EF), ]; class ColumnizePageView extends StatefulWidget { const ColumnizePageView({Key? key}) : super(key: key); @override _ColumnizePageViewState createState() => _ColumnizePageViewState(); } class _ColumnizePageViewState extends State { final ValueNotifier factor = ValueNotifier(0); late PageController _ctrl; final int _firstOffset = 1000; //初始偏移 int _position = 0; //页面位置 @override void initState() { super.initState(); _position = _position + _firstOffset; double value = ((_position - _firstOffset + 1) % 5) / 5; factor.value = value == 0 ? 1 : value; _ctrl = PageController( viewportFraction: kAppEnv.isDesktopUI ? 0.5 : 0.9, initialPage: _position, )..addListener(() { if (_ctrl.page != null) { double value = (_ctrl.page! - _firstOffset + 1) % 5 / 5; factor.value = value == 0 ? 1 : value; } }); } @override void dispose() { _ctrl.dispose(); factor.dispose(); super.dispose(); } Color get color => Colors.blue; Color get nextColor => Colors.orangeAccent; bool get isDark => Theme.of(context).brightness == Brightness.dark; BoxDecoration get boxDecoration => BoxDecoration( color: isDark ? Colors.white.withAlpha(33) : Colors.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(40), topRight: Radius.circular(40)), ); @override Widget build(BuildContext context) { List data = context.watch().state.data; Widget child = PageView.builder( controller: _ctrl, // itemCount: 7, itemBuilder: (_, index) { return AnimatedBuilder( child: _buildByIndex(context, index, data), animation: _ctrl, builder: (context, child) => _buildAnimItemByIndex( context, child, index, ), ); }, onPageChanged: (index) { _position = index; }, ); if (!kIsDesk) { return child; } return MouseRegion( onEnter: _onEnter, onExit: _onExit, child: Stack( alignment: Alignment.center, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 48.0), child: child), Positioned( right: 0, child: IconButton( onPressed: () { _position += 1; _ctrl.animateToPage(_position, duration: Duration(milliseconds: 500), curve: Curves.easeIn); }, icon: Icon(Icons.navigate_next_outlined))), Positioned( left: 0, child: IconButton( onPressed: () { _position -= 1; _ctrl.animateToPage(_position, duration: Duration(milliseconds: 500), curve: Curves.easeIn); }, icon: Icon(Icons.navigate_before))), ], ), ); } Widget? _buildByIndex(BuildContext context, int index, List data) { int realIndex = _fixPosition(index, _firstOffset, data.length); return ColumnizeItem( columnize: data[realIndex], color: colors[realIndex % colors.length], ); } Widget _buildAnimItemByIndex(BuildContext context, Widget? child, int index) { double value; if (_ctrl.position.haveDimensions && _ctrl.page != null) { value = _ctrl.page! - index; } else { value = (_position - index).toDouble(); } value = (1 - ((value.abs()) * .3)).clamp(0, 1).toDouble(); value = Curves.easeOut.transform(value); return Transform( transform: Matrix4.diagonal3Values(1.0, value, 1.0), alignment: Alignment.center, child: Padding( padding: const EdgeInsets.all(6.0), child: child, ), ); } int _fixPosition(int realPos, int initPos, int length) { final int offset = realPos - initPos; int result = offset % length; return result < 0 ? length + result : result; } bool _hover = false; void _onEnter(PointerEnterEvent event) { setState(() { _hover = true; }); } void _onExit(PointerExitEvent event) { setState(() { _hover = false; }); } } class ColumnizeItem extends StatelessWidget { final Columnize columnize; final Color color; const ColumnizeItem({Key? key, required this.columnize, required this.color}) : super(key: key); @override Widget build(BuildContext context) { return GestureDetector( onTap: () { ArticleRepository repository = context.read().repository; Navigator.of(context).push(SlidePageRoute( child: MultiBlocProvider(providers: [ BlocProvider( create: (_) => ArticleBloc(repository, groupId: columnize.id, pageSize: 100) ..init(), ), ], child: ColumnDetailPage(columnize: columnize)))); }, child: Container( alignment: Alignment.topLeft, padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10), // margin: EdgeInsets.only(left: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( columnize.title, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), Text( '作者: ${columnize.username}', style: TextStyle( fontSize: 12, color: Color( 0xff6A6D76, )), ), ], ), Spacer(), Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), border: Border.all(color: Color(0xff3872E0))), child: Text( '${columnize.count} 篇', style: TextStyle( color: Color(0xff3872E0), fontSize: 12, height: 1.1), ), ) ], ), const SizedBox( height: 6, ), Text( '专栏简介: ${columnize.subtitle}', style: TextStyle( color: Color( 0xffA3A3A3, ), fontSize: 12), maxLines: 3, ), Spacer(), Row( children: [ Text( '更新时间: ${formatLong.format(DateTime.fromMillisecondsSinceEpoch(columnize.update, isUtc: true))}', style: TextStyle( color: Color( 0xff6A6D76, ), fontSize: 12), ), Spacer(), ], ), ], ), decoration: BoxDecoration( gradient: LinearGradient(transform: GradientRotation(3 * pi / 4), colors: [ color.withOpacity(0.1), color.withOpacity(0.08), color.withOpacity(0), // Theme.of(context).primaryColor.withAlpha(88) ]), // color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), ), ); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/view/article/sliver_article.dart ================================================ import 'dart:io'; import 'package:app/app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../bloc/exp.dart'; import '../../data/exp.dart'; import 'article_detail_page.dart'; import 'columnize_page_view.dart'; class SliverArticlePanel extends StatelessWidget { const SliverArticlePanel({Key? key}) : super(key: key); @override Widget build(BuildContext context) { ArticleState state = context.watch().state; return switch (state) { ArticleLoading() => const SliverToBoxAdapter( child: CupertinoActivityIndicator(), ), ArticleWithData() => SliverArticle( data: state.data, ), ArticleFailed() => const SliverToBoxAdapter( child: Text('error'), ), }; } } class SliverArticle extends StatelessWidget { final List
data; const SliverArticle({Key? key, required this.data}) : super(key: key); @override Widget build(BuildContext context) { const SliverGridDelegate deskGridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 420, mainAxisSpacing: 8, mainAxisExtent: 188, crossAxisSpacing: 8, ); Widget child = kIsDesk ? SliverGrid( delegate: SliverChildBuilderDelegate( _buildItem, childCount: data.length, ), gridDelegate: deskGridDelegate) : SliverList( delegate: SliverChildBuilderDelegate( _buildItem, childCount: data.length, )); return SliverPadding( padding: const EdgeInsets.only(bottom: 0), sliver: child); } Widget? _buildItem(BuildContext context, int index) { return ArticlePanel(article: data[index]); } } class ArticlePanel extends StatelessWidget { final Article article; const ArticlePanel({Key? key, required this.article}) : super(key: key); void toArticleDetail(BuildContext context) { if (Platform.isAndroid || Platform.isIOS) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => ArticleDetailPage(article: article), ), ); } else { _launchURL('https://juejin.cn${article.url}'); } } void _launchURL(String url) async { print(url); Uri uri = Uri.parse(url); if (await canLaunchUrl(Uri.parse(url))) { await launchUrl(uri, mode: LaunchMode.externalApplication); } else { debugPrint('Could not launch $url'); } } @override Widget build(BuildContext context) { return GestureDetector( onTap: () => toArticleDetail(context), child: Container( color: Theme.of(context).listTileTheme.tileColor, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), margin: const EdgeInsets.only(bottom: 6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const CircleAvatar( backgroundImage: AssetImage( 'assets/images/icon_head.webp', ), backgroundColor: Colors.transparent, radius: 10, ), const SizedBox( width: 6, ), Expanded( child: Text('${article.username}', style: const TextStyle( color: Color(0xff6A6D76), fontSize: 12))), const Text( '掘金', style: TextStyle(fontSize: 12, color: Color(0xff6A6D76)), ), ], ), Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Text( article.title, maxLines: 2, style: const TextStyle(fontWeight: FontWeight.bold), ), ), Row( children: [ Expanded( child: Text( '${article.subtitle}', maxLines: 4, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 12, color: Color( 0xffA3A3A3, )), )), if (article.cover != null && article.cover!.isNotEmpty) Padding( padding: const EdgeInsets.only(left: 8.0), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( article.cover!, width: 110, )), ) ], ), const SizedBox( height: 4, ), Row( children: [ const Spacer(), Text( '更新时间: ${formatLong.format(DateTime.fromMillisecondsSinceEpoch(article.update, isUtc: true))}', style: const TextStyle( color: Color( 0xff6A6D76, ), fontSize: 12), ), ], ), ], ), ), ); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/view/article/sliver_columnize.dart ================================================ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'columnize_page_view.dart'; import 'package:l10n/l10n.dart'; class ColumnizeViewPage extends StatefulWidget { const ColumnizeViewPage({Key? key}) : super(key: key); @override State createState() => _ColumnizeViewPageState(); } class _ColumnizeViewPageState extends State { late PageController _ctrl; @override void initState() { super.initState(); _ctrl = PageController(viewportFraction: 0.9); } @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; return Container( height: 220, color: Theme.of(context).listTileTheme.tileColor, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(left: 16.0,right: 16,top: 12,bottom: 4), child: Row( children: [ CircleAvatar( backgroundImage: AssetImage('assets/images/icon_head.webp',), backgroundColor: Colors.transparent, radius: 10, ), SizedBox(width: 6,), Text("捷特文章专栏",style: TextStyle(fontSize: 16,fontWeight: FontWeight.bold),), Spacer(), GestureDetector( onTap: () async{ Uri uri = Uri.parse('https://juejin.im/user/5b42c0656fb9a04fe727eb37'); if (await canLaunchUrl(uri)) { await launchUrl(uri,mode:LaunchMode.externalNonBrowserApplication ); } else { debugPrint('Could not launch ${uri.path}'); } }, child: Wrap( crossAxisAlignment: WrapCrossAlignment.center, children: [ Text( context.l10n.knowledgeToJuejin ,style: TextStyle(fontSize: 12,color: Colors.blue),), Icon(Icons.navigate_next,size: 12,color: Colors.blue,) ], ), ), ], ), ), Expanded( child: ColumnizePageView(), ), SizedBox(height: 10,) ], ), ); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/view/article/toly_article_scroll_page.dart ================================================ import 'package:artifact/artifact.dart'; import 'package:components/components.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tolyui_refresh/tolyui_refresh.dart'; import '../../bloc/exp.dart'; import 'sliver_article.dart'; import 'sliver_columnize.dart'; class TolyArticleScrollPage extends StatefulWidget { const TolyArticleScrollPage({Key? key}) : super(key: key); @override State createState() => _TolyArticleScrollPageState(); } class _TolyArticleScrollPageState extends State { final RefreshController _refreshController = RefreshController(initialRefresh: false); int maxCount = 60; @override void dispose() { _refreshController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return RefreshConfigWrapper( child: TolyRefresh( enablePullUp: true, onRefresh: _onRefresh, onLoading: _loadMore, controller: _refreshController, child: CustomScrollView( slivers: [ // SliverOverlapInjector( // handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), // ), SliverPadding( padding: EdgeInsets.only(top: 10, bottom: 10), sliver: const SliverToBoxAdapter( child: ColumnizeViewPage(), ), ), SliverArticlePanel(), ], ) // ListView.builder( // padding: EdgeInsets.only(top: 10), // itemCount: data.length, // itemBuilder: _buildItem, // ), ), ); } void _onRefresh() async { // monitor network fetch await Future.delayed(Duration(milliseconds: 500)); _refreshController.refreshCompleted(); } void _loadMore() async { ArticleBloc bloc = context.read(); await context.read().loadNextPageMore(); // int length = data.length; ArticleState state = bloc.state; if (state is ArticleWithData) { if (state.data.length >= state.total) { _refreshController.loadNoData(); await Future.delayed(Duration(milliseconds: 2000)); _refreshController.resetNoData(); return; } _refreshController.loadComplete(); } } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/view/artifact_page.dart ================================================ import 'dart:math'; import 'package:algorithm/algorithm.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:l10n/l10n.dart'; import 'package:url_launcher/url_launcher.dart'; import '../bloc/columnize/bloc.dart'; import '../bloc/exp.dart'; import '../data/exp.dart'; import '../data/repository/article_repository.dart'; import 'article/toly_article_scroll_page.dart'; import 'dart:ui' as ui; import 'building/building_panel.dart'; class ArtifactPage extends StatefulWidget { const ArtifactPage({Key? key}) : super(key: key); @override State createState() => _ArtifactPageState(); } // 任意处下滑可出现 AppBar // |--- NestedScrollView#floatHeaderSlivers: true // |--- SliverAppBar#floating: true // 任意处下滑可出现 AppBar + 轻滑出现 // |--- NestedScrollView#floatHeaderSlivers: true // |--- SliverAppBar#floating: true // |--- SliverAppBar#snap: true const List kArtifactInfo = [ '分类收录张风捷特烈的博客文章', '可视化排序算法', '收录布局方案,提供界面样板', 'Flutter 知识小要点,一网打尽', ]; class _ArtifactPageState extends State with SingleTickerProviderStateMixin { late TabController controller; List data = []; @override void initState() { super.initState(); controller = TabController(length: 2, vsync: this); controller.addListener(_listen); data = List.generate(5, (index) => 'Init $index'); } int _curIndex = 0; @override void dispose() { controller.dispose(); super.dispose(); } ArticleRepository aRepository = const ArticleRepository(); ColumnizeRepository cRepository = const ColumnizeRepository(); @override Widget build(BuildContext context) { double bottom = MediaQuery.of(context).padding.bottom; String name = SortStateScope.of(context).config.name; return MultiBlocProvider( providers: [ BlocProvider( create: (_) => ColumnizeBloc(cRepository)..init()), BlocProvider( create: (_) => ArticleBloc(aRepository)..init()), ], child: Scaffold( endDrawer: SortSettings(), backgroundColor: const Color(0xffF2F3F5), bottomNavigationBar: Container(height: bottom), body: NestedScrollView( headerSliverBuilder: _buildAppBar, floatHeaderSlivers: true, body: TabBarView( controller: controller, children: [ TolyArticleScrollPage(), Column( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ GestureDetector( onTap: () { _launchURL( 'https://github.com/toly1994328/FlutterUnit/blob/master/packages/algorithm/lib/src/algorithm/sort/functions/${name}.dart'); }, child: Text( '查看排序源码', style: TextStyle( fontSize: 12, color: Theme.of(context).primaryColor, ), )), Spacer(), SortSelector(), ], ), ), Expanded(child: SortPaper()), ], ), // BuildingPanel(), // BuildingPanel(), ], ), ), ), ); } _launchURL(String url) async { Uri uri = Uri.parse(url); if (await canLaunchUrl(Uri.parse(url))) { await launchUrl(uri, mode: LaunchMode.externalApplication); } else { debugPrint('Could not launch $url'); } } List _buildAppBar(BuildContext context, bool innerBoxIsScrolled) { // print('innerBoxIsScrolled:$innerBoxIsScrolled'); return [ // SliverOverlapAbsorber( // handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), // // sliver: SliverAppBar( floating: true, snap: true, pinned: true, backgroundColor: Colors.white, leading: _curIndex == 1 ? SortButton() : null, // flexibleSpace: Image.network( // 'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/448d4eb270f44edab0192a1281141954~tplv-k3u1fbpfcp-watermark.image?', // fit: BoxFit.cover, // ), // expandedHeight: 120, flexibleSpace: Container( // height: 240, decoration: BoxDecoration( gradient: LinearGradient(colors: [ Color(0xffD3D5F5), Color(0xffC8EBFA), ], transform: GradientRotation(pi / 4))), ), // flexibleSpace: Doodle(), // leading: Center( // child: Text( // '宝具库', // style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), // )), // actions: [Icon(Icons.cached_outlined)], titleSpacing: 0, // leadingWidth: 40, title: Column( children: [ Text( 'Flutter 知识宝库', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), const SizedBox( height: 4, ), Text( kArtifactInfo[_curIndex], maxLines: 2, style: TextStyle( fontSize: 10, fontWeight: FontWeight.normal, color: Colors.purple), ), ], ), actions: [ if (_curIndex == 1) IconButton( onPressed: () { Scaffold.of(context).openEndDrawer(); }, icon: Icon(Icons.settings)), if (_curIndex != 1) IconButton(onPressed: () {}, icon: Icon(Icons.search_rounded)) ], // title: Padding( // padding: const EdgeInsets.only(right: 8.0), // child: CupertinoTextField(), // ), bottom: TabBar( controller: controller, tabs: [ Tab( // icon: Icon(Icons.account_balance_wallet_outlined), text: context.l10n.knowledgeTabToly, ), Tab( // icon: Icon(Icons.account_balance_wallet_outlined), text: context.l10n.knowledgeTabAlgo, ), // Tab( // // icon: Icon(Icons.account_balance_wallet_outlined), // text:context.l10n.knowledgeTabLayout, // ), // Tab( // // icon: Icon(Icons.account_balance_wallet_outlined), // text:context.l10n.knowledgeTabPoint, // ), ], ), ), // ) ]; } Widget? _buildItem(BuildContext context, int index) { return ListTile( tileColor: Colors.white, title: Text(data[index]), ); } void _listen() { print('${controller.index}'); if (_curIndex != controller.index) { setState(() { _curIndex = controller.index; }); } } } class Doodle extends StatelessWidget { const Doodle({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return RepaintBoundary( child: CustomPaint( painter: DoodlePainter(), child: const Center(), ), ); } } class DoodlePainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { Paint pen = Paint(); pen.shader = ui.Gradient.linear( Offset.zero, Offset(size.width, size.height), [const Color(0xffD3D5F5), const Color(0xffC8EBFA)], [0, 1], TileMode.mirror, Matrix4.rotationZ(pi / 4).storage); canvas.drawRect(Offset.zero & size, pen); // print(size); canvas.drawCircle( Offset(size.width / 2, size.height / 2), size.height / 6, Paint() ..color = Colors.grey ..style = PaintingStyle.stroke); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/view/building/building_panel.dart ================================================ import 'package:flutter/material.dart'; import 'package:l10n/l10n.dart'; class BuildingPanel extends StatelessWidget { const BuildingPanel({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add_chart,size: 36,color: Colors.grey,), const SizedBox(height: 8,), Text( context.l10n.knowledgeConstruction ,style: TextStyle(color: Colors.grey),), ], ), ); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/articles/view/desk_artifact_page.dart ================================================ import 'package:algorithm/algorithm.dart'; import 'package:artifact/artifact.dart'; import 'package:components/components.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:layout/layout.dart'; import 'package:url_launcher/url_launcher.dart'; import '../bloc/exp.dart'; import '../data/exp.dart'; import 'article/sliver_article.dart'; import 'article/sliver_columnize.dart'; import 'building/building_panel.dart'; import 'package:l10n/l10n.dart'; class DeskKnowledgePage extends StatefulWidget { const DeskKnowledgePage({super.key}); @override State createState() => _DeskKnowledgePageState(); } class _DeskKnowledgePageState extends State with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { late TabController controller; List data = []; int _curIndex = 0; @override void initState() { super.initState(); controller = TabController(length: 3, vsync: this); controller.addListener(_listen); data = List.generate(5, (index) => 'Init $index'); } ArticleRepository aRepository = const ArticleRepository(); ColumnizeRepository cRepository = const ColumnizeRepository(); @override Widget build(BuildContext context) { super.build(context); AppLocalizations l10n = context.l10n; return MultiBlocProvider( providers: [ BlocProvider( create: (_) => ColumnizeBloc(cRepository)..init()), BlocProvider( create: (_) => ArticleBloc(aRepository, pageSize: 1000)..init()), ], child: Scaffold( endDrawer: SortSettings(), body: Column( children: [ DeskKnowledgeTabTopBar( onTabPressed: (int value) { controller.index = value; }, tabs: [ l10n.knowledgeTabLayout, l10n.knowledgeTabAlgo, l10n.knowledgeTabToly, ], ), Expanded( child: TabBarView( controller: controller, children: [ LayoutRouterPage(), AlgoRouterPage(), TolyArticlesPage(), // DeskPointPage(), ], )) ], ), ), ); } void _listen() { print('${controller.index}'); if (_curIndex != controller.index) { setState(() { _curIndex = controller.index; }); } } @override bool get wantKeepAlive => true; } class TolyArticlesPage extends StatelessWidget { const TolyArticlesPage({super.key}); @override Widget build(BuildContext context) { return CustomScrollView( slivers: [ SliverPadding( padding: EdgeInsets.only(top: 10, bottom: 10, right: 36, left: 36), sliver: const SliverToBoxAdapter( child: ColumnizeViewPage(), ), ), SliverPadding( padding: EdgeInsets.only(right: 36, left: 36), sliver: SliverArticlePanel(), ), ], ); } } class SoreAlgoPage extends StatelessWidget { const SoreAlgoPage({super.key}); @override Widget build(BuildContext context) { String name = SortStateScope.of(context).config.name; return Column( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ GestureDetector( onTap: () { _launchURL( 'https://github.com/toly1994328/FlutterUnit/blob/master/packages/algorithm/lib/src/algorithm/sort/functions/${name}.dart'); }, child: Text( '查看排序源码', style: TextStyle( fontSize: 12, color: Theme.of(context).primaryColor, ), )), Spacer(), SortButton(), const SizedBox( width: 12, ), SortSelector(), const SizedBox( width: 12, ), GestureDetector( onTap: () { Scaffold.of(context).openEndDrawer(); }, child: Icon(Icons.settings)) ], ), ), Expanded(child: SortPaper()), ], ); } void _launchURL(String url) async { Uri uri = Uri.parse(url); if (await canLaunchUrl(Uri.parse(url))) { await launchUrl(uri, mode: LaunchMode.externalApplication); } else { debugPrint('Could not launch $url'); } } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/bloc/bloc.dart ================================================ export 'point_comment_bloc.dart'; export 'point_bloc.dart'; ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/bloc/point_bloc.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import '../repository/api/point_api.dart'; import 'package:equatable/equatable.dart'; import '../data/model/issue.dart'; /// create by 张风捷特烈 on 2020-09-03 /// contact me by email 1981462002@qq.com /// 说明: class PointBloc extends Cubit { final PointApi api; PointBloc(this.api) : super(const PointLoading()); void loadPoint() async { emit(const PointLoading()); ApiRet> ret = await api.getIssues(); if(ret.failed){ emit(PointLoadFailure(ret.msg)); } emit(PointLoaded(ret.data)); } } sealed class PointState extends Equatable { const PointState(); } class PointLoading extends PointState { const PointLoading(); @override List get props => []; } class PointLoaded extends PointState { final List issues; const PointLoaded(this.issues); @override List get props => [issues]; } class PointLoadFailure extends PointState { final String error; const PointLoadFailure(this.error); @override List get props => [error]; } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/bloc/point_comment_bloc.dart ================================================ import 'package:artifact/src/points/repository/api/point_api.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../data/model/github_model.dart'; import 'package:equatable/equatable.dart'; /// create by 张风捷特烈 on 2020-09-03 /// contact me by email 1981462002@qq.com /// 说明: class PointCommentBloc extends Cubit { final PointApi api = PointApiImpl(); PointCommentBloc() : super(const PointCommentInitial()); void loadPointComment(Issue point) async { emit(PointCommentLoading(point)); if (point.number == null) { emit(const PointCommentLoadFailure('point_bloc id 为空')); return; } ApiRet> ret = await api.getIssuesComment(point.number!); if (ret.failed) { emit(PointCommentLoadFailure(ret.msg)); return; } final List comments = ret.data; comments.sort((a, b) => a.createdAt!.compareTo(b.createdAt!)); emit(PointCommentLoaded(point, comments)); } } sealed class PointCommentState extends Equatable { const PointCommentState(); } class PointCommentInitial extends PointCommentState { const PointCommentInitial(); @override List get props => []; } class PointCommentLoading extends PointCommentState { final Issue issue; const PointCommentLoading(this.issue); @override List get props => [issue]; } class PointCommentLoaded extends PointCommentState { final Issue issue; final List comments; const PointCommentLoaded(this.issue, this.comments); @override List get props => [issue, comments]; @override String toString() { return 'PointCommentLoaded{issue: $issue, comments: $comments}'; } } class PointCommentLoadFailure extends PointCommentState { final String error; const PointCommentLoadFailure(this.error); @override List get props => [error]; } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/data/api/app_info.dart ================================================ import 'package:app/app.dart'; import 'package:equatable/equatable.dart'; import 'package:utils/utils.dart'; class AppInfoApi { static Future> getAppVersion({required String appName}) async { String errorMsg = ""; var result = await HttpUtil.instance .client .get(PathUnit.appInfo+"/$appName") .catchError((err) { errorMsg = err.toString(); }); // 获取的数据非空且 status = true if (result.data != null && result.data['status']) { // 说明有数据 if (result.data['data'] != null) { return TaskResult.success( data: AppInfo( appName: result.data['data']['appName'], appVersion: result.data['data']['appVersion'], appUrl: result.data['data']['appUrl'], appSize: result.data['data']['appSize'], )); } else { return const TaskResult.success(data: null); } } return TaskResult.error(msg: '请求错误: $errorMsg'); } } class AppInfo extends Equatable{ final String appName; final String appVersion; final String appUrl; final int appSize; const AppInfo({ required this.appName, required this.appVersion, required this.appUrl, required this.appSize, }); @override List get props => [appName,appVersion,appUrl,appSize]; @override String toString() { return 'AppInfo{appName: $appName, appVersion: $appVersion, appUrl: $appUrl, appSize: $appSize}'; } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/data/api/category_api.dart ================================================ import 'package:app/app.dart'; import 'package:utils/utils.dart'; /// create by 张风捷特烈 on 2021/2/24 /// contact me by email 1981462002@qq.com /// 说明: class CategoryApi { static Future> uploadCategoryData( {required String data, required String likeData}) async { String errorMsg = ""; try { var result = await HttpUtil.instance.client.post( PathUnit.categoryDataSync, data: {"data": data, "likeData": likeData}); print(result.data); if (result.data != null) { return TaskResult.success(data:result.data['status']); } } catch (e) { print(e); errorMsg = e.toString(); } return TaskResult.error(msg: '请求错误: $errorMsg'); } static Future> getCategoryData() async { String errorMsg = ""; var result = await HttpUtil.instance .client .get(PathUnit.categoryData) .catchError((err) { errorMsg =err.toString(); }); // 获取的数据非空且 status = true if (result.data != null && result.data['status']) { // 说明有数据 if (result.data['data'] != null) { return TaskResult.success(data:CategoryData.fromJson(result.data['data'])); } else { return const TaskResult.success(data:null); } } return TaskResult.error(msg: '请求错误: $errorMsg'); } } class CategoryData{ final int categoryDataId; final int userId; final String data; final String likeData; CategoryData( {required this.categoryDataId, required this.userId, required this.data, required this.likeData}); factory CategoryData.fromJson(Map map) { return CategoryData( categoryDataId: map['categoryDataId'], userId: map["userId"], likeData: map["likeData"], data: map["data"]); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/data/api/issues_api.dart ================================================ import 'dart:convert'; import 'package:dio/dio.dart'; import '../model/github_model.dart'; import '../model/issue.dart'; import '../model/repository.dart'; /// create by 张风捷特烈 on 2020/6/17 /// contact me by email 1981462002@qq.com /// 说明: ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/data/model/github_model.dart ================================================ export './github_user.dart'; export './issue.dart'; export './issue_comment.dart'; export './license.dart'; export './repository.dart'; export './repository_permissions.dart'; ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/data/model/github_user.dart ================================================ /// create by 张风捷特烈 on 2020/6/17 /// contact me by email 1981462002@qq.com /// 说明: class GithubUser { GithubUser( this.login, this.id, this.nodeId, this.avatarUrl, this.gravatarId, this.url, this.htmlUrl, this.followersUrl, this.followingUrl, this.gistsUrl, this.starredUrl, this.subscriptionsUrl, this.organizationsUrl, this.reposUrl, this.eventsUrl, this.receivedEventsUrl, this.type, this.siteAdmin, this.name, this.company, this.blog, this.location, this.email, this.starred, this.bio, this.publicRepos, this.publicGists, this.followers, this.following, this.createdAt, this.updatedAt, this.privateGists, this.totalPrivateRepos, this.ownedPrivateRepos, this.diskUsage, this.collaborators, this.twoFactorAuthentication); String? login; int? id; String? nodeId; String? avatarUrl; String? gravatarId; String? url; String? htmlUrl; String? followersUrl; String? followingUrl; String? gistsUrl; String? starredUrl; String? subscriptionsUrl; String? organizationsUrl; String? reposUrl; String? eventsUrl; String? receivedEventsUrl; String? type; bool? siteAdmin; String? name; String? company; String? blog; String? location; String? email; String? starred; String? bio; int? publicRepos; int? publicGists; int? followers; int? following; DateTime? createdAt; DateTime? updatedAt; int? privateGists; int? totalPrivateRepos; int? ownedPrivateRepos; int? diskUsage; int? collaborators; bool? twoFactorAuthentication; factory GithubUser.fromJson(Map json) => GithubUser( json['login'] as String?, json['id'] as int?, json['node_id'] as String?, json['avatar_url'] as String?, json['gravatar_id'] as String?, json['url'] as String?, json['html_url'] as String?, json['followers_url'] as String?, json['following_url'] as String?, json['gists_url'] as String?, json['starred_url'] as String?, json['subscriptions_url'] as String?, json['organizations_url'] as String?, json['repos_url'] as String?, json['events_url'] as String?, json['received_events_url'] as String?, json['type'] as String?, json['site_admin'] as bool?, json['name'] as String?, json['company'] as String?, json['blog'] as String?, json['location'] as String?, json['email'] as String?, json['starred'] as String?, json['bio'] as String?, json['public_repos'] as int?, json['public_gists'] as int?, json['followers'] as int?, json['following'] as int?, json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), json['private_gists'] as int?, json['total_private_repos'] as int?, json['owned_private_repos'] as int?, json['disk_usage'] as int?, json['collaborators'] as int?, json['two_factor_authentication'] as bool?, ); Map toJson() => { 'login': login, 'id': id, 'node_id': nodeId, 'avatar_url': avatarUrl, 'gravatar_id': gravatarId, 'url': url, 'html_url': htmlUrl, 'followers_url': followersUrl, 'following_url': followingUrl, 'gists_url': gistsUrl, 'starred_url': starredUrl, 'subscriptions_url': subscriptionsUrl, 'organizations_url': organizationsUrl, 'repos_url': reposUrl, 'events_url': eventsUrl, 'received_events_url': receivedEventsUrl, 'type': type, 'site_admin': siteAdmin, 'name': name, 'company': company, 'blog': blog, 'location': location, 'email': email, 'starred': starred, 'bio': bio, 'public_repos': publicRepos, 'public_gists': publicGists, 'followers': followers, 'following': following, 'created_at': createdAt?.toIso8601String(), 'updated_at': updatedAt?.toIso8601String(), 'private_gists': privateGists, 'total_private_repos': totalPrivateRepos, 'owned_private_repos': ownedPrivateRepos, 'disk_usage': diskUsage, 'collaborators': collaborators, 'two_factor_authentication': twoFactorAuthentication, }; // 命名构造函数 GithubUser.empty(); } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/data/model/issue.dart ================================================ import 'package:equatable/equatable.dart'; import 'github_user.dart'; class Issue extends Equatable { final int? id; final int? number; final String? title; final String? state; final bool? locked; final int? commentNum; final DateTime? createdAt; final DateTime? updatedAt; final DateTime? closedAt; final String? body; final String? bodyHtml; final GithubUser? user; final String? repoUrl; final String? htmlUrl; final GithubUser? closeBy; const Issue( this.id, this.number, this.title, this.state, this.locked, this.commentNum, this.createdAt, this.updatedAt, this.closedAt, this.body, this.bodyHtml, this.user, this.repoUrl, this.htmlUrl, this.closeBy, ); static Issue fromJson(dynamic json) => Issue( json['id'] as int?, json['number'] as int?, json['title'] as String?, json['state'] as String?, json['locked'] as bool?, json['comments'] as int?, json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), json['closed_at'] == null ? null : DateTime.parse(json['closed_at'] as String), json['body'] as String?, json['body_html'] as String?, json['user'] == null ? null : GithubUser.fromJson(json['user'] as Map), json['repository_url'] as String?, json['html_url'] as String?, json['closed_by'] == null ? null : GithubUser.fromJson(json['closed_by'] as Map), ); Map toJson() => { 'id': id, 'number': number, 'title': title, 'state': state, 'locked': locked, 'comments': commentNum, 'created_at': createdAt?.toIso8601String(), 'updated_at': updatedAt?.toIso8601String(), 'closed_at': closedAt?.toIso8601String(), 'body': body, 'body_html': bodyHtml, 'user': user, 'repository_url': repoUrl, 'html_url': htmlUrl, 'closed_by': closeBy, }; @override String toString() { return 'Issue{id: $id, number: $number, title: $title, state: $state, locked: $locked, commentNum: $commentNum, createdAt: $createdAt, updatedAt: $updatedAt, closedAt: $closedAt, body: $body, bodyHtml: $bodyHtml, user: $user, repoUrl: $repoUrl, htmlUrl: $htmlUrl, closeBy: $closeBy}'; } @override List get props => [ id, number, title, state, locked, commentNum, createdAt, updatedAt, closedAt, body, bodyHtml, user, repoUrl, htmlUrl, closeBy, ]; } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/data/model/issue_comment.dart ================================================ import 'github_user.dart'; class IssueComment{ int? id; GithubUser? user; DateTime? createdAt; DateTime? updatedAt; String? authorAssociation; String? body; String? bodyHtml; String? type; String? htmlUrl; IssueComment( this.id, this.user, this.createdAt, this.updatedAt, this.authorAssociation, this.body, this.bodyHtml, this.type, this.htmlUrl, ); factory IssueComment.fromJson(Map json) => IssueComment( json['id'] as int?, json['user'] == null ? null : GithubUser.fromJson(json['user'] as Map), json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), json['author_association'] as String?, json['body'] as String?, json['body_html'] as String?, json['event'] as String?, json['html_url'] as String?, ); Map toJson() => { 'id': id, 'user': user, 'created_at': createdAt?.toIso8601String(), 'updated_at': updatedAt?.toIso8601String(), 'author_association': authorAssociation, 'body': body, 'body_html': bodyHtml, 'event': type, 'html_url': htmlUrl, }; } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/data/model/license.dart ================================================ class License { String? name; String? spdxId; License(this.name,this.spdxId); String get type { return spdxId ==null?"未知":spdxId!; } factory License.fromJson(Map json) => License( json['name'] as String?, json['spdx_id'] as String?, ); Map toJson() => { 'name': name, 'spdx_id': spdxId, }; } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/data/model/repository.dart ================================================ import 'github_user.dart'; import 'license.dart'; import 'repository_permissions.dart'; class Repository { int? id; int? size; String? name; String? fullName; String? htmlUrl; String? description; String? language; String? defaultBranch; DateTime? createdAt; DateTime? updatedAt; DateTime? pushedAt; String? gitUrl; String? sshUrl; String? cloneUrl; String? svnUrl; int? stargazersCount; int? watchersCount; int? forksCount; int? openIssuesCount; int? subscribersCount; bool? private; bool? fork; bool? hasIssues; bool? hasProjects; bool? hasDownloads; bool? hasWiki; bool? hasPages; GithubUser? owner; License? license; Repository? parent; RepositoryPermissions? permissions; List? topics; int? allIssueCount; Repository( this.id, this.size, this.name, this.fullName, this.htmlUrl, this.description, this.language, this.license, this.defaultBranch, this.createdAt, this.updatedAt, this.pushedAt, this.gitUrl, this.sshUrl, this.cloneUrl, this.svnUrl, this.stargazersCount, this.watchersCount, this.forksCount, this.openIssuesCount, this.subscribersCount, this.private, this.fork, this.hasIssues, this.hasProjects, this.hasDownloads, this.hasWiki, this.hasPages, this.owner, this.parent, this.permissions, this.topics, ); factory Repository.fromJson(Map json) => Repository( json['id'] as int?, json['size'] as int?, json['name'] as String?, json['full_name'] as String?, json['html_url'] as String?, json['description'] as String?, json['language'] as String?, json['license'] == null ? null : License.fromJson(json['license'] as Map), json['default_branch'] as String?, json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), json['pushed_at'] == null ? null : DateTime.parse(json['pushed_at'] as String), json['git_url'] as String?, json['ssh_url'] as String?, json['clone_url'] as String?, json['svn_url'] as String?, json['stargazers_count'] as int?, json['watchers_count'] as int?, json['forks_count'] as int?, json['open_issues_count'] as int?, json['subscribers_count'] as int?, json['private'] as bool?, json['fork'] as bool?, json['has_issues'] as bool?, json['has_projects'] as bool?, json['has_downloads'] as bool?, json['has_wiki'] as bool?, json['has_pages'] as bool?, json['owner'] == null ? null : GithubUser.fromJson(json['owner'] as Map), json['parent'] == null ? null : Repository.fromJson(json['parent'] as Map), json['permissions'] == null ? null : RepositoryPermissions.fromJson( json['permissions'] as Map), (json['topics'] as List?)?.map((e) => e as String).toList(), )..allIssueCount = json['allIssueCount'] as int?; @override String toString() { return 'Repository{id: $id, size: $size, name: $name, fullName: $fullName, htmlUrl: $htmlUrl, description: $description, language: $language, defaultBranch: $defaultBranch, createdAt: $createdAt, updatedAt: $updatedAt, pushedAt: $pushedAt, gitUrl: $gitUrl, sshUrl: $sshUrl, cloneUrl: $cloneUrl, svnUrl: $svnUrl, stargazersCount: $stargazersCount, watchersCount: $watchersCount, forksCount: $forksCount, openIssuesCount: $openIssuesCount, subscribersCount: $subscribersCount, private: $private, fork: $fork, hasIssues: $hasIssues, hasProjects: $hasProjects, hasDownloads: $hasDownloads, hasWiki: $hasWiki, hasPages: $hasPages, owner: $owner, license: $license, parent: $parent, permissions: $permissions, topics: $topics, allIssueCount: $allIssueCount}'; } Map toJson() => { 'id': id, 'size': size, 'name': name, 'full_name': fullName, 'html_url': htmlUrl, 'description': description, 'language': language, 'default_branch': defaultBranch, 'created_at': createdAt?.toIso8601String(), 'updated_at': updatedAt?.toIso8601String(), 'pushed_at': pushedAt?.toIso8601String(), 'git_url': gitUrl, 'ssh_url': sshUrl, 'clone_url': cloneUrl, 'svn_url': svnUrl, 'stargazers_count': stargazersCount, 'watchers_count': watchersCount, 'forks_count': forksCount, 'open_issues_count': openIssuesCount, 'subscribers_count': subscribersCount, 'private': private, 'fork': fork, 'has_issues': hasIssues, 'has_projects': hasProjects, 'has_downloads': hasDownloads, 'has_wiki': hasWiki, 'has_pages': hasPages, 'owner': owner, 'license': license, 'parent': parent, 'permissions': permissions, 'topics': topics, 'allIssueCount': allIssueCount, }; Repository.empty(); } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/data/model/repository_permissions.dart ================================================ class RepositoryPermissions { bool? admin; bool? push; bool? pull; RepositoryPermissions( this.admin, this.push, this.pull, ); factory RepositoryPermissions.fromJson(Map json) => RepositoryPermissions( json['admin'] as bool?, json['push'] as bool?, json['pull'] as bool?, ); Map toJson() => { 'admin': admin, 'push': push, 'pull': pull, }; } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/exp.dart ================================================ export 'data/api/category_api.dart'; export 'view/desk_ui/desk_point_page.dart'; export 'view/desk_ui/github_repo_panel.dart'; export 'view/issues_point/issues_point_page.dart'; export 'view/issues_point/issues_detail.dart'; export 'bloc/bloc.dart'; ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/repository/api/point_api.dart ================================================ import 'dart:convert'; import '../../data/model/issue.dart'; import '../../data/model/issue_comment.dart'; import '../../data/model/repository.dart'; import 'package:fx_dio/fx_dio.dart'; import 'package:app/app.dart'; export 'package:fx_dio/fx_dio.dart' show ApiRet; abstract interface class PointApi { /// 获取 github 中 FlutterUnit 仓库信息 Future> getFlutterUnitRepo(); /// 获取 github 中 FlutterUnit 仓库 issues 列表 Future>> getIssues(); Future>> getIssuesComment(int pointId); } class PointApiImpl implements PointApi { Host get unit => FxDio()(); @override Future> getFlutterUnitRepo() async { return unit.get(UnitApi.repository.path, convertor: (data) { dynamic repoStr = data['data']['repositoryData']; return Repository.fromJson(json.decode(repoStr)); }); } @override Future>> getIssues( {int page = 1, int pageSize = 100}) async { return unit.get>( UnitApi.point.path, queryParameters: { "page": page, "pageSize": pageSize, }, convertor: (data) => data['data'] .map((e) => Issue.fromJson(json.decode(e['pointData']))) .toList(), ); } @override Future>> getIssuesComment(int pointId) async { return unit.get>("${UnitApi.pointComment.path}$pointId", convertor: (data) => data['data'] .map((e) => IssueComment.fromJson(json.decode(e['pointCommentData']))) .toList()); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/view/desk_ui/desk_point_page.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../bloc/point_bloc.dart'; import '../../bloc/bloc.dart'; import '../../data/model/repository.dart'; import '../../repository/api/point_api.dart'; import '../issues_point/issues_point_page.dart'; import 'github_repo_panel.dart'; import 'package:fx_dio/fx_dio.dart'; class DeskPointPage extends StatefulWidget { const DeskPointPage({Key? key}) : super(key: key); @override State createState() => _DeskPointPageState(); } class _DeskPointPageState extends State { Repository _repository = Repository.fromJson({ 'full_name': 'toly1994328/FlutterUnit', 'license': {"spdx_id": 'GPL-3.0'}, 'description': '【Flutter 集录指南 App】The unity of flutter, The unity of coder.', 'stargazers_count': 7958, 'forks_count': 1039, 'subscribers_count': 126, 'open_issues_count': 40, }); final PointApi _api = PointApiImpl(); @override void initState() { super.initState(); _loadRepo(); } void _loadRepo() async { final ApiRet ret = await _api.getFlutterUnitRepo(); if (ret.success) { setState(() { _repository = ret.data; }); } } @override Widget build(BuildContext context) { return BlocProvider( create: (_) => PointBloc(_api)..loadPoint(), child: Scaffold( body: Column( children: [ Expanded( child: Row( children: [ Column( children: [ GithubRepoPanel( repository: _repository, ), Expanded(child: SizedBox(width: 250, child: IssuesTip())) ], ), VerticalDivider( width: 1, ), Expanded(flex: 2, child: IssuesPointContent()), ], )) ], ), ), ); } } class IssuesTip extends StatelessWidget { const IssuesTip({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Text.rich( TextSpan(children: [ TextSpan( text: '* 注: ', style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)), TextSpan( text: '要点集录中的 QA 数据收录在 FlutterUnit 以 point 为标签的 issues 中。如果需要提供数据,在 issues 中问答即可。'), TextSpan( text: '点击这里跳转', mouseCursor: SystemMouseCursors.click, recognizer: TapGestureRecognizer()..onTap = _toUrl, style: TextStyle( color: Colors.blue, decoration: TextDecoration.underline, fontWeight: FontWeight.bold)), ]), style: TextStyle(fontSize: 14), ), ); } void _toUrl() async { String url = 'https://github.com/toly1994328/FlutterUnit/issues?q=label%3Apoint+'; if (!await launchUrl(Uri.parse(url))) { throw Exception('Could not launch $url'); } } } class SimpleDeskTopBar extends StatelessWidget { final Widget? leading; final Widget? tail; final double height; const SimpleDeskTopBar( {super.key, this.leading, this.tail, this.height = 64}); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; return DragToMoveWrapper( child: Container( height: height, color: isDark ? Color(0xff2C3036) : Colors.white, child: Row( children: [ if (leading != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: leading!, ), const Spacer(), const SizedBox( width: 20, ), if (tail != null) tail!, const WindowButtons(), ], ), ), ); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/view/desk_ui/github_repo_panel.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import '../../data/model/repository.dart'; class GithubRepoPanel extends StatefulWidget { final Repository repository; const GithubRepoPanel({Key? key, required this.repository}) : super(key: key); @override State createState() => _GithubRepoPanelState(); } class _GithubRepoPanelState extends State { @override Widget build(BuildContext context) { return IconTheme( data: IconThemeData(size: 18,color: Theme.of(context).primaryColor), child: Align( alignment: Alignment.topCenter, child: Container( // alignment: Alignment.topCenter, width: 250, padding: EdgeInsets.all(10), margin: EdgeInsets.all(10), decoration: BoxDecoration( boxShadow: [ BoxShadow(color: Colors.black.withOpacity(0.1), offset: Offset(0, .5), blurRadius: 3) ], color:Theme.of(context).listTileTheme.tileColor, borderRadius: BorderRadius.circular(8)), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( widget.repository.fullName!, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), const SizedBox(height: 4,), Row( children: [ Text( '创建时间', style: const TextStyle(), ), const SizedBox(width: 8,), Text( '2020年04月15日', style: const TextStyle(), ), ], ), const SizedBox(height: 4,), Row( children: [ Text('开源协议'), const SizedBox(width: 8,), WrapColor( padding: EdgeInsets.symmetric(vertical: 2,horizontal: 4), child: Text( '${widget.repository.license?.type}', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.white, height: 1, fontSize: 12), ), ), ], ), Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( widget.repository.description!, style: const TextStyle(color: Colors.grey), ), ), const Divider(), const SizedBox(height: 8,), DefaultTextStyle( style: TextStyle(fontSize: 12,color: Theme.of(context).primaryColor), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Icon(Icons.star_border), Text(widget.repository.stargazersCount.toString(),style: TextStyle(fontSize: 12),), ]), SizedBox( height: 15, child: VerticalDivider( width: 1, color: Theme.of(context).primaryColor, ), ), Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Icon(TolyIcon.icon_show), const SizedBox( width: 5, ), Text(widget.repository.subscribersCount.toString(),style: TextStyle(fontSize: 12),), ]), SizedBox( height: 15, child: VerticalDivider( width: 1, color: Theme.of(context).primaryColor, ), ), Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Icon(TolyIcon.icon_fork), Text(widget.repository.forksCount.toString(),style: TextStyle(fontSize: 12),), ]), SizedBox( height: 15, child: VerticalDivider( width: 1, color: Theme.of(context).primaryColor, ), ), Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Icon(TolyIcon.icon_issues), const SizedBox( width: 5, ), Text(widget.repository.openIssuesCount.toString(),style: TextStyle(fontSize: 12),), ]), ], ), ) ], ), ), ), ); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/view/issues_point/issue_item.dart ================================================ import 'dart:ui'; import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:utils/utils.dart'; import '../../data/model/issue.dart'; /// create by 张风捷特烈 on 2020/9/3 /// contact me by email 1981462002@qq.com /// 说明: class IssueItem extends StatelessWidget { final Issue issue; final ValueChanged onTap; const IssueItem({ super.key, required this.issue, required this.onTap, }); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; return GestureDetector( onTap: () => onTap(issue), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( border: Border( bottom: BorderSide( color: Theme.of(context).dividerTheme.color ?? Colors.transparent, width: 1 / window.devicePixelRatio)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTop(), Padding( padding: const EdgeInsets.only(top: 5.0, bottom: 5.0, left: 10), child: Text( '${issue.title}', style: TextStyle(fontSize: 15, color: Colors.grey, shadows: [ Shadow(color: isDark ? Colors.black : Colors.white, offset: Offset(1, .5)) ]), ), ), Row( children: [ const Spacer(), WrapColor( color: Colors.greenAccent, child: Text( issue.commentNum.toString(), style: const TextStyle(color: Colors.white), )), const SizedBox( width: 5, ), const Icon( TolyIcon.icon_common, size: 20, ), ], ) ], ), ), ); } Widget _buildTop() { return Row( children: [ CircleImage( image: NetworkImage(issue.user?.avatarUrl ?? ''), size: 40, borderSize: 2, ), const SizedBox( width: 10, ), WrapColor( child: Text( "#${issue.number}", style: const TextStyle(color: Colors.white), )), const SizedBox( width: 10, ), Text( '${issue.user?.login}', style: const TextStyle(fontWeight: FontWeight.bold), ), const Spacer(), Text(ConvertMan.time2string(issue.createdAt!)), ], ); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/view/issues_point/issues_detail.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:utils/utils.dart'; import '../../bloc/bloc.dart'; import '../../data/model/github_model.dart'; import '../../data/model/issue.dart'; /// create by 张风捷特烈 on 2020/9/3 /// contact me by email 1981462002@qq.com /// 说明: class IssuesDetailPage extends StatelessWidget { const IssuesDetailPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: PreferredSize( preferredSize: Size(0,kToolbarHeight), child: DragToMoveWrapper( child: AppBar( centerTitle: false, title: const Text('Flutter 要点集录'), ), ), ), body: BlocBuilder( builder: _buildContent), ); } Widget _buildContent(BuildContext context, PointCommentState state) { if (state is PointCommentLoading) { return IssueTitle(issue: state.issue); } if (state is PointCommentLoaded) { return CustomScrollView( slivers: [ SliverToBoxAdapter(child: IssueTitle(issue: state.issue)), SliverList( delegate: SliverChildBuilderDelegate( (ctx, int index) => IssueCommentWidget( comment: state.comments[index], ), childCount: state.comments.length), ) ], ); } return Container(); } } class IssueTitle extends StatelessWidget { final Issue issue; const IssueTitle({Key? key, required this.issue}) : super(key: key); String get issueDesHtml => issue.bodyHtml != null ? issue.bodyHtml! : (issue.body != null) ? issue.body! : ""; @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; return SingleChildScrollView( child: Column( children: [ Row( children: [ Flexible( child: Container( padding: const EdgeInsets.symmetric(horizontal: 8), child: Panel( color: isDark?Colors.transparent:null, child: Text( '${issue.title}', maxLines: 1, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold), )), ), ), WrapColor( child: Text( '#${issue.number}', style: const TextStyle(color: Colors.white), ), ) ], ), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0,vertical: 8), child: WrapColor( color: Colors.blue.withAlpha(22), child: ListTile( dense: true, leading: CircleImage( size: 40, borderSize: 1, image: NetworkImage(issue.user!.avatarUrl!), ), title: Text(issue.user!.login!), subtitle: Row( children: [ Text('创建于:${ConvertMan.time2string(issue.createdAt!)}'), const Spacer(), WrapColor( color: Colors.green, child: Text( '更新于:${ConvertMan.time2string(issue.updatedAt!)}', style: const TextStyle(color: Colors.white), )), ], ), ), ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 8.0,horizontal: 20), child: MarkdownWidget( markdownData: issueDesHtml, style: isDark?MarkdownWidget.kDarkLight:MarkdownWidget.kWhite), ), const Divider( thickness: 2, ) ], ), ); } } class IssueCommentWidget extends StatelessWidget { final IssueComment comment; const IssueCommentWidget({Key? key, required this.comment}) : super(key: key); String get issueDesHtml => comment.bodyHtml != null ? comment.bodyHtml! : (comment.body != null) ? comment.body! : ""; @override Widget build(BuildContext context) { return Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: WrapColor( color: Colors.blue.withAlpha(22), child: ListTile( dense: true, leading: CircleImage( size: 40, borderSize: 1, image: NetworkImage(comment.user!.avatarUrl!), ), title: Text(comment.user!.login!), subtitle: Row( children: [ Text('创建于:${ConvertMan.time2string(comment.createdAt!)}'), const Spacer(), WrapColor( color: Colors.green, child: Text( '更新于:${ConvertMan.time2string(comment.updatedAt!)}', style: const TextStyle(color: Colors.white), )), ], ), ), ), ), Padding( padding: const EdgeInsets.all(8.0), child: MarkdownWidget( markdownData: issueDesHtml, style: MarkdownWidget.kWhite), ), const Divider(thickness: 2) ], ); } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/view/issues_point/issues_point_page.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import '../../bloc/bloc.dart'; import '../../data/model/issue.dart'; import '../../data/model/repository.dart'; import '../../repository/api/point_api.dart'; import 'issue_item.dart'; import 'issues_detail.dart'; import 'repo_widget.dart'; /// create by 张风捷特烈 on 2020/6/17 /// contact me by email 1981462002@qq.com /// 说明: class IssuesPointScope extends StatelessWidget { const IssuesPointScope({super.key}); @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider( create: (_) => PointBloc(PointApiImpl())..loadPoint()), ], child: const IssuesPointPage(), ); } } class IssuesPointPage extends StatelessWidget { const IssuesPointPage({super.key}); @override Widget build(BuildContext context) { return const Scaffold(body: IssuesPointContent()); } } class IssuesPointContent extends StatefulWidget { const IssuesPointContent({super.key}); @override State createState() => _IssuesPointContentState(); } class _IssuesPointContentState extends State { Repository? _repository; @override void initState() { super.initState(); _loadRepo(); } @override Widget build(BuildContext context) { return BlocBuilder( builder: (_, state) => RefreshIndicator( onRefresh: _loadIssues, child: CustomScrollView(slivers: [ // _buildSliverAppBar(), buildContentByState(state) ]), )); } Widget buildContentByState(PointState state) { if (state is PointLoading) { return const SliverPadding( padding: EdgeInsets.only(top: 150), sliver: SliverToBoxAdapter( child: Center( child: SpinKitCircle( color: Colors.blue, ), )), ); } if (state is PointLoaded) { List issues = state.issues; return SliverList( delegate: SliverChildBuilderDelegate( (ctx, int index) => IssueItem(onTap: toDetailPage, issue: issues[index]), childCount: issues.length, ), ); } if (state is PointLoadFailure) { return SliverPadding( padding: const EdgeInsets.only(top: 40), sliver: SliverToBoxAdapter( child: Center( child: Text(state.error), )), ); } return const SliverPadding( padding: EdgeInsets.zero, ); } void toDetailPage(Issue issue) { Navigator.of(context).push( SlidePageRoute( child: BlocProvider( create: (_) => PointCommentBloc()..loadPointComment(issue), child: const IssuesDetailPage(), ), ), ); } Widget _buildSliverAppBar() { return SliverAppBar( expandedHeight: 210.0, // leading: _buildLeading(), title: const Text('Flutter要点集录'), // actions: _buildActions(), elevation: 5, pinned: true, actions: [ IconButton( icon: const Icon( Icons.help_outline, color: Colors.white, ), onPressed: () { // Navigator.of(context).pushNamed(UnitRouter.bug); }) ], backgroundColor: Colors.blue, flexibleSpace: FlexibleSpaceBar( //伸展处布局 titlePadding: const EdgeInsets.only(left: 55, bottom: 15), //标题边距 collapseMode: CollapseMode.parallax, //视差效果 background: _repository == null ? const Center( child: SpinKitFadingCube( color: Colors.white, ), ) : RepoWidget( repository: _repository!, ), ), ); } Future _loadIssues() async { BlocProvider.of(context).loadPoint(); await Future.delayed(const Duration(milliseconds: 200)); } void _loadRepo() async { PointApi api = context.read().api; final ApiRet ret = await api.getFlutterUnitRepo(); if (ret.success) { setState(() { _repository = ret.data; }); } } } ================================================ FILE: modules/knowledge_system/artifact/lib/src/points/view/issues_point/repo_widget.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:utils/utils.dart'; import '../../data/model/repository.dart'; /// create by 张风捷特烈 on 2020/9/3 /// contact me by email 1981462002@qq.com /// 说明: class RepoWidget extends StatelessWidget { final Repository repository; const RepoWidget({Key? key, required this.repository}) : super(key: key); @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.only(top: 56 + 32.0, bottom: 5), padding: const EdgeInsets.only(top:20,right: 10,left: 10), // padding: EdgeInsets.all(10), decoration: const BoxDecoration( boxShadow: [ BoxShadow(color: Colors.grey, offset: Offset(0, .5), blurRadius: 3) ], color: Colors.white, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(10), bottomRight: Radius.circular(10), )), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Row( children: [ Text( repository.fullName!, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), const SizedBox(width: 10,), WrapColor( child: Text( '${repository.license?.type}', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.white, fontSize: 12), ), ), const Spacer(), Text( "创建:${ConvertMan.time2string(repository.createdAt!, just: true)}", style: const TextStyle(color: Colors.grey), ), ], ), Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( repository.description!, style: const TextStyle(color: Colors.grey), ), ), const Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Icon(Icons.star_border), Text(repository.stargazersCount.toString()), ]), const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Text( "|", style: TextStyle(fontSize: 20, color: Colors.blue), ), ), Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Icon(TolyIcon.icon_show), const SizedBox( width: 5, ), Text(repository.subscribersCount.toString()), ]), const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Text( "|", style: TextStyle(fontSize: 20, color: Colors.blue), ), ), Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Icon(TolyIcon.icon_fork), Text(repository.forksCount.toString()), ]), const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Text( "|", style: TextStyle(fontSize: 20, color: Colors.blue), ), ), Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Icon(TolyIcon.icon_issues), const SizedBox( width: 5, ), Text(repository.openIssuesCount.toString()), ]), ], ) ], ), ); } } ================================================ FILE: modules/knowledge_system/artifact/pubspec.yaml ================================================ name: artifact description: artifact version: 0.0.1 homepage: environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=2.17.0" resolution: workspace dependencies: flutter: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/knowledge_system/artifact/test/utils_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; void main() { } ================================================ FILE: modules/knowledge_system/awesome/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ build/ ================================================ FILE: modules/knowledge_system/awesome/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" channel: "stable" project_type: package ================================================ FILE: modules/knowledge_system/awesome/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/knowledge_system/awesome/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/knowledge_system/awesome/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/knowledge_system/awesome/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/knowledge_system/awesome/lib/awesome.dart ================================================ library awesome; /// A Calculator. class Calculator { /// Returns [value] plus 1. int addOne(int value) => value + 1; } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_01/main.dart ================================================ import 'package:flutter/material.dart'; import 'page/home/home_page.dart'; void main(){ runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const ChangeNotifierHome01(), ); } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_01/notifier/progress_value_notifier.dart ================================================ import 'package:flutter/material.dart'; ProgressValueNotifier progress = ProgressValueNotifier(); class ProgressValueNotifier with ChangeNotifier{ double _value = 0; double get value =>_value; String get valueStr => '${(value*100).toStringAsFixed(1)}%'; set value(double value){ _value = value.clamp(0, 1); notifyListeners(); } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_01/page/detail/detail_progress_view.dart ================================================ import 'package:flutter/material.dart'; import '../../notifier/progress_value_notifier.dart'; class DetailProgressView extends StatefulWidget { const DetailProgressView({super.key}); @override State createState() => _DetailProgressViewState(); } class _DetailProgressViewState extends State { @override void initState() { super.initState(); progress.addListener(_update); } @override Widget build(BuildContext context) { return Stack( alignment: Alignment.center, children:[ SizedBox( width: 200, height: 50, child: LinearProgressIndicator( value: progress.value, backgroundColor: Colors.grey, ), ), Text(progress.valueStr,style: TextStyle(color: Colors.white),) ], ); } void _update() { setState(() {}); } @override void dispose() { progress.removeListener(_update); super.dispose(); } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_01/page/detail/download_detail.dart ================================================ import 'package:flutter/material.dart'; import 'detail_progress_view.dart'; class DownloadDetailPage extends StatelessWidget { const DownloadDetailPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('下载详情页'), ), body: Center(child: DetailProgressView()), ); } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_01/page/home/home_page.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import '../../notifier/progress_value_notifier.dart'; import '../detail/download_detail.dart'; import 'home_progress_view.dart'; class ChangeNotifierHome01 extends StatefulWidget { const ChangeNotifierHome01({super.key}); @override State createState() => _ChangeNotifierHome01State(); } class _ChangeNotifierHome01State extends State { TextEditingController c = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _startTimer, child: const Icon(Icons.restart_alt), ), appBar: AppBar( title: const Text('下载进度通知模拟'), actions: [ IconButton(onPressed: (){ Navigator.of(context).push(MaterialPageRoute(builder: (_)=>const DownloadDetailPage())); }, icon: const Icon(Icons.info_outline)) ], ), body: const Center(child: HomeProgressView()), ); } Timer? _timer; void _startTimer(){ if(_timer!=null) return; if(progress.value==1.0){ progress.value=0; } print("======${progress.value==1.0}======"); _timer = Timer.periodic(const Duration(milliseconds: 200),_updateProgress); } void _updateProgress(Timer timer) { if(progress.value>=1.0){ timer.cancel(); _timer = null; return; } progress.value += 0.01; } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_01/page/home/home_progress_view.dart ================================================ import 'package:flutter/material.dart'; import '../../notifier/progress_value_notifier.dart'; class HomeProgressView extends StatefulWidget { const HomeProgressView({super.key}); @override State createState() => _HomeProgressViewState(); } class _HomeProgressViewState extends State { @override void initState() { super.initState(); progress.addListener(_update); } @override Widget build(BuildContext context) { return Stack( alignment: Alignment.center, children:[ SizedBox( width: 100, height: 100, child: CircularProgressIndicator( value: progress.value, backgroundColor: Colors.grey, ), ), Text(progress.valueStr) ], ); } void _update() { setState(() {}); } @override void dispose() { progress.removeListener(_update); super.dispose(); } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_02/main.dart ================================================ import 'package:flutter/material.dart'; import 'notifier/download_data_scope.dart'; import 'notifier/progress_value_notifier.dart'; import 'page/home/home_page.dart'; class MyApp extends StatelessWidget{ const MyApp({super.key}); @override Widget build(BuildContext context) { return DownloadDataScope( notifier: ProgressValueNotifier(), child: MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const ChangeNotifierHome02(), ), ); } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_02/notifier/download_data_scope.dart ================================================ import 'package:flutter/cupertino.dart'; import 'progress_value_notifier.dart'; class DownloadDataScope extends InheritedNotifier{ const DownloadDataScope({super.key, required super.child,super.notifier}); static ProgressValueNotifier of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType()!.notifier!; } static ProgressValueNotifier read(BuildContext context) { return context.getInheritedWidgetOfExactType()!.notifier!; } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_02/notifier/progress_value_notifier.dart ================================================ import 'package:flutter/material.dart'; class ProgressValueNotifier with ChangeNotifier{ double _value = 0; double get value =>_value; String get valueStr => '${(value*100).toStringAsFixed(1)}%'; set value(double value){ _value = value.clamp(0, 1); notifyListeners(); } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_02/page/detail/detail_progress_view.dart ================================================ import 'package:flutter/material.dart'; import '../../notifier/download_data_scope.dart'; import '../../notifier/progress_value_notifier.dart'; class DetailProgressView extends StatelessWidget{ const DetailProgressView({super.key}); @override Widget build(BuildContext context) { ProgressValueNotifier progress = DownloadDataScope.of(context); return Stack( alignment: Alignment.center, children:[ SizedBox( width: 200, height: 50, child: LinearProgressIndicator( value: progress.value, backgroundColor: Colors.grey, ), ), Text(progress.valueStr,style: TextStyle(color: Colors.white),) ], ); } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_02/page/detail/download_detail.dart ================================================ import 'package:flutter/material.dart'; import 'detail_progress_view.dart'; class DownloadDetailPage extends StatelessWidget { const DownloadDetailPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('下载详情页'), ), body: Center(child: DetailProgressView()), ); } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_02/page/home/home_page.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import '../../notifier/download_data_scope.dart'; import '../../notifier/progress_value_notifier.dart'; import '../detail/download_detail.dart'; import 'home_progress_view.dart'; class ChangeNotifierHome02 extends StatefulWidget { const ChangeNotifierHome02({super.key}); @override State createState() => _ChangeNotifierHome02State(); } class _ChangeNotifierHome02State extends State { TextEditingController c = TextEditingController(); @override Widget build(BuildContext context) { print("=========_ChangeNotifierHome02State#build==========="); return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _startTimer, child: const Icon(Icons.restart_alt), ), appBar: AppBar( title: const Text('下载进度通知模拟'), actions: [ IconButton(onPressed: (){ Navigator.of(context).push(MaterialPageRoute(builder: (_)=>const DownloadDetailPage())); }, icon: const Icon(Icons.info_outline)) ], ), body: const Center(child: HomeProgressView()), ); } Timer? _timer; ProgressValueNotifier get progress => DownloadDataScope.read(context); void _startTimer(){ if(_timer!=null) return; if(progress.value==1.0){ progress.value=0; } _timer = Timer.periodic(const Duration(milliseconds: 200),_updateProgress); } void _updateProgress(Timer timer) { if(progress.value>=1.0){ timer.cancel(); _timer = null; return; } progress.value += 0.01; } } ================================================ FILE: modules/knowledge_system/awesome/lib/listenable/change_notifier_02/page/home/home_progress_view.dart ================================================ import 'package:flutter/material.dart'; import '../../notifier/download_data_scope.dart'; import '../../notifier/progress_value_notifier.dart'; class HomeProgressView extends StatelessWidget{ const HomeProgressView({super.key}); @override Widget build(BuildContext context) { ProgressValueNotifier progress = DownloadDataScope.of(context); return Stack( alignment: Alignment.center, children:[ SizedBox( width: 100, height: 100, child: CircularProgressIndicator( value: progress.value, backgroundColor: Colors.grey, ), ), Text(progress.valueStr) ], ); } } ================================================ FILE: modules/knowledge_system/awesome/pubspec.yaml ================================================ name: awesome description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/knowledge_system/awesome/test/awesome_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:awesome/awesome.dart'; void main() { test('adds one to input values', () { final calculator = Calculator(); expect(calculator.addOne(2), 3); expect(calculator.addOne(-7), -6); expect(calculator.addOne(0), 1); }); } ================================================ FILE: modules/knowledge_system/layout/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ build/ ================================================ FILE: modules/knowledge_system/layout/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "7482962148e8d758338d8a28f589f317e1e42ba4" channel: "stable" project_type: package ================================================ FILE: modules/knowledge_system/layout/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/knowledge_system/layout/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/knowledge_system/layout/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/knowledge_system/layout/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/knowledge_system/layout/lib/layout.dart ================================================ library layout; export 'src/views/layout_page.dart'; ================================================ FILE: modules/knowledge_system/layout/lib/src/bloc/display_logic.dart ================================================ import 'package:flutter/material.dart'; import '../data/display_map/display_map.dart'; import '../data/model/display_frame.dart'; import 'display_state.dart'; class DisplayLogic with ChangeNotifier { DisplayLogic(this._state); DisplayState _state; DisplayState get state => _state; bool get enableNext => _state.activeIndex < _state.total - 1; bool get enablePrev => _state.activeIndex > 0; void nextPage() { if (enableNext) { _state = _state.copyWith(activeIndex: _state.activeIndex + 1); notifyListeners(); } } void active(String path) { if (path != _state.router) { int length = kDisplayMap[path]!.length; _state = DisplayState( router: path, activeIndex: 0, total: length, ); notifyListeners(); } } void prevPage() { if (enablePrev) { _state = _state.copyWith(activeIndex: _state.activeIndex - 1); notifyListeners(); } } } class DisplayScope extends InheritedNotifier { const DisplayScope({ required super.notifier, required super.child, super.key, }); static DisplayLogic of(BuildContext context) => context.dependOnInheritedWidgetOfExactType()!.notifier!; } ================================================ FILE: modules/knowledge_system/layout/lib/src/bloc/display_state.dart ================================================ import 'package:layout/src/data/model/display_frame.dart'; import '../data/display_map/display_map.dart'; class DisplayState { final String router; final int activeIndex; final int total; DisplayState({ required this.router, required this.activeIndex, required this.total, }); DisplayFrame get frame { return kDisplayMap[router]![activeIndex]; } DisplayState copyWith({ int? total, int? activeIndex, String? router, }) => DisplayState( router: router ?? this.router, activeIndex: activeIndex ?? this.activeIndex, total: total ?? this.total, ); } ================================================ FILE: modules/knowledge_system/layout/lib/src/data/display_map/base.dart ================================================ import 'package:flutter/material.dart'; import 'package:layout/src/views/base/positioned/positioned_show.dart'; import '../../views/base/align/align_show.dart'; import '../../views/base/align/align_show2.dart'; import '../../views/base/padding/inner_padding.dart'; import '../../views/base/padding/outer_padding.dart'; import '../../views/base/padding/sizedbox_padding.dart'; import '../../views/base/size/size_loss_by_align.dart'; import '../../views/base/size/size_tight_constraint.dart'; import '../../views/base/size/size_unconstraint.dart'; import '../model/display_frame.dart'; List get baseSize => [ DisplayFrame( title: '父级中的紧约束', desc: "当前虽然指定 SizeBox 宽高为 150*100。但由于父级的紧约束被强制固定尺寸。" "下一步,将父级的紧约束改为宽松约束。", src: '', display: (BuildContext context) => const SizeTightConstraint( info: "受到紧约束\n尺寸无法生效", ), ), DisplayFrame( title: '用布局组件放宽父级约束', desc: "通过嵌套 Align、Row、Column、Flex、Scaffold 等组件, 提供一个宽松的父级约束,让 SizeBox 指定的尺寸可以生效" "就可以生效。", src: '', display: (BuildContext context) => const LossDisplay(), ), DisplayFrame( title: '用 UnconstrainedBox 组件解除约束', desc: "通过嵌套 UnconstrainedBox 组件,可以解除之前父级对当前区域的约束,从而使指定尺寸生效。", src: '', display: (BuildContext context) => const SizeUnconstrain(), ), ]; List get basePadding => [ DisplayFrame( title: 'Padding 实现内边距', desc: "将色块区域视为边界,文字距离边界有一定的内边距。", src: '', display: (BuildContext context) => const InnerPadding(), ), DisplayFrame( title: 'Padding 实现外边距', desc: "两个色块区域之间,右侧可以通过 Padding 嵌套,距离外部有边距。Container 的 margin 属性就是这个原理。", src: '', display: (BuildContext context) => const OuterPadding(), ), DisplayFrame( title: 'SizedBox 实现边距', desc: "有时在行列布局中,可以通过空白的 SizedBox 组件进行站位,来简单地实现边距效果。", src: '', display: (BuildContext context) => const SizedBoxPadding(), ), ]; List get baseAlign => [ DisplayFrame( title: 'Align 组件实现对齐', desc: "Align 组件可以在自身区域内(示例中灰色),对子组件(蓝色)进行对齐定位。", src: '', display: (BuildContext context) => const AlignShow(), ), DisplayFrame( title: 'Align 实现 sin 排布', desc: "由于Alignment对象可指定在父容器中宽高的分率位置,可以使用Align实现一些复杂的排布需求,比如按指定的数学方程变化位置。", src: '', display: (BuildContext context) => const AlignShow2(), ), ]; List get basePostioned => [ DisplayFrame( title: 'Positioned 组件实现定位', desc: "Positioned 组件可以在 Stack 组件内,对子组件指定位置(左上右下)进行定位布局。", src: '', display: (BuildContext context) => const PositionedShow(), ), ]; ================================================ FILE: modules/knowledge_system/layout/lib/src/data/display_map/display_map.dart ================================================ import 'package:flutter/material.dart'; import '../../views/base/size/size_loss_by_align.dart'; import '../../views/base/size/size_tight_constraint.dart'; import '../../views/base/size/size_unconstraint.dart'; import '../../views/popable/autocomplete_demo.dart'; import '../../views/popable/dropdown_button_demo.dart'; import '../../views/popable/dropdown_menu_demo.dart'; import '../model/display_frame.dart'; import 'base.dart'; import 'funny.dart'; import 'multi.dart'; Map> get kDisplayMap => { '/base/size': baseSize, '/base/padding': basePadding, '/base/align': baseAlign, '/base/positioned': basePostioned, '/multi/flex': multiFlex, '/multi/wrap': multiWrap, '/multi/stack': multiStack, '/scroll/list': listView, '/scroll/grid': gridView, '/scroll/page': pageView, '/funny/elevator':funnyElevator, // '/popable/DropdownButton': [ // DisplayFrame( // title: '下拉按钮 DropdownButton', // desc: // "Material 风格的下拉选择按钮。基于 Navigator 导航实现,推入 _DropdownRoute 路由,所以点击外部区域时,弹框消失,且外部无法响应该次点击事件。视图构建逻辑非常固定,可定制性低。条目一次性完全加载,不适合海量条目。", // src: '', // display: (BuildContext context) => const CustomDropDownButton(), // ), // ], // '/popable/DropdownMenu': [ // DisplayFrame( // title: '下拉按钮 DropdownMenu', // desc: // "Material 风格的下拉选择按钮。基于 Navigator 导航实现,推入 _DropdownRoute 路由,所以点击外部区域时,弹框消失,且外部无法响应该次点击事件。视图构建逻辑非常固定,可定制性低。条目一次性完全加载,不适合海量条目。", // src: '', // display: (BuildContext context) => const DropdownMenuNode1(), // ), // ], // '/popable/Autocomplete': [ // DisplayFrame( // title: '自动填充 Autocomplete', // desc: // "Material 风格的下拉选择按钮。基于 Navigator 导航实现,推入 _DropdownRoute 路由,所以点击外部区域时,弹框消失,且外部无法响应该次点击事件。视图构建逻辑非常固定,可定制性低。条目一次性完全加载,不适合海量条目。", // src: '', // display: (BuildContext context) => const AutocompleteDemo(), // ), // ], }; ================================================ FILE: modules/knowledge_system/layout/lib/src/data/display_map/funny.dart ================================================ // Copyright 2014 The 星星 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 星星 // CreateTime: 2024-07-02 // Contact Me: 1981462002@qq.com import 'package:flutter/cupertino.dart'; import '../../views/interest/elevator/elevator.dart'; import '../model/display_frame.dart'; List get funnyElevator => [ DisplayFrame( title: '电梯布局', desc: "模拟完成电梯的运行。", src: '', display: (BuildContext context) => ElevatorRoom(), ), ]; ================================================ FILE: modules/knowledge_system/layout/lib/src/data/display_map/multi.dart ================================================ import 'package:flutter/material.dart'; import 'package:layout/src/views/base/positioned/positioned_show.dart'; import 'package:layout/src/views/playground/view/stack/stack_playground.dart'; import '../../views/base/align/align_show.dart'; import '../../views/base/align/align_show2.dart'; import '../../views/base/padding/inner_padding.dart'; import '../../views/base/padding/outer_padding.dart'; import '../../views/base/padding/sizedbox_padding.dart'; import '../../views/base/size/size_loss_by_align.dart'; import '../../views/base/size/size_tight_constraint.dart'; import '../../views/base/size/size_unconstraint.dart'; import '../../views/multi/flex/column_show.dart'; import '../../views/playground/view/flex/flex_playground.dart'; import '../../views/multi/flex/row_show.dart'; import '../../views/playground/view/wrap/wrap_playground.dart'; import '../../views/scroll/grid_view/grid_view_demo01.dart'; import '../../views/scroll/list_view/list_view_demo01.dart'; import '../../views/scroll/page_view/page_view_demo01.dart'; import '../model/display_frame.dart'; List get multiFlex => [ DisplayFrame( title: 'Flex PlayGround', desc: "在 Flex PlayGround 中,你可以通过交互来直观体验 Flex 布局特性。灰色是 Flex 布局区域。", src: '', display: (BuildContext context) => const FlexPlayground(), ), DisplayFrame( title: 'Row 组件横向排列', desc: "可以将若干个组件横向排列,区域宽度无上限约束,子组件总宽超过时会越界异常。详细布局特性见第三页: Flex PlayGround", src: '', display: (BuildContext context) => const RowShow(), ), DisplayFrame( title: 'Column 组件横向排列', desc: "可以将若干个组件竖向排列,区域高度无上限约束,子组件总高超过时会越界异常。详细布局特性见第三页: Flex PlayGround", src: '', display: (BuildContext context) => const ColumnShow(), ), ]; List get multiWrap => [ DisplayFrame( title: 'Wrap PlayGround', desc: "在 Wrap PlayGround 中,你可以通过交互来直观体验 Wrap 布局特性。", src: '', display: (BuildContext context) => WrapPlayground(), ), ]; List get multiStack => [ DisplayFrame( title: 'Stack PlayGround', desc: "在 Stack PlayGround 中,你可以通过交互来直观体验 Stack 布局特性。", src: '', display: (BuildContext context) => StackPlayground(), ), ]; List get listView => [ DisplayFrame( title: 'ListView 滑动列表', desc: "通过 ListView.builder 构造,可以实现按需加载的滑动视图。", src: '', display: (BuildContext context) => ListViewDemo01(), ), ]; List get gridView => [ DisplayFrame( title: 'GridView 滑动网格', desc: "通过 GridView.builder 构造,可以实现按需加载的网格滑动视图。", src: '', display: (BuildContext context) => GridViewDemo01(), ), ]; List get pageView => [ DisplayFrame( title: 'PageView 滑动界面', desc: "通过 GridView.builder 构造,可以实现按需加载的网格滑动视图。", src: '', display: (BuildContext context) => PageViewDemo01(), ), ]; ================================================ FILE: modules/knowledge_system/layout/lib/src/data/model/display_frame.dart ================================================ import 'package:flutter/cupertino.dart'; import '../../views/base/size/size_tight_constraint.dart'; import '../../views/base/size/size_loss_by_align.dart'; import '../../views/base/size/size_unconstraint.dart'; import '../../views/popable/autocomplete_demo.dart'; import '../../views/popable/dropdown_button_demo.dart'; import '../../views/popable/dropdown_menu_demo.dart'; class DisplayFrame { final String title; final String desc; final String src; final WidgetBuilder display; DisplayFrame({ required this.title, required this.desc, required this.src, required this.display, }); } ================================================ FILE: modules/knowledge_system/layout/lib/src/ext/go_router/listener.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-05-25 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; mixin RouterChangeListenerMixin on State { late GoRouterDelegate _delegate; String get path => '/${_delegate.currentConfiguration.matches.last.matchedLocation}'; @override void initState() { super.initState(); _delegate = GoRouter.of(context).routerDelegate; _delegate.addListener(_onChange); } @override void dispose() { _delegate.removeListener(_onChange); super.dispose(); } void _onChange() { RouteMatchBase match = _delegate.currentConfiguration.matches.last; onChangeRoute("/${match.matchedLocation}"); } void onChangeRoute(String path); } ================================================ FILE: modules/knowledge_system/layout/lib/src/ext/go_router/path.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-05-25 // Contact Me: 1981462002@qq.com import 'package:go_router/go_router.dart'; extension GoRouterPath on GoRouter{ String get path => '/${routerDelegate.currentConfiguration.matches.last.matchedLocation}'; } ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/menu/base_layout.dart ================================================ import 'package:flutter/material.dart'; // import 'package:iroute/app/res/fx_icon.dart'; Map get baseMenus => { 'path': '/base', 'icon': Icons.layers_rounded, 'label': '基本布局', 'children': [ { 'path': '/size', 'label': '布局尺寸', }, { 'path': '/padding', 'label': '布局边距', }, { 'path': '/align', 'label': '布局对齐', }, { 'path': '/positioned', 'label': '布局定位', }, ] }; ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/menu/funny.dart ================================================ import 'package:flutter/material.dart'; // import 'package:iroute/app/res/fx_icon.dart'; // { // 'path': '/expanded', // 'label': '延展布局', // // 'icon': Icons.text_fields, // }, // { // 'path': '/holy', // 'label': '圣杯布局', // // 'icon': Icons.text_fields, // }, Map get funnyMenus => { 'path': '/funny', 'icon': Icons.multitrack_audio, 'label': '趣味布局', 'children': [ { 'path': '/elevator', 'label': '电梯布局', }, ] }; ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/menu/layout.dart ================================================ // import '../../../app/res/fx_icon.dart'; import 'package:flutter/material.dart'; Map home = { 'path': '/home', 'label': '布局总览', 'icon': Icons.dashboard // 'children': [ // { // 'icon': Icons.home, // 'path': '/home', // 'label': '首页', // }, // { // 'path': '/collect', // 'label': '我的收藏', // 'icon': Icons.collections_bookmark_outlined, // }, // ], }; ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/menu/menu_repository_impl.dart ================================================ import 'base_layout.dart'; import 'funny.dart'; import 'scroll.dart'; import 'layout.dart'; import 'multi.dart'; Map get layoutMenus => { 'children': [ home, baseMenus, multiMenus, calcMenus, funnyMenus, ] }; ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/menu/multi.dart ================================================ import 'package:flutter/material.dart'; // import 'package:iroute/app/res/fx_icon.dart'; // { // 'path': '/expanded', // 'label': '延展布局', // // 'icon': Icons.text_fields, // }, // { // 'path': '/holy', // 'label': '圣杯布局', // // 'icon': Icons.text_fields, // }, Map get multiMenus => { 'path': '/multi', 'icon': Icons.multitrack_audio, 'label': '多子布局', 'children': [ { 'path': '/flex', 'label': 'Flex 适应布局', }, { 'path': '/wrap', 'label': 'Wrap 包裹布局', }, { 'path': '/stack', 'label': 'Stack 堆叠布局', }, ] }; ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/menu/popable.dart ================================================ import 'package:flutter/material.dart'; // import 'package:iroute/app/res/fx_icon.dart'; Map get popableMenus => { 'path': '/popable', 'icon': Icons.layers_rounded, 'label': '菜单浮层', 'children': [ { 'path': '/DropdownButton', 'label': '下拉按钮', }, { 'path': '/DropdownMenu', 'label': '下拉菜单', // 'icon': Icons.calculate_outlined, }, { 'path': '/Autocomplete', 'label': '自动填充', // 'icon': Icons.calculate_outlined, }, ] }; ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/menu/scroll.dart ================================================ import 'package:flutter/material.dart'; // import 'package:iroute/app/res/fx_icon.dart'; Map get calcMenus => { 'path': '/scroll', 'icon': Icons.touch_app_outlined, 'label': '滑动布局', 'children': [ { 'path': '/list', 'label': '列表布局', // 'icon': Icons.list_alt, }, { 'path': '/grid', 'label': '网格布局', // 'icon': Icons.grid_on_sharp, }, { 'path': '/page', 'label': '滑页布局', // 'icon': Icons.grid_on_sharp, }, ] }; ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/router/app_router.dart ================================================ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../../layout.dart'; import 'desk_router.dart'; RouteBase get layoutRoutes => GoRoute( path: '/', redirect: (_, __) => null, routes: [ // GoRoute( // path: 'start_error', // builder: (BuildContext context, GoRouterState state) { // return AppStartFixListener( // child: ErrorPage( // error: state.extra.toString(), // ), // ); // }, // ), deskNavRoute ], ); ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/router/desk_router.dart ================================================ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../../layout.dart'; import '../../views/base/size/size_tight_constraint.dart'; import '../../views/base/size/size_display.dart'; import '../../views/test_show.dart'; import '../menu/menu_repository_impl.dart'; import '../view/app_desk_navigation.dart'; RouteBase get deskNavRoute => ShellRoute( builder: (BuildContext context, GoRouterState state, Widget child) { return AppDeskNavigation(content: child); }, routes: [ GoRoute( path: 'home', builder: (BuildContext context, GoRouterState state) { return const LayoutPage(); }, ), GoRoute( path: 'base/:name', builder: (BuildContext context, GoRouterState state) { return const FrameDisplayPanel(); }, ), GoRoute( path: 'scroll/:name', builder: (BuildContext context, GoRouterState state) { return const FrameDisplayPanel(); }, ), GoRoute( path: 'popable/:name', builder: (BuildContext context, GoRouterState state) { return const FrameDisplayPanel(); }, ), GoRoute( path: 'multi/:name', builder: (BuildContext context, GoRouterState state) { return const FrameDisplayPanel(); }, ), GoRoute( path: 'funny/:name', builder: (BuildContext context, GoRouterState state) { return const FrameDisplayPanel(); }, // builder: (_,__)=>Text("暂未实现"), // routes: [ // GoRoute( // path: 'row', // builder: (BuildContext context, GoRouterState state) { // return TextShow( // info: 'row', // ); // }, // ), // GoRoute( // path: 'column', // builder: (BuildContext context, GoRouterState state) { // return TextShow( // info: 'column', // ); // }, // ), GoRoute( // path: 'expanded', // builder: (BuildContext context, GoRouterState state) { // return TextShow( // info: 'expanded', // ); // }, // ), // GoRoute( // path: 'holy', // builder: (BuildContext context, GoRouterState state) { // return TextShow( // info: 'holy', // ); // }, // ), // ] ), // GoRoute( // path: 'text/gen/secret', // builder: (BuildContext context, GoRouterState state) { // return SecretGenPage(); // }, // ), // GoRoute( // path: 'text/:name', // pageBuilder: (BuildContext context, GoRouterState state) { // return CustomTransitionPage( // key: ValueKey(state.uri.path), // // transitionDuration: const Duration(milliseconds: 500), // // reverseTransitionDuration: const Duration(milliseconds: 500), // child: ToolListPanel( // key: ValueKey(state.fullPath??''), // name: state.pathParameters['name'] ?? ''), // transitionsBuilder: (BuildContext context, // Animation animation, // Animation secondaryAnimation, // Widget child) { // return FadeTransition( // opacity: animation.drive(CurveTween(curve: Curves.easeIn)), // child: child, // ); // }); // }, // ), ]); ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/router/transition/fade_page_transitions_builder.dart ================================================ import 'package:flutter/material.dart'; class FadePageTransitionsBuilder extends PageTransitionsBuilder { const FadePageTransitionsBuilder(); @override Widget buildTransitions( PageRoute? route, BuildContext? context, Animation animation, Animation secondaryAnimation, Widget child, ) { return FadeTransition( opacity: animation.drive(CurveTween(curve: Curves.easeIn)), child: child, ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/router/transition/page_route/fade_page_route.dart ================================================ //渐变透明路由动画 import 'dart:io'; import 'package:flutter/material.dart'; import '../slide_transition/cupertino_back_gesture_detector.dart'; class FadePageRoute extends MaterialPageRoute { final Widget child; final Duration duration; final Curve? curve; FadePageRoute({ required this.child, this.duration = const Duration(milliseconds: 300), this.curve, }) : super(builder: (_) => child); @override Widget buildTransitions( BuildContext context, Animation animation, Animation secondaryAnimation, Widget child, ) { if (Platform.isIOS) { child = CupertinoBackGestureDetector( enabledCallback: () => isPopGestureEnabled(this), onStartPopGesture: () => startPopGesture(this), child: child, ); } if (curve != null) { animation = CurvedAnimation( parent: animation, curve: curve!, ); } return FadeTransition( opacity: Tween(begin: 0.1, end: 1.0).animate(animation), child: child, ); } @override Duration get transitionDuration => duration; @override @protected bool get hasScopedWillPopCallback { return false; } } ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/router/transition/page_route/slide_page_route.dart ================================================ //右--->左 import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../slide_transition/cupertino_back_gesture_detector.dart'; class SlidePageRoute extends MaterialPageRoute { final Widget child; final Duration duration; SlidePageRoute({ required this.child, this.duration = const Duration(milliseconds: 300), }) : super(builder: (_) => child); @override Widget buildTransitions( BuildContext context, Animation animation, Animation secondaryAnimation, Widget child, ) { if (Platform.isIOS) { child = CupertinoBackGestureDetector( enabledCallback: () => isPopGestureEnabled(this), onStartPopGesture: () => startPopGesture(this), child: child); } final bool linearTransition = isPopGestureInProgress(this); return CupertinoPageTransition( primaryRouteAnimation: animation, secondaryRouteAnimation: secondaryAnimation, linearTransition: linearTransition, child: child, ); } @override Duration get transitionDuration => duration; @override @protected bool get hasScopedWillPopCallback { return false; } } ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/router/transition/page_route/zero_page_route.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import '../slide_transition/cupertino_back_gesture_detector.dart'; class ZeroPageRoute extends MaterialPageRoute { final Widget child; ZeroPageRoute({ required this.child, }) : super(builder: (_) => child); @override Widget buildTransitions( BuildContext context, Animation animation, Animation secondaryAnimation, Widget child, ) { if (Platform.isIOS) { child = CupertinoBackGestureDetector( enabledCallback: () => isPopGestureEnabled(this), onStartPopGesture: () => startPopGesture(this), child: child, ); } return child; } @override Duration get transitionDuration => Duration.zero; } ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/router/transition/size_clip_transition.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; class SizeClipTransition extends StatelessWidget { final Animation animation; final Animation secondaryAnimation; final Widget child; const SizeClipTransition({ super.key, required this.animation, required this.secondaryAnimation, required this.child, }); @override Widget build(BuildContext context) { return ClipPath( clipper: CirclePathClipper(Curves.easeIn.transform(animation.value)), child: child, ); } } class SizePathClipper extends CustomClipper { final double progress; SizePathClipper(this.progress); @override Path getClip(Size size) { Rect box = Rect.fromLTWH(0, 0, size.width, size.height); Rect center = Rect.fromCenter( center: Offset(size.width / 2, size.height / 2), width: size.width * (1 - progress), height: size.height, ); return Path() ..addRect(box) ..addRect(center) ..fillType = PathFillType.evenOdd; } @override bool shouldReclip(covariant SizePathClipper oldClipper) { return oldClipper.progress != progress; } } class ScalePathClipper extends CustomClipper { final double progress; ScalePathClipper(this.progress); @override Path getClip(Size size) { Rect box = Rect.fromLTWH(0, 0, size.width, size.height); Rect center = Rect.fromCenter( center: Offset(size.width / 2, size.height / 2), width: size.width * (1 - progress), height: size.height* (1 - progress), ); return Path() ..addRect(box) ..addRect(center) ..fillType = PathFillType.evenOdd; } @override bool shouldReclip(covariant ScalePathClipper oldClipper) { return oldClipper.progress != progress; } } class CirclePathClipper extends CustomClipper { final double progress; CirclePathClipper(this.progress); @override Path getClip(Size size) { print('progress:$progress'); if(progress==0){ return Path(); } Rect box = Rect.fromLTWH(0, 0, size.width, size.height); Rect center = Rect.fromCircle( center: Offset(size.width , 0), radius: sqrt(size.width*size.width+size.height*size.height) * (progress), ); Path zone = Path()..addRect(box); Path cliper = Path()..addOval(center); return Path.combine(PathOperation.intersect, zone, cliper ); } @override bool shouldReclip(covariant CirclePathClipper oldClipper) { return oldClipper.progress != progress; } } ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/router/transition/slide_transition/cupertino_back_gesture_detector.dart ================================================ import 'dart:math'; import 'dart:ui'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; // The maximum time for a page to get reset to it's original position if the // user releases a page mid swipe. const int _kMaxPageBackAnimationTime = 300; // Milliseconds. const double _kBackGestureWidth = 20.0; const double _kMinFlingVelocity = 1.0; // Screen widths per second. // An eyeballed value for the maximum time it takes for a page to animate forward // if the user releases a page mid swipe. const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds. /// This is the widget side of [CupertinoBackGestureController]. /// /// This widget provides a gesture recognizer which, when it determines the /// route can be closed with a back gesture, creates the controller and /// feeds it the input from the gesture recognizer. /// /// The gesture data is converted from absolute coordinates to logical /// coordinates by this widget. /// /// The type `T` specifies the return type of the route with which this gesture /// detector is associated. class CupertinoBackGestureDetector extends StatefulWidget { const CupertinoBackGestureDetector({ super.key, required this.enabledCallback, required this.onStartPopGesture, required this.child, }); final Widget child; final ValueGetter enabledCallback; final ValueGetter> onStartPopGesture; @override _CupertinoBackGestureDetectorState createState() => _CupertinoBackGestureDetectorState(); } class _CupertinoBackGestureDetectorState extends State> { CupertinoBackGestureController? _backGestureController; late HorizontalDragGestureRecognizer _recognizer; @override void initState() { super.initState(); _recognizer = HorizontalDragGestureRecognizer(debugOwner: this) ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate ..onEnd = _handleDragEnd ..onCancel = _handleDragCancel; } @override void dispose() { _recognizer.dispose(); super.dispose(); } void _handleDragStart(DragStartDetails details) { assert(mounted); assert(_backGestureController == null); _backGestureController = widget.onStartPopGesture(); } void _handleDragUpdate(DragUpdateDetails details) { assert(mounted); assert(_backGestureController != null); _backGestureController!.dragUpdate(_convertToLogical(details.primaryDelta! / context.size!.width)); } void _handleDragEnd(DragEndDetails details) { assert(mounted); assert(_backGestureController != null); _backGestureController!.dragEnd(_convertToLogical(details.velocity.pixelsPerSecond.dx / context.size!.width)); _backGestureController = null; } void _handleDragCancel() { assert(mounted); // This can be called even if start is not called, paired with the "down" event // that we don't consider here. _backGestureController?.dragEnd(0.0); _backGestureController = null; } void _handlePointerDown(PointerDownEvent event) { if (widget.enabledCallback()) { _recognizer.addPointer(event); } } double _convertToLogical(double value) { switch (Directionality.of(context)) { case TextDirection.rtl: return -value; case TextDirection.ltr: return value; } } @override Widget build(BuildContext context) { assert(debugCheckHasDirectionality(context)); // For devices with notches, the drag area needs to be larger on the side // that has the notch. double dragAreaWidth = Directionality.of(context) == TextDirection.ltr ? MediaQuery.paddingOf(context).left : MediaQuery.paddingOf(context).right; dragAreaWidth = max(dragAreaWidth, _kBackGestureWidth); return Stack( fit: StackFit.passthrough, children: [ widget.child, PositionedDirectional( start: 0.0, width: dragAreaWidth, top: 0.0, bottom: 0.0, child: Listener( onPointerDown: _handlePointerDown, behavior: HitTestBehavior.translucent, ), ), ], ); } } class CupertinoBackGestureController { /// Creates a controller for an iOS-style back gesture. /// /// The [navigator] and [controller] arguments must not be null. CupertinoBackGestureController({ required this.navigator, required this.controller, }) { navigator.didStartUserGesture(); } final AnimationController controller; final NavigatorState navigator; /// The drag gesture has changed by [fractionalDelta]. The total range of the /// drag should be 0.0 to 1.0. void dragUpdate(double delta) { controller.value -= delta; } /// The drag gesture has ended with a horizontal motion of /// [fractionalVelocity] as a fraction of screen width per second. void dragEnd(double velocity) { // Fling in the appropriate direction. // AnimationController.fling is guaranteed to // take at least one frame. // // This curve has been determined through rigorously eyeballing native iOS // animations. const Curve animationCurve = Curves.fastLinearToSlowEaseIn; final bool animateForward; // If the user releases the page before mid screen with sufficient velocity, // or after mid screen, we should animate the page out. Otherwise, the page // should be animated back in. if (velocity.abs() >= _kMinFlingVelocity) { animateForward = velocity <= 0; } else { animateForward = controller.value > 0.5; } if (animateForward) { // The closer the panel is to dismissing, the shorter the animation is. // We want to cap the animation time, but we want to use a linear curve // to determine it. final int droppedPageForwardAnimationTime = min( lerpDouble(_kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)!.floor(), _kMaxPageBackAnimationTime, ); controller.animateTo(1.0, duration: Duration(milliseconds: droppedPageForwardAnimationTime), curve: animationCurve); } else { // This route is destined to pop at this point. Reuse navigator's pop. navigator.pop(); // The popping may have finished inline if already at the target destination. if (controller.isAnimating) { // Otherwise, use a custom popping animation duration and curve. final int droppedPageBackAnimationTime = lerpDouble(0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)!.floor(); controller.animateBack(0.0, duration: Duration(milliseconds: droppedPageBackAnimationTime), curve: animationCurve); } } if (controller.isAnimating) { // Keep the userGestureInProgress in true state so we don't change the // curve of the page transition mid-flight since CupertinoPageTransition // depends on userGestureInProgress. late AnimationStatusListener animationStatusCallback; animationStatusCallback = (AnimationStatus status) { navigator.didStopUserGesture(); controller.removeStatusListener(animationStatusCallback); }; controller.addStatusListener(animationStatusCallback); } else { navigator.didStopUserGesture(); } } } // Called by _CupertinoBackGestureDetector when a pop ("back") drag start // gesture is detected. The returned controller handles all of the subsequent // drag events. CupertinoBackGestureController startPopGesture(PageRoute route) { return CupertinoBackGestureController( navigator: route.navigator!, controller: route.controller!, // protected access ); } bool isPopGestureEnabled(PageRoute route) { print( "======_isPopGestureEnabled:${route.hasScopedWillPopCallback}========="); // If there's nothing to go back to, then obviously we don't support // the back gesture. if (route.isFirst) { return false; } // If the route wouldn't actually pop if we popped it, then the gesture // would be really confusing (or would skip internal routes), so disallow it. if (route.willHandlePopInternally) { return false; } // If attempts to dismiss this route might be vetoed such as in a page // with forms, then do not allow the user to dismiss the route with a swipe. if (route.hasScopedWillPopCallback) { return false; } // Fullscreen dialogs aren't dismissible by back swipe. if (route.fullscreenDialog) { return false; } // If we're in an animation already, we cannot be manually swiped. if (route.animation!.status != AnimationStatus.completed) { return false; } // If we're being popped into, we also cannot be swiped until the pop above // it completes. This translates to our secondary animation being // dismissed. if (route.secondaryAnimation!.status != AnimationStatus.dismissed) { return false; } // If we're in a gesture already, we cannot start another. if (isPopGestureInProgress(route)) { return false; } // Looks like a back gesture would be welcome! return true; } /// True if an iOS-style back swipe pop gesture is currently underway for [route]. /// /// This just check the route's [NavigatorState.userGestureInProgress]. /// /// See also: /// /// * [popGestureEnabled], which returns true if a user-triggered pop gesture /// would be allowed. bool isPopGestureInProgress(PageRoute route) { return route.navigator!.userGestureInProgress; } ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/router/transition/slide_transition/slide_page_transition_builder.dart ================================================ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'cupertino_back_gesture_detector.dart'; class SlidePageTransitionsBuilder extends PageTransitionsBuilder { const SlidePageTransitionsBuilder(); @override Widget buildTransitions( PageRoute? route, BuildContext? context, Animation animation, Animation secondaryAnimation, Widget child, ) { if (Platform.isIOS) { child = CupertinoBackGestureDetector( enabledCallback: () => isPopGestureEnabled(route!), onStartPopGesture: () => startPopGesture(route!), child: child); } final bool linearTransition = isPopGestureInProgress(route!); return CupertinoPageTransition( primaryRouteAnimation: animation, secondaryRouteAnimation: secondaryAnimation, linearTransition: linearTransition, child: child, ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/view/app_desk_navigation.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import '../../views/display/layout_playground.dart'; import 'app_menu_tree.dart'; class AppDeskNavigation extends StatelessWidget { final Widget content; const AppDeskNavigation({super.key, required this.content}); @override Widget build(BuildContext context) { Color backgroundColor = context.isDark ? Color(0xff001529) : Colors.white; return Scaffold( backgroundColor: backgroundColor, body: Row( children: [ const AppMenuTree(), Expanded(child: LayoutPlayGround(content: content)), ], ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/navigation/view/app_menu_tree.dart ================================================ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:tolyui/tolyui.dart'; import '../../bloc/display_logic.dart'; import '../../ext/go_router/listener.dart'; import '../menu/menu_repository_impl.dart'; class AppMenuTree extends StatefulWidget { const AppMenuTree({super.key}); @override State createState() => _AppMenuTreeState(); } class _AppMenuTreeState extends State with RouterChangeListenerMixin { late MenuTreeMeta _menuMeta; @override void initState() { super.initState(); _initTreeMeta(); } void _initTreeMeta() { MenuNode root = MenuNode.fromMap(layoutMenus); List parts = Uri.parse(path).pathSegments; String parentPath = parts.sublist(0, parts.length - 1).join('/'); _menuMeta = MenuTreeMeta( expandMenus: ['/$parentPath'], activeMenu: root.find(path), root: root, ); } @override Widget build(BuildContext context) { Color expandBackgroundColor = context.isDark ? Colors.black : Colors.transparent; Color backgroundColor = context.isDark ? Color(0xff001529) : Colors.white; return TolyRailMenuTree( leading: const SizedBox( height: 18, ), enableWidthChange: true, maxWidth: 360, width: 190, meta: _menuMeta, backgroundColor: backgroundColor, expandBackgroundColor: expandBackgroundColor, onSelect: _onSelect, ); } void _onMenuRouterChange(BuildContext context, String? path) { if (path != null) { context.go(path); DisplayScope.of(context).active(path); } } void _onSelect(MenuNode menu) { if (menu.isLeaf) { context.go(menu.id); print(path); } else { _menuMeta = _menuMeta.select(menu, singleExpand: true); setState(() {}); } } @override void reassemble() { MenuNode root = MenuNode.fromMap(layoutMenus); _menuMeta = _menuMeta.copyWith(root: root); super.reassemble(); } @override void onChangeRoute(String path) { _menuMeta = _menuMeta.selectPath(path, singleExpand: true); DisplayScope.of(context).active(path); setState(() {}); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/base/align/align_show.dart ================================================ import 'package:flutter/material.dart'; class AlignShow extends StatelessWidget { const AlignShow({super.key}); final List alignments = const [ Alignment.topLeft, Alignment.topCenter, Alignment.topRight, Alignment.centerLeft, Alignment.center, Alignment.centerRight, Alignment.bottomLeft, Alignment.bottomCenter, Alignment.bottomRight, ]; final List alignmentsInfo = const [ "topLeft", "topCenter", "topRight", "centerLeft", "center", "centerRight", "bottomLeft", "bottomCenter", "bottomRight", ]; @override Widget build(BuildContext context) { TextStyle style = TextStyle( fontWeight: FontWeight.bold, color: Colors.grey, fontSize: 12 ); return Center( child: Wrap( children: alignments .toList() .map((mode) => Column(children: [ Container( margin: const EdgeInsets.all(5), width: 150, height: 100, color: Colors.grey.withAlpha(88), child: Align( child: Container( width: 30, height: 30, color: Colors.cyanAccent, ), alignment: mode)), Text( alignmentsInfo[alignments.indexOf(mode)], style: style, ) ])) .toList()), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/base/align/align_show2.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; class Ball extends StatelessWidget { const Ball({ super.key, this.radius = 15, this.color = Colors.blue, }); final double radius; //半径 final Color color; //颜色 @override Widget build(BuildContext context) { return Container( width: radius * 2, height: radius * 2, decoration: BoxDecoration( shape: BoxShape.circle, color: color, ), ); } } class AlignShow2 extends StatefulWidget { const AlignShow2({ super.key, }); @override _AlignShow2State createState() => _AlignShow2State(); } class _AlignShow2State extends State { double _x = 0.0; //Alignment坐标系上的x坐标 @override Widget build(BuildContext context) { var item = Container( width: 300, height: 120, color: Colors.black.withAlpha(10), child: Align( child: const Ball( color: Colors.orangeAccent, ), alignment: Alignment(_x, f(_x * pi)), ), ); var slider = SizedBox( width: 320, child: Slider( max: 180, min: -180, divisions: 360, label: "${_x.toStringAsFixed(2)}π", value: _x * 180, onChanged: (v) => setState(() => _x = v / 180)), ); return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [slider, item], ), ); } double f(x) { //映射函数 -- 可随意指定 double y = sin(x); return y; } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/base/padding/inner_padding.dart ================================================ import 'package:flutter/material.dart'; class InnerPadding extends StatelessWidget { const InnerPadding({super.key}); @override Widget build(BuildContext context) { Color? color = Theme.of(context).primaryColor; TextStyle? style = const TextStyle(color: Colors.white); return Center( child: ColoredBox( color: color, child: SizedBox( width: 200, height: 80, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0,vertical: 8), child: Text("张风捷特烈 " * 10, style: style), ), ), ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/base/padding/outer_padding.dart ================================================ import 'package:flutter/material.dart'; import 'inner_padding.dart'; class OuterPadding extends StatelessWidget { const OuterPadding({super.key}); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ InnerPadding(), Padding( padding: const EdgeInsets.only(left: 24.0), child: InnerPadding(), ), ], ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/base/padding/sizedbox_padding.dart ================================================ import 'package:flutter/material.dart'; import 'inner_padding.dart'; class SizedBoxPadding extends StatelessWidget { const SizedBoxPadding({super.key}); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ InnerPadding(), SizedBox(width: 24), InnerPadding(), ], ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/base/positioned/positioned_show.dart ================================================ import 'package:flutter/material.dart'; class PositionedShow extends StatefulWidget { const PositionedShow({super.key}); @override State createState() => _PositionedShowState(); } class _PositionedShowState extends State { @override Widget build(BuildContext context) { Widget yellowBox = Container( color: Colors.yellow, height: 100, width: 100, ); Widget redBox = Container( color: Colors.red, height: 90, width: 90, ); Widget greenBox = DraggedBox( onPanUpdate: _onPanUpdate, ); Widget cyanBox = Container( color: Colors.cyanAccent, height: 70, width: 70, ); return Container( color: Colors.grey.withAlpha(33), child: Stack( children: [ yellowBox, redBox, Positioned(top: _top, left: _left, child: greenBox), Positioned(bottom: 20, right: 20, child: cyanBox) ], )); } double _top = 20; double _left = 20; void _onPanUpdate(DragUpdateDetails details) { setState(() { _top += details.delta.dy; _left += details.delta.dx; }); } } class DraggedBox extends StatelessWidget { final GestureDragUpdateCallback? onPanUpdate; const DraggedBox({super.key, this.onPanUpdate}); @override Widget build(BuildContext context) { return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onPanUpdate: onPanUpdate, child: Container( color: Colors.green, height: 80, width: 80, ), ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/base/size/size_display.dart ================================================ import 'package:flutter/material.dart'; import '../../../bloc/display_logic.dart'; import '../../../data/model/display_frame.dart'; class FrameDisplayPanel extends StatelessWidget { const FrameDisplayPanel({super.key}); @override Widget build(BuildContext context) { DisplayFrame frame = DisplayScope.of(context).state.frame; return Material( color: Colors.transparent, elevation: 0, child: frame.display(context), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/base/size/size_loss_by_align.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:layout/src/views/components/grid_xy_layout.dart'; import 'size_tight_constraint.dart'; class LossDisplay extends StatelessWidget { const LossDisplay({super.key}); @override Widget build(BuildContext context) { return GridXYLayout( capacity: (2, 2), xyBuilder: ((int, int) pos) => switch (pos) { (0, 0) => const Align(child: SizeTightConstraint(info: 'Align 放宽约束')), (0, 1) => const Row(children: [SizeTightConstraint(info: 'Row 放宽约束')]), (1, 0) => const Scaffold( backgroundColor: Colors.transparent, body: SizeTightConstraint(info: 'Scaffold 放宽约束'), ), (1, 1) => const Column( children: [SizeTightConstraint(info: 'Column 放宽约束')], ), _ => const SizedBox() }, ); } } class SizeLossByAlign extends StatelessWidget { const SizeLossByAlign({super.key}); @override Widget build(BuildContext context) { return const Align( alignment: Alignment.topCenter, child: ColoredBox( color: Colors.redAccent, child: SizedBox( width: 120, height: 80, child: Center( child: Text( 'Align 放宽约束', style: TextStyle(color: Colors.white), )), ), ), ); } } class SizeLossByRow extends StatelessWidget { const SizeLossByRow({super.key}); @override Widget build(BuildContext context) { return const Row( children: [ ColoredBox( color: Colors.redAccent, child: SizedBox( width: 150, height: 100, child: Center( child: Text( 'Row 放宽约束', style: TextStyle(color: Colors.white), )), ), ), ], ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/base/size/size_tight_constraint.dart ================================================ import 'package:flutter/material.dart'; class SizeTightConstraint extends StatelessWidget { final String info; const SizeTightConstraint({super.key, required this.info}); @override Widget build(BuildContext context) { Color color = Theme.of(context).primaryColor; return LayoutBuilder( builder: (ctx,cts)=>ColoredBox( color: color, child: SizedBox( width: 150, height: 100, child: Center( child: Text( '$info\n当前约束:\n${cts.toString().replaceAll('BoxConstraints', '')}', style: const TextStyle(color: Colors.white), textAlign: TextAlign.center, )), ), ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/base/size/size_unconstraint.dart ================================================ import 'package:flutter/material.dart'; import 'package:layout/src/views/base/size/size_tight_constraint.dart'; class SizeUnconstrain extends StatelessWidget { const SizeUnconstrain({super.key}); @override Widget build(BuildContext context) { return UnconstrainedBox( child: SizeTightConstraint(info: "通过 UnconstrainedBox 解除约束",), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/components/grid_xy_layout.dart ================================================ import 'package:flutter/material.dart'; typedef XYBuilder = Function((int x, int y)); class GridXYLayout extends StatelessWidget { final (int, int) capacity; final XYBuilder xyBuilder; final bool hasLine; const GridXYLayout({ super.key, this.capacity = (2, 2), required this.xyBuilder, this.hasLine = true, }); @override Widget build(BuildContext context) { List children = []; for (int i = 0; i < capacity.$1; i++) { List columnChildren = []; for (int j = 0; j < capacity.$2; j++) { columnChildren.add(Expanded(child: xyBuilder((i, j)))); if (hasLine && j != capacity.$2 - 1) { columnChildren.add(const Divider()); } } children.add(Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ))); if (hasLine && i != capacity.$1 - 1) { children.add(const VerticalDivider()); } } return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: children, ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/display/layout_playground.dart ================================================ import 'package:flutter/material.dart'; import 'playground_bottom_bar.dart'; import 'playground_top_bar.dart'; class LayoutPlayGround extends StatelessWidget { final Widget content; const LayoutPlayGround({ super.key, required this.content, }); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const PlaygroundTopBar(), const Divider(), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 24), child: DecoratedBox( decoration: BoxDecoration( border: Border.all(color: Colors.grey.withOpacity(0.4)), ), child: content, ), )), const Divider(), const PlaygroundBottomBar() ], ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/display/playground_bottom_bar.dart ================================================ import 'package:flutter/material.dart'; import 'package:layout/src/bloc/display_logic.dart'; import '../../data/model/display_frame.dart'; class PlaygroundBottomBar extends StatelessWidget { const PlaygroundBottomBar({super.key}); @override Widget build(BuildContext context) { DisplayFrame frame = DisplayScope.of(context).state.frame; return Container( width: double.maxFinite, color: Color(0xfff2f2f2), padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), // height: 24, child: Text( frame.desc, style: TextStyle(fontSize: 12, fontFamily: '宋体'), ), // child: NavigationToolbar( // centerMiddle: true, // middle: Text("${menu??'布局测试'}"), // trailing: IconButton(onPressed: (){ // // }, icon: Icon(Icons.code)), // ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/display/playground_top_bar.dart ================================================ import 'package:flutter/material.dart'; import 'package:tolyui/basic/basic.dart'; import '../../bloc/display_logic.dart'; import '../../bloc/display_state.dart'; import '../../data/model/display_frame.dart'; class PlaygroundTopBar extends StatelessWidget { const PlaygroundTopBar({super.key}); @override Widget build(BuildContext context) { DisplayState state = DisplayScope.of(context).state; DisplayFrame frame = state.frame; const ActionStyle style = ActionStyle.light(backgroundColor: Color(0xffd5d5d5)); return Container( color: const Color(0xfff2f2f2), padding: const EdgeInsets.symmetric(horizontal: 12), height: 46, child: NavigationToolbar( centerMiddle: true, leading: UnconstrainedBox( child: Align( alignment: Alignment.centerLeft, child: Text( '当前: ${state.activeIndex + 1}/${state.total}', style: const TextStyle(color: Colors.grey, fontWeight: FontWeight.bold), )), ), middle: Text( frame.title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), trailing: Wrap( children: [ TolyAction( style: style, child: const Icon(Icons.skip_previous, size: 20), onTap: () { DisplayScope.of(context).prevPage(); }), TolyAction( style: style, child: const Icon(Icons.skip_next, size: 20), onTap: () { DisplayScope.of(context).nextPage(); }), TolyAction(style: style, child: const Icon(Icons.code, size: 20), onTap: () {}), ], ), ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/interest/elevator/elevator.dart ================================================ // Copyright 2014 The 星星. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 星星 // CreateTime: 2024-07-02 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; import 'package:tolyui/basic/basic.dart'; class ElevatorRoom extends StatefulWidget { const ElevatorRoom({super.key}); @override State createState() => _ElevatorRoomState(); } class _ElevatorRoomState extends State { int lv = 1 ; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { double roomHeight = constraints.maxHeight/20; return Row( children: [ SizedBox( width: 10, ), Stack( children: [ Column( children: List.generate( 20, (index) => Container( decoration: BoxDecoration(border: Border.all(width: 1, color: Colors.grey)), width: constraints.minWidth / 10, height: roomHeight, )), ), Positioned( bottom: roomHeight * ( lv - 1 ), child: elevator( width: constraints.minWidth / 10, height: constraints.maxHeight / 20,), ), ], ), Column( children: [ TolyAction(child: Icon(Icons.arrow_upward_outlined), onTap:upRoom), TolyAction(child: Icon(Icons.arrow_downward_outlined), onTap:downRoom), ], ), ], ); }, ); } void upRoom() { lv+=1; setState(() { }); } void downRoom() { lv-=1; setState(() { }); } Widget elevator({required double height, required double width}) { return Container( height: height, width: width, color: Colors.blue, ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/layout_page.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; import 'package:layout/src/navigation/router/app_router.dart'; import '../bloc/display_logic.dart'; import '../bloc/display_state.dart'; import '../data/display_map/display_map.dart'; class LayoutRouterPage extends StatefulWidget { LayoutRouterPage({super.key}); @override State createState() => _LayoutRouterPageState(); } class _LayoutRouterPageState extends State { final GoRouter _router = GoRouter( initialLocation: '/base/size', routes: [layoutRoutes], onException: (BuildContext ctx, GoRouterState state, GoRouter router) { router.go('/404', extra: state.uri.toString()); }, ); late final DisplayLogic logic; @override void initState() { logic = DisplayLogic(DisplayState( router: '/base/size', activeIndex: 0, total: kDisplayMap['/base/size']!.length, )); super.initState(); } @override Widget build(BuildContext context) { return DisplayScope( notifier: logic, child: Column( children: [ const Divider(), Expanded( child: Router.withConfig(config: _router), ), ], ), ); } } class LayoutPage extends StatelessWidget { const LayoutPage({super.key}); @override Widget build(BuildContext context) { return Material(color: Colors.white, child: const Center(child: Text("TODO"))); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/multi/flex/column_show.dart ================================================ import 'package:flutter/material.dart'; class ColumnShow extends StatelessWidget { const ColumnShow({super.key}); @override Widget build(BuildContext context) { List colors = [ const Color(0xffe64032), const Color(0xff307dee), const Color(0xfff9c01f), const Color(0xff309949), ]; return Column( children: [ Container(width: 20, height: 20, color: colors[0]), Container(width: 10, height: 80, color: colors[1]), Container(width: 40, height: 30, color: colors[2]), Container(width: 60, height: 20, color: colors[3]), ], ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/multi/flex/row_show.dart ================================================ import 'package:flutter/material.dart'; class RowShow extends StatelessWidget { const RowShow({super.key}); @override Widget build(BuildContext context) { List colors = [ Color(0xffe64032), Color(0xff307dee), Color(0xfff9c01f), Color(0xff309949), ]; return Row( children: [ Container(width: 20, height: 20, color: colors[0]), Container(width: 10, height: 80, color: colors[1]), Container(width: 40, height: 30, color: colors[2]), Container(width: 60, height: 20, color: colors[3]), ], ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/cons.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-29 // Contact Me: 1981462002@qq.com import 'dart:ui'; const List kColors = [ Color(0xffe64032), Color(0xff307dee), Color(0xfff9c01f), Color(0xff309949), ]; ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/data/flex_attr.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-29 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; class FlexAttr { final Axis direction; final MainAxisAlignment mainAxisAlignment; final CrossAxisAlignment crossAxisAlignment; final MainAxisSize mainAxisSize; final TextDirection textDirection; final VerticalDirection verticalDirection; final TextBaseline textBaseline; FlexAttr({ required this.direction, this.mainAxisAlignment = MainAxisAlignment.start, this.crossAxisAlignment = CrossAxisAlignment.center, this.mainAxisSize = MainAxisSize.max, this.textDirection = TextDirection.ltr, this.textBaseline = TextBaseline.alphabetic, this.verticalDirection = VerticalDirection.down, }); FlexAttr copyWith({ Axis? direction, MainAxisAlignment? mainAxisAlignment, CrossAxisAlignment? crossAxisAlignment, MainAxisSize? mainAxisSize, TextDirection? textDirection, VerticalDirection? verticalDirection, TextBaseline? textBaseline, }) => FlexAttr( direction: direction ?? this.direction, mainAxisAlignment: mainAxisAlignment ?? this.mainAxisAlignment, crossAxisAlignment: crossAxisAlignment ?? this.crossAxisAlignment, mainAxisSize: mainAxisSize ?? this.mainAxisSize, textDirection: textDirection ?? this.textDirection, verticalDirection: verticalDirection ?? this.verticalDirection, textBaseline: textBaseline ?? this.textBaseline, ); } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/data/stack_attr.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-29 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; class StackAttr { final AttrAlignment alignment; final StackFit fit; final TextDirection textDirection; StackAttr({ this.alignment = AttrAlignment.topStart, this.fit = StackFit.loose, this.textDirection = TextDirection.ltr, }); StackAttr copyWith({ AttrAlignment? alignment, StackFit? fit, TextDirection? textDirection, }) => StackAttr( alignment: alignment ?? this.alignment, fit: fit ?? this.fit, textDirection: textDirection ?? this.textDirection, ); } enum AttrAlignment{ topStart(Alignment.topLeft), topCenter(Alignment.topCenter), topRight(Alignment.topRight), bottomCenter(Alignment.bottomCenter), bottomLeft(Alignment.bottomLeft), bottomRight(Alignment.bottomRight), center(Alignment.center), centerLeft(Alignment.centerLeft), centerRight(Alignment.centerRight), ; final Alignment value; const AttrAlignment(this.value); } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/data/wrap_attr.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-29 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; class WrapAttr { final Axis direction; final WrapAlignment mainAlignment; final WrapCrossAlignment crossAxisAlignment; final double spacing; final TextDirection textDirection; final VerticalDirection verticalDirection; final WrapAlignment runAlignment; final double runSpacing; const WrapAttr({ this.direction = Axis.horizontal, this.mainAlignment = WrapAlignment.start, this.crossAxisAlignment = WrapCrossAlignment.center, this.spacing = 0.0, this.textDirection = TextDirection.ltr, this.verticalDirection = VerticalDirection.down, this.runAlignment = WrapAlignment.start, this.runSpacing =0.0, }); WrapAttr copyWith({ Axis? direction, WrapAlignment? mainAlignment, WrapCrossAlignment? crossAxisAlignment, double? spacing, TextDirection?textDirection, VerticalDirection? verticalDirection, WrapAlignment?runAlignment, Clip? clipBehavior, double ?runSpacing, }) => WrapAttr( direction: direction ?? this.direction, mainAlignment: mainAlignment ?? this.mainAlignment, crossAxisAlignment: crossAxisAlignment ?? this.crossAxisAlignment, spacing: spacing ?? this.spacing, textDirection: textDirection ?? this.textDirection, verticalDirection: verticalDirection ?? this.verticalDirection, runAlignment: runAlignment ?? this.runAlignment, runSpacing: runSpacing??this.runSpacing, ); } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/view/display_item.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-29 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; class DisplayItem { final double width; final double height; final Color color; DisplayItem({ required this.width, required this.height, required this.color, }); } class DisplayPlayItem extends StatefulWidget { final DisplayItem item; final bool selected; const DisplayPlayItem({ super.key, required this.item, required this.selected, }); @override State createState() => _DisplayPlayItemState(); } class _DisplayPlayItemState extends State { @override Widget build(BuildContext context) { return Container( width: widget.item.width, height: widget.item.height, decoration: BoxDecoration( border: widget.selected ? Border.all() : null, color: widget.item.color, ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/view/flex/flex_op_panel.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-29 // Contact Me: 1981462002@qq.com import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import '../../data/flex_attr.dart'; import '../form_item/item_selector.dart'; import '../form_item/item_size_input.dart'; class FlexOpTool extends StatefulWidget { final ValueChanged onAddBox; final VoidCallback onDelete; final VoidCallback onReset; final FlexAttr attr; final ValueChanged onAttrChange; const FlexOpTool({ super.key, required this.onAddBox, required this.onDelete, required this.onReset, required this.attr, required this.onAttrChange, }); @override State createState() => _FlexOpToolState(); } class _FlexOpToolState extends State { final TextEditingController _widthCtrl = TextEditingController(text: '24'); final TextEditingController _heightCtrl = TextEditingController(text: '64'); @override Widget build(BuildContext context) { TextStyle labelStyle = const TextStyle(color: Color(0xff61666d), fontSize: 12); return Column( children: [ const Padding( padding: EdgeInsets.all(8.0), child: Text( 'Flex 操作面板', style: TextStyle(fontWeight: FontWeight.bold), ), ), const Divider(), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), child: Row( children: [ Text('增删操作', style: labelStyle.copyWith(fontWeight: FontWeight.bold)), const Spacer(), TolyAction(onTap: _handleAdd, child: const Icon(CupertinoIcons.add, size: 18)), TolyAction(onTap: widget.onDelete, child: const Icon(CupertinoIcons.delete, size: 16)), ], ), ), ItemSizeInput(widthCtrl: _widthCtrl, heightCtrl: _heightCtrl), const SizedBox(height: 12), const Divider(), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), child: Row( children: [ Text('属性操作', style: labelStyle.copyWith(fontWeight: FontWeight.bold)), const Spacer(), TolyAction(onTap: widget.onReset, child: const Icon(CupertinoIcons.refresh, size: 16)), ], ), ), ItemSelector( label: '排列方向:', subTitle: 'direction', selectIndex: widget.attr.direction.index, data: Axis.values, calcFun: (Axis data) => data.name, onSelect: (Axis value) { widget.onAttrChange(widget.attr.copyWith(direction: value)); }, ), ItemSelector( label: '主轴对齐:', subTitle: 'mainAxisAlignment', selectIndex: widget.attr.mainAxisAlignment.index, data: MainAxisAlignment.values, calcFun: (MainAxisAlignment data) => data.name, onSelect: (MainAxisAlignment value) { widget.onAttrChange(widget.attr.copyWith(mainAxisAlignment: value)); }, ), ItemSelector( label: '主轴尺寸:', subTitle: 'mainAxisSize', selectIndex: widget.attr.mainAxisSize.index, data: MainAxisSize.values, calcFun: (MainAxisSize data) => data.name, onSelect: (MainAxisSize value) { widget.onAttrChange(widget.attr.copyWith(mainAxisSize: value)); }, ), ItemSelector( label: '叉轴对齐:', subTitle: 'crossAxisAlignment', selectIndex: widget.attr.crossAxisAlignment.index, data: CrossAxisAlignment.values, calcFun: (CrossAxisAlignment data) => data.name, onSelect: (CrossAxisAlignment value) { widget.onAttrChange(widget.attr.copyWith(crossAxisAlignment: value)); }, ), ItemSelector( label: '垂直方向:', subTitle: 'verticalDirection', selectIndex: widget.attr.verticalDirection.index, data: VerticalDirection.values, calcFun: (VerticalDirection data) => data.name, onSelect: (VerticalDirection value) { widget.onAttrChange(widget.attr.copyWith(verticalDirection: value)); }, ), ItemSelector( label: '文字方向:', subTitle: 'textDirection', selectIndex: widget.attr.textDirection.index, data: TextDirection.values, calcFun: (TextDirection data) => data.name, onSelect: (TextDirection value) { widget.onAttrChange(widget.attr.copyWith(textDirection: value)); }, ), ItemSelector( label: '文字基线:', subTitle: 'textBaseline', selectIndex: widget.attr.textBaseline.index, data: TextBaseline.values, calcFun: (TextBaseline data) => data.name, onSelect: (TextBaseline value) { widget.onAttrChange(widget.attr.copyWith(textBaseline: value)); }, ), ], ); } void _handleAdd() { final double? width = double.tryParse(_widthCtrl.text); final double? height = double.tryParse(_heightCtrl.text); if (width == null || height == null) { $message.warning(message: '请输入合法宽高数字!'); return; } widget.onAddBox(Size(width, height)); } @override void dispose() { _widthCtrl.dispose(); _heightCtrl.dispose(); super.dispose(); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/view/flex/flex_playground.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-23 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import '../../cons.dart'; import '../../data/flex_attr.dart'; import '../display_item.dart'; import 'flex_op_panel.dart'; class FlexPlayground extends StatefulWidget { const FlexPlayground({super.key}); @override State createState() => _FlexPlaygroundState(); } class _FlexPlaygroundState extends State { List _data = []; late FlexAttr _attr; int _selectIndex = -1; @override void initState() { super.initState(); _reset(init: true); } @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: Center( child: ColoredBox( color: Colors.grey.withOpacity(0.1), child: FlexDisplay( items: _data, attr: _attr, selectIndex: _selectIndex, onSelectChanged: _onSelectChanged, )), )), const VerticalDivider(), SizedBox( width: 210, child: FlexOpTool( attr: _attr, onReset: _reset, onAddBox: _onAddBox, onDelete: _deleteSelectIndex, onAttrChange: _onAttrChange, )), ], ); } void _reset({bool init=false}){ _attr = FlexAttr(direction: Axis.horizontal); _data = [ DisplayItem(width: 20, height: 20, color: kColors[0]), DisplayItem(width: 10, height: 80, color: kColors[1]), DisplayItem(width: 40, height: 30, color: kColors[2]), DisplayItem(width: 60, height: 20, color: kColors[3]), ]; if(init) return; setState(() {}); } void _onAttrChange(FlexAttr attr) { setState(() { _attr = attr; }); } void _onAddBox(Size size) { int index = _data.length + 1; Color color = kColors[index % kColors.length]; _data.add(DisplayItem(width: size.width, height: size.height, color: color)); setState(() {}); } void _deleteSelectIndex() { if (_selectIndex < 0 || _selectIndex >= _data.length) { $message.warning(message: '请先选择删除的色块!'); return; } _data.removeAt(_selectIndex); _selectIndex = -1; setState(() {}); } void _onSelectChanged(int value) { if (_selectIndex == value) { _selectIndex = -1; } else { _selectIndex = value; } setState(() {}); } } class FlexDisplay extends StatelessWidget { final List items; final FlexAttr attr; final int selectIndex; final ValueChanged onSelectChanged; const FlexDisplay({ super.key, required this.items, required this.attr, required this.selectIndex, required this.onSelectChanged, }); @override Widget build(BuildContext context) { return Flex( direction: attr.direction, mainAxisAlignment: attr.mainAxisAlignment, crossAxisAlignment: attr.crossAxisAlignment, mainAxisSize: attr.mainAxisSize, textDirection: attr.textDirection, verticalDirection: attr.verticalDirection, textBaseline: TextBaseline.alphabetic, children: items.asMap().keys.map((int index) { bool active = selectIndex == index; return GestureDetector( onTap: () => onSelectChanged(index), child: DisplayPlayItem(item: items[index], selected: active), ); }).toList(), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/view/form_item/item_selector.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-30 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; typedef NameCalc = String Function(T data); class ItemSelector extends StatelessWidget { final int selectIndex; final List data; final NameCalc calcFun; final ValueChanged onSelect; final String label; final String subTitle; const ItemSelector({ super.key, required this.selectIndex, required this.data, required this.calcFun, required this.onSelect, required this.subTitle, required this.label, }); @override Widget build(BuildContext context) { TextStyle labelStyle = const TextStyle(color: Color(0xff61666d), fontSize: 12); DropMenuCellStyle lightStyle = const DropMenuCellStyle( padding: EdgeInsets.symmetric(horizontal: 4,vertical: 1), borderRadius: BorderRadius.all(Radius.circular(6)), foregroundColor: Color(0xff1f1f1f), backgroundColor: Colors.transparent, disableColor: Color(0xffbfbfbf), hoverBackgroundColor: Color(0xfff5f5f5), hoverForegroundColor: Color(0xff1f1f1f), textStyle: TextStyle(fontSize: 11) ); return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: labelStyle), Text(subTitle, style: labelStyle.copyWith(fontSize: 8)), ], ), const Spacer(), TolySelect( fontSize: 11, cellStyle: lightStyle, data: data.map((e) => calcFun(e)).toList(), selectIndex: selectIndex, iconSize: 16, height: 25, width: 110, maxHeight: 200, shrinkWrapWidthOverlay: false, minWidth: 0, onSelected: (int index) => onSelect(data[index]), ) ], ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/view/form_item/item_size_input.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-29 // Contact Me: 1981462002@qq.com import 'package:flutter/cupertino.dart'; class ItemSizeInput extends StatelessWidget { final TextEditingController widthCtrl; final TextEditingController heightCtrl; const ItemSizeInput({ super.key, required this.widthCtrl, required this.heightCtrl, }); @override Widget build(BuildContext context) { TextStyle labelStyle = const TextStyle(color: Color(0xff61666d), fontSize: 12); return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Row( children: [ Text('宽高: ', style: labelStyle), const SizedBox(width: 20), Expanded( child: CupertinoTextField( controller: widthCtrl, style: const TextStyle(fontSize: 12), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), )), const Padding( padding: EdgeInsets.symmetric(horizontal: 4.0), child: Text( "x", style: TextStyle(fontSize: 12), ), ), Expanded( child: CupertinoTextField( controller: heightCtrl, style: const TextStyle(fontSize: 12), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), )) ], ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/view/form_item/value_input.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-30 // Contact Me: 1981462002@qq.com import 'package:flutter/cupertino.dart'; class ValueInput extends StatelessWidget { final String label; final String subtitle; final ValueChanged onChange; const ValueInput({ super.key, required this.onChange, required this.label, required this.subtitle, }); @override Widget build(BuildContext context) { TextStyle labelStyle = const TextStyle(color: Color(0xff61666d), fontSize: 12); return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: labelStyle), Text(subtitle, style: labelStyle.copyWith(fontSize: 8)), ], ), const Spacer(), SizedBox( width: 110, child: CupertinoTextField( keyboardType: TextInputType.number, onChanged: onChange, style: const TextStyle(fontSize: 12), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), ), ) ], ), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/view/stack/stack_op_panel.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-29 // Contact Me: 1981462002@qq.com import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import '../../data/stack_attr.dart'; import '../form_item/item_selector.dart'; import '../form_item/item_size_input.dart'; class StackOpTool extends StatefulWidget { final ValueChanged onAddBox; final VoidCallback onDelete; final VoidCallback onReset; final StackAttr attr; final ValueChanged onAttrChange; const StackOpTool({ super.key, required this.onAddBox, required this.onDelete, required this.onReset, required this.attr, required this.onAttrChange, }); @override State createState() => _StackOpToolState(); } class _StackOpToolState extends State { final TextEditingController _widthCtrl = TextEditingController(text: '24'); final TextEditingController _heightCtrl = TextEditingController(text: '64'); @override Widget build(BuildContext context) { TextStyle labelStyle = const TextStyle(color: Color(0xff61666d), fontSize: 12); return Column( children: [ const Padding( padding: EdgeInsets.all(8.0), child: Text( 'Flex 操作面板', style: TextStyle(fontWeight: FontWeight.bold), ), ), const Divider(), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), child: Row( children: [ Text('增删操作', style: labelStyle.copyWith(fontWeight: FontWeight.bold)), const Spacer(), TolyAction(child: const Icon(CupertinoIcons.add, size: 18), onTap: _handleAdd), TolyAction(child: const Icon(CupertinoIcons.delete, size: 16), onTap: widget.onDelete), ], ), ), ItemSizeInput(widthCtrl: _widthCtrl, heightCtrl: _heightCtrl), const SizedBox(height: 12), const Divider(), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), child: Row( children: [ Text('属性操作', style: labelStyle.copyWith(fontWeight: FontWeight.bold)), Spacer(), TolyAction(child: const Icon(CupertinoIcons.refresh, size: 16), onTap: widget.onReset), ], ), ), ItemSelector( label: '对齐方式:', subTitle: 'direction', selectIndex: widget.attr.alignment.index, data: AttrAlignment.values, calcFun: (AttrAlignment data) => data.name, onSelect: (AttrAlignment value) { widget.onAttrChange(widget.attr.copyWith(alignment: value)); }, ), ItemSelector( label: '适应模式:', subTitle: 'fit', selectIndex: widget.attr.fit.index, data: StackFit.values, calcFun: (StackFit data) => data.name, onSelect: (StackFit value) { widget.onAttrChange(widget.attr.copyWith(fit: value)); }, ), ItemSelector( label: '文字方向:', subTitle: 'textDirection', selectIndex: widget.attr.textDirection.index, data: TextDirection.values, calcFun: (TextDirection data) => data.name, onSelect: (TextDirection value) { widget.onAttrChange(widget.attr.copyWith(textDirection: value)); }, ), ], ); } void _handleAdd() { final double? width = double.tryParse(_widthCtrl.text); final double? height = double.tryParse(_heightCtrl.text); if (width == null || height == null) { $message.warning(message: '请输入合法宽高数字!'); return; } widget.onAddBox(Size(width, height)); } @override void dispose() { _widthCtrl.dispose(); _heightCtrl.dispose(); super.dispose(); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/view/stack/stack_playground.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-23 // Contact Me: 1981462002@qq.com import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import '../../cons.dart'; import '../../data/flex_attr.dart'; import '../../data/stack_attr.dart'; import '../display_item.dart'; import 'stack_op_panel.dart'; class StackPlayground extends StatefulWidget { const StackPlayground({super.key}); @override State createState() => _StackPlaygroundState(); } class _StackPlaygroundState extends State { List _data = []; StackAttr _attr = StackAttr(); int _selectIndex = -1; @override void initState() { super.initState(); _data = [ DisplayItem(width: 80, height: 80, color: kColors[3]), DisplayItem(width: 60, height: 60, color: kColors[2]), DisplayItem(width: 40, height: 40, color: kColors[1]), DisplayItem(width: 20, height: 20, color: kColors[0]), ]; } @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: Center( child: SizedBox( width: 200, height: 200, child: ColoredBox( color: Colors.grey.withOpacity(0.1), child: StackDisplay( items: _data, attr: _attr, selectIndex: _selectIndex, onSelectChanged: _onSelectChanged, )), ), )), const VerticalDivider(), SizedBox( width: 200, child: StackOpTool( attr: _attr, onReset: ()=>_onAttrChange(StackAttr()), onAddBox: _onAddBox, onDelete: _deleteSelectIndex, onAttrChange: _onAttrChange, )), ], ); } void _onAttrChange(StackAttr attr) { setState(() { _attr = attr; }); } void _onAddBox(Size size) { int index = _data.length + 1; Color color = kColors[index % kColors.length]; _data.add(DisplayItem(width: size.width, height: size.height, color: color)); print(size); setState(() {}); } void _onSelectChanged(int value) { if (_selectIndex == value) { _selectIndex = -1; } else { _selectIndex = value; } setState(() {}); } void _deleteSelectIndex() { if (_selectIndex < 0 || _selectIndex >= _data.length) { $message.warning(message: '请先选择删除的色块!'); return; } _data.removeAt(_selectIndex); _selectIndex = -1; setState(() {}); } } class StackDisplay extends StatelessWidget { final List items; final StackAttr attr; final int selectIndex; final ValueChanged onSelectChanged; const StackDisplay({ super.key, required this.items, required this.attr, required this.selectIndex, required this.onSelectChanged, }); @override Widget build(BuildContext context) { return Stack( fit: attr.fit, alignment: attr.alignment.value, textDirection: attr.textDirection, children: items.asMap().keys.map((int index) { bool active = selectIndex == index; return GestureDetector( onTap: () => onSelectChanged(index), child: DisplayPlayItem(item: items[index], selected: active), ); }).toList(), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/view/wrap/wrap_op_panel.dart ================================================ // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-29 // Contact Me: 1981462002@qq.com import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:layout/src/views/playground/view/form_item/value_input.dart'; import 'package:tolyui/tolyui.dart'; import '../../data/wrap_attr.dart'; import '../form_item/item_selector.dart'; import '../form_item/item_size_input.dart'; class WrapOpTool extends StatefulWidget { final ValueChanged onAddBox; final VoidCallback onDelete; final WrapAttr attr; final ValueChanged onAttrChange; final VoidCallback onReset; const WrapOpTool({ super.key, required this.onAddBox, required this.onDelete, required this.onReset, required this.attr, required this.onAttrChange, }); @override State createState() => _WrapOpToolState(); } class _WrapOpToolState extends State { final TextEditingController widthCtrl = TextEditingController(text: '24'); final TextEditingController heightCtrl = TextEditingController(text: '64'); final TextEditingController spacingCtrl = TextEditingController(text: "0"); final TextEditingController rubSpacingCtrl = TextEditingController(text: "0"); @override Widget build(BuildContext context) { TextStyle labelStyle = TextStyle(color: Color(0xff61666d), fontSize: 12); return Column( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text( 'Wrap 操作面板', style: TextStyle(fontWeight: FontWeight.bold), ), ), Divider(), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), child: Row( children: [ Text('增删操作', style: labelStyle.copyWith(fontWeight: FontWeight.bold)), Spacer(), TolyAction(child: Icon(CupertinoIcons.add, size: 18), onTap: _handleAdd), TolyAction(child: Icon(CupertinoIcons.delete, size: 16), onTap: widget.onDelete), ], ), ), ItemSizeInput( widthCtrl: widthCtrl, heightCtrl: heightCtrl, ), const SizedBox(height: 12), Divider(), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), child: Row( children: [ Text('属性操作', style: labelStyle.copyWith(fontWeight: FontWeight.bold)), Spacer(), TolyAction( child: const Icon(CupertinoIcons.refresh, size: 16), onTap: widget.onReset), ], ), ), ItemSelector( label: '排列方向:', subTitle: 'direction', selectIndex: widget.attr.direction.index, data: Axis.values, calcFun: (Axis data) => data.name, onSelect: (Axis value) { widget.onAttrChange(widget.attr.copyWith(direction: value)); }, ), ItemSelector( label: '主轴对齐:', subTitle: 'mainAlignment', selectIndex: widget.attr.mainAlignment.index, data: WrapAlignment.values, calcFun: (WrapAlignment data) => data.name, onSelect: (WrapAlignment value) { widget.onAttrChange(widget.attr.copyWith(mainAlignment: value)); }, ), ItemSelector( label: '交叉轴对齐:', subTitle: 'crossAxisAlignment', selectIndex: widget.attr.crossAxisAlignment.index, data: WrapCrossAlignment.values, calcFun: (WrapCrossAlignment data) => data.name, onSelect: (WrapCrossAlignment value) { widget.onAttrChange(widget.attr.copyWith(crossAxisAlignment: value)); }, ), ItemSelector( label: '叉轴对齐:', subTitle: 'runAlignment', selectIndex: widget.attr.runAlignment.index, data: WrapAlignment.values, calcFun: (WrapAlignment data) => data.name, onSelect: (WrapAlignment value) { widget.onAttrChange(widget.attr.copyWith(runAlignment: value)); }, ), ItemSelector( label: '垂直方向:', subTitle: 'verticalDirection', selectIndex: widget.attr.verticalDirection.index, data: VerticalDirection.values, calcFun: (VerticalDirection data) => data.name, onSelect: (VerticalDirection value) { widget.onAttrChange(widget.attr.copyWith(verticalDirection: value)); }, ), ItemSelector( label: '文字方向:', subTitle: 'textDirection', selectIndex: widget.attr.textDirection.index, data: TextDirection.values, calcFun: (TextDirection data) => data.name, onSelect: (TextDirection value) { widget.onAttrChange(widget.attr.copyWith(textDirection: value)); }, ), ValueInput( label: '主轴间距:', subtitle: 'spacing', onChange: (String v) { widget.onAttrChange(widget.attr.copyWith(spacing: double.tryParse(v))); }, ), ValueInput( label: '叉轴间距:', subtitle: 'runSpacing', onChange: (String v) { widget.onAttrChange(widget.attr.copyWith(runSpacing: double.parse(v))); }, ), ], ); } void _handleAdd() { final double? width = double.tryParse(widthCtrl.text); final double? height = double.tryParse(heightCtrl.text); if (width == null || height == null) { $message.warning(message: '请输入合法宽高数字!'); return; } widget.onAddBox(Size(width, height)); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/playground/view/wrap/wrap_playground.dart ================================================ // Copyright 2014 The 星星 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 星星 // CreateTime: 2024-06-25 // Contact Me: 1981462002@qq.com // Copyright 2014 The 张风捷特烈 . All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Author: 张风捷特烈 // CreateTime: 2024-06-23 // Contact Me: 1981462002@qq.com import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import '../../data/wrap_attr.dart'; import '../display_item.dart'; import '../form_item/item_size_input.dart'; import 'wrap_op_panel.dart'; const List kColors = [ Color(0xffd23eb9), Color(0xff2164c7), Color(0xffd5a213), Color(0xff16e848), ]; class WrapPlayground extends StatefulWidget { const WrapPlayground({super.key}); @override State createState() => _WrapPlaygroundState(); } class _WrapPlaygroundState extends State { List _data = []; late WrapAttr _attr; int _selectIndex = -1; @override void initState() { super.initState(); _reset(); } @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: Center( child: ColoredBox( color: Colors.grey.withOpacity(0.1), child: WrapDisplay( items: _data, attr: _attr, selectIndex: _selectIndex, onSelectChanged: _onSelectChanged, )))), const VerticalDivider(), SizedBox( width: 210, child: WrapOpTool( attr: _attr, onAddBox: _onAddBox, onDelete: _deleteSelectIndex, onAttrChange: _onAttrChange, onReset: _reset, )), ], ); } void _reset() { _attr = const WrapAttr(); _data = [ DisplayItem(width: 20, height: 20, color: kColors[0]), DisplayItem(width: 10, height: 80, color: kColors[1]), DisplayItem(width: 40, height: 30, color: kColors[2]), DisplayItem(width: 60, height: 20, color: kColors[3]), ]; setState(() {}); } void _onAttrChange(WrapAttr attr) { _attr = attr; setState(() {}); } void _onAddBox(Size size) { int index = _data.length + 1; Color color = kColors[index % kColors.length]; _data.add(DisplayItem(width: size.width, height: size.height, color: color)); print(size); setState(() {}); } void _onSelectChanged(int value) { if (_selectIndex == value) { _selectIndex = -1; } else { _selectIndex = value; } setState(() {}); } void _deleteSelectIndex() { if (_selectIndex < 0 || _selectIndex >= _data.length) { $message.warning(message: '请先选择删除的色块!'); return; } _data.removeAt(_selectIndex); _selectIndex = -1; setState(() {}); } } class WrapDisplay extends StatelessWidget { final List items; final WrapAttr attr; final int selectIndex; final ValueChanged onSelectChanged; const WrapDisplay({ super.key, required this.items, required this.attr, required this.selectIndex, required this.onSelectChanged, }); @override Widget build(BuildContext context) { return Wrap( direction: attr.direction, alignment: attr.mainAlignment, crossAxisAlignment: attr.crossAxisAlignment, spacing: attr.spacing, textDirection: attr.textDirection, verticalDirection: attr.verticalDirection, runAlignment: attr.runAlignment, runSpacing: attr.runSpacing, children: items.asMap().keys.map((int index) { bool active = selectIndex == index; return GestureDetector( onTap: () => onSelectChanged(index), child: DisplayPlayItem(item: items[index], selected: active), ); }).toList(), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/popable/autocomplete_demo.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; class AutocompleteDemo extends StatelessWidget { const AutocompleteDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: Center( child: Autocomplete( optionsBuilder: buildOptions, onSelected: onSelected, ), ), ); } void onSelected(String selection) { debugPrint('当前选择了 $selection'); } Future> buildOptions( TextEditingValue textEditingValue, ) async { if (textEditingValue.text == '') { return const Iterable.empty(); } return searchByArgs(textEditingValue.text); } Future> searchByArgs(String args) async{ // 模拟网络请求 await Future.delayed(const Duration(milliseconds: 200)); const List data = [ 'toly', 'toly49', 'toly42', 'toly56', 'card', 'ls', 'alex', 'fan sha', ]; return data.where((String name) => name.contains(args)); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/popable/dropdown_button_demo.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020-03-16 /// contact me by email 1981462002@qq.com /// 说明: // { // "widgetId": 55, // "name": 'DropdownButton基本用法', // "priority": 1, // "subtitle": // "【value】 : 当前值 【T】\n" // "【items】 : 下拉选框 【List>】\n" // "【icon】 : 图标 【Widget】\n" // "【elevation】 : 影深 【double】\n" // "【onChanged】 : 选择条目事件 【Function(T)】\n" // "【backgroundColor】 : 背景色 【Color】", // } class CustomDropDownButton extends StatefulWidget { const CustomDropDownButton({Key? key}) : super(key: key); @override _CustomDropDownButtonState createState() => _CustomDropDownButtonState(); } class _CustomDropDownButtonState extends State { Color _color = Colors.red; final List _colors = const [ Colors.red, Colors.yellow, Colors.blue, Colors.green ]; final List _info = const ["红色", "黄色", "蓝色", "绿色"]; @override Widget build(BuildContext context) { return Wrap( children: [ Container( margin: const EdgeInsets.symmetric(horizontal: 20), width: 50, height: 50, color: _color, ), DropdownButton( value: _color, elevation: 1, icon: Icon( Icons.expand_more, size: 20, color: _color, ), items: _buildItems(), onChanged: (v) => setState(() => _color = v??Colors.blue)), ], ); } List> _buildItems() => _colors .map((e) => DropdownMenuItem( value: e, child: Text( _info[_colors.indexOf(e)], style: TextStyle(color: e), ))) .toList(); } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/popable/dropdown_menu_demo.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/9/21 /// contact me by email 1981462002@qq.com /// 说明: 370 DropdownMenu 表单菜单 /// 下拉选择组件,支持文本输入过滤,可自定义菜单项。底层主要依赖 MenuAnchor 和 TextFiled 实现。 /// link: 55 // { // "widgetId": 370, // "name": '下拉菜单的简单使用', // "priority": 1, // "subtitle": // "【dropdownMenuEntries】 : 菜单条目列表 【List>】\n" // "【initialSelection】 : 表单验证回调 【T?】\n" // "【onSelected】 : 表单保存回调 【ValueChanged?】\n" // "【menuHeight】 : 菜单高度 【double】\n" // "【width】 : 输入框宽度 【double】", // } class DropdownMenuNode1 extends StatefulWidget { const DropdownMenuNode1({super.key}); @override State createState() => _DropdownMenuNode1State(); } class _DropdownMenuNode1State extends State { final List data = ['语文', '数学', '英语', '物理', '化学', '生物', '地理']; late String _dropdownValue = data.first; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField(), // DropdownMenu( // requestFocusOnTap: false, // menuHeight: 200, // initialSelection: data.first, // onSelected: _onSelect, // dropdownMenuEntries: _buildMenuList(data), // ), const SizedBox(height: 8,), Text('你选择的学科是: $_dropdownValue') ], ), ); } void _onSelect(String? value) { // setState(() { // _dropdownValue = value!; // }); } List> _buildMenuList(List data) { return data.map((String value) { return DropdownMenuEntry(value: value, label: value); }).toList(); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/scroll/grid_view/grid_view_demo01.dart ================================================ import 'package:flutter/material.dart'; class GridViewDemo01 extends StatelessWidget { GridViewDemo01({super.key}); final List data = List.generate(128, (i) => Color(0xFFFF00FF - 2 * i)); @override Widget build(BuildContext context) { return SizedBox( height: 200, child: GridView.count( crossAxisCount: 4, mainAxisSpacing: 2, crossAxisSpacing: 2, childAspectRatio: 1 / 0.618, children: data.map((color) => _buildItem(color)).toList(), ), ); } Container _buildItem(Color color) => Container( alignment: Alignment.center, width: 100, height: 30, color: color, child: Text( colorString(color), style: const TextStyle( color: Colors.white, shadows: [ Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2) ], ), ), ); String colorString(Color color) => "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/scroll/list_view/list_view_demo01.dart ================================================ import 'package:flutter/material.dart'; class ListViewDemo01 extends StatelessWidget { const ListViewDemo01({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: ListView.builder( itemCount: 100, itemBuilder: (_, index) { return Card( child: ListTile( tileColor: Colors.transparent, title: Text('Test index:$index'), )); }), ); } } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/scroll/page_view/page_view_demo01.dart ================================================ import 'package:flutter/material.dart'; const List kColors3 = [Colors.blue,Colors.red, Colors.green, Colors.orange]; class PageViewDemo01 extends StatelessWidget { PageViewDemo01({super.key}); final List data = List.generate(128, (i) => Color(0xFFFF00FF - 2 * i)); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: PageView.builder( itemCount: 8, itemBuilder: (_, index) { Color color = kColors3[index % kColors3.length]; return Container( color: color, alignment: Alignment.center, child: Text( 'Page ${index + 1}\n${colorString(color)}', textAlign: TextAlign.center, style: TextStyle(fontSize: 18, color: Colors.white), ), ); }), ); } String colorString(Color color) => "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; } ================================================ FILE: modules/knowledge_system/layout/lib/src/views/test_show.dart ================================================ import 'package:flutter/material.dart'; class TextShow extends StatelessWidget { final String info; const TextShow({super.key, required this.info}); @override Widget build(BuildContext context) { return Material( color: Colors.white, child: Center(child: Text(info)), ); } } ================================================ FILE: modules/knowledge_system/layout/pubspec.yaml ================================================ name: layout description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/knowledge_system/layout/test/layout_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:layout/layout.dart'; void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); } ================================================ FILE: modules/knowledge_system/note/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies build/ ================================================ FILE: modules/knowledge_system/note/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "c519ee916eaeb88923e67befb89c0f1dabfa83e6" channel: "stable" project_type: package ================================================ FILE: modules/knowledge_system/note/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/knowledge_system/note/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/knowledge_system/note/README.md ================================================ #### 匠心巧记 模块 打造便捷使用的 [全端同步] 笔记 ================================================ FILE: modules/knowledge_system/note/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/knowledge_system/note/lib/note.dart ================================================ export 'src/view/view.dart'; export 'src/bloc/bloc.dart'; export 'src/bloc/news_bloc.dart'; export 'src/repository/repository.dart'; export 'src/env/env.dart'; export 'package:flutter_quill/flutter_quill.dart' show FlutterQuillLocalizations; ================================================ FILE: modules/knowledge_system/note/lib/src/bloc/bloc.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fx_dio/fx_dio.dart'; import 'package:note/src/repository/article_repository.dart'; import 'package:note/src/repository/model/article.dart'; import '../repository/model/model.dart'; class ArtSysBloc extends Cubit { ArtSysBloc() : super(ArtSysState(articles: [])); final ArticleRepository _repository = HttpArticleRepository(); TextEditingController titleCtrl = TextEditingController(); TextEditingController ctrl = TextEditingController(); Future loadFirstFrame() async { if (state.articles.isEmpty) { emit(state.copyWith(status: const LoadingStatus())); } ApiRet> ret = await _repository.list(SizeFilter()); if (ret.success) { ArtSysState newState = state.copyWith( articles: ret.data, status: SuccessStatus(ret.paginate?.total ?? 0), ); emit(newState); _openCurrent(); return; } print(ret.trace?.toString()); ArtSysState newState = state.copyWith( status: FailedStatus(ret.trace?.error, ret.trace?.stack), ); emit(newState); } void _openCurrent() { int? id = state.active?.id; if (id != null) { open(id); } } Future newArticle() async { await _repository.create( ArticleCreatePayload( subtitle: '', title: '新建文档', url: '', cover: '', type: 1, createAt: DateTime.timestamp().toIso8601String(), ), ); await loadFirstFrame(); } void select(ArticlePo article) { if (article.type == 1) { open(article.id); } else {} titleCtrl.text = article.title; emit(state.copyWith(active: article)); } void open(int id) async { ApiRet ret = await _repository.open(id); if (ret.success) { ctrl.text = ret.data; } } void write(String content) async { int? id = state.active?.id; if (id != null) { ApiRet ret = await _repository.write(id, content); } } void updateTitleV2() { ArticlePo? article = state.active; String title = titleCtrl.text; if (article != null) { updateTitle(article, title); } } void updateTitle(ArticlePo article, String title) async { if (title == article.title) return; ApiRet ret = await _repository.update( article.id, ArticleUpdatePayload(title: title)); if (ret.success) { open(article.id); loadFirstFrame(); titleCtrl.text = ret.data.title; } else { print(ret.trace?.error); } } Future delete() async { int? id = state.active?.id; if (id != null) { ApiRet ret = await _repository.delete(id); await loadFirstFrame(); } } } sealed class ListStatus { const ListStatus(); } class LoadingStatus extends ListStatus { const LoadingStatus(); } class SuccessStatus extends ListStatus { final int total; const SuccessStatus(this.total); } class FailedStatus extends ListStatus { final Object? error; final StackTrace? trace; const FailedStatus(this.error, [this.trace]); } class ArtSysState { final List articles; final ArticlePo? active; final ListStatus status; ArtSysState({ required this.articles, this.active, this.status = const LoadingStatus(), }); ArtSysState copyWith({ List? articles, ArticlePo? active, ListStatus? status, }) { return ArtSysState( articles: articles ?? this.articles, active: active ?? this.active, status: status ?? this.status, ); } } ================================================ FILE: modules/knowledge_system/note/lib/src/bloc/news_bloc.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fx_dao/src/table/dao.dart'; import 'package:note/note.dart'; import 'package:fx_dio/fx_dio.dart'; import 'package:app/app.dart'; class NewsBloc extends Cubit with Cacheable>, TimeoutCache> { NewsBloc() : super(NewsState(headerNews: [])); final ArticleRepository _repository = HttpArticleRepository(); void initByCache() async { List? retCache = await find(shouldRemove: false); if (retCache != null) { emit(NewsState(headerNews: retCache)); return; } } @override String get cacheKey => 'flutter.unit.news'; void load() async { List? retCache = await find(); if (retCache != null) { print("=====load in cache========="); emit(NewsState(headerNews: retCache)); return; } refreshFromNet(); } Future refreshFromNet() async { SizeFilter filter = const SizeFilter( page: 1, pageSize: 8, ); ApiRet> ret = await _repository.getArticlesByTag(1, filter: filter); print("=====load in net========="); if (ret.success) { save(ret.data); emit(NewsState(headerNews: ret.data)); } } @override ConvertorList> get convertor => (e) { return e.map(ArticlePo.fromCache).toList(); }; } class NewsState { final List headerNews; NewsState({ required this.headerNews, }); } ================================================ FILE: modules/knowledge_system/note/lib/src/env/env.dart ================================================ import 'package:fx_dio/fx_dio.dart'; class NoteEnv with NoteModuleBridge { static NoteEnv? _instance; NoteEnv._(); factory NoteEnv() { _instance ??= NoteEnv._(); return _instance!; } NoteModuleBridge? _bridge; void attachBridge(NoteModuleBridge bridge) { _bridge = bridge; } @override Host get host => _bridge!.host; } mixin NoteModuleBridge { Host get host; } ================================================ FILE: modules/knowledge_system/note/lib/src/repository/article_repository.dart ================================================ import 'dart:convert'; import 'package:fx_dio/fx_dio.dart'; import '../env/env.dart'; import 'model/model.dart'; typedef PaginateList = ({List list, int total}); abstract class ArticleRepository { Future> create(ArticleCreatePayload payload); Future> open(int id); Future> write(int id, String content); Future>> list(SizeFilter filter); /// 根据标签查询文章列表 /// [tagId] 标签 id /// [filter] 分页信息 Future>> getArticlesByTag( int tagId, { SizeFilter? filter, }); Future> delete(int id); Future> update(int id, ArticleUpdatePayload payload); Future> loadArticleTree(); } class HttpArticleRepository implements ArticleRepository { Host get host => NoteEnv().host; @override Future> create(ArticleCreatePayload payload) { return host.post( '/article', data: payload.apiData, convertor: (rep) => rep['status'] == true, ); } @override Future> delete(int id) { return host.delete( '/article/$id', convertor: (rep) => rep['status'] == true, ); } @override Future>> list(SizeFilter filter) { return host.get>( '/article', queryParameters: { 'page': filter.page, 'page_size': filter.pageSize, }, convertor: (data) { return data.map(ArticlePo.fromApi).toList(); }, ); } @override Future> open(int id) { return host.get( '/article/open/$id', convertor: (rep) => rep, ); } @override Future> write(int id, String content) { return host.post( '/article/write', data: { 'article_id': id, 'content': content, }, convertor: (rep) { return rep; }, ); } @override Future> update(int id, ArticleUpdatePayload payload) { return host.put( '/article/$id', data: payload.apiData, convertor: (rep) { return ArticlePo.fromApi(rep); }, ); } @override Future> loadArticleTree() { return host.get( '/category', convertor: (rep) { return Hierarchy.fromJson(rep['data']); }, ); } @override Future>> getArticlesByTag(int tagId, {SizeFilter? filter}) { SizeFilter size = filter ?? const SizeFilter(); return host.get>( '/article/tag', queryParameters: { 'tag_id': tagId, 'page': size.page, 'page_size': size.pageSize, }, convertor: (data) => data.map(ArticlePo.fromApi).toList(), ); } } ================================================ FILE: modules/knowledge_system/note/lib/src/repository/model/article.dart ================================================ import 'package:fx_dao/fx_dao.dart'; import 'package:intl/intl.dart'; DateFormat _noteTimeShort = DateFormat('yyyy/M/d'); DateFormat _noteTimeLong = DateFormat('yyyy/M/d HH:mm:ss'); Duration offset = DateTime.now().timeZoneOffset; enum ArticleType { net, custom, } class ArticlePo implements Po { final String title; final String? subtitle; final String url; final String? cover; final int create; final int update; final int id; final int status; final int type; ArticlePo({ required this.title, this.subtitle = '', required this.url, this.cover = '', this.update = 0, this.create = 0, this.status = 0, this.type = 0, this.id = -1, }); String get updateDate { return _noteTimeLong .format(DateTime.fromMillisecondsSinceEpoch(update).add(offset)); } String get createDate => _noteTimeShort .format(DateTime.fromMillisecondsSinceEpoch(create).add(offset)); factory ArticlePo.fromApi(dynamic map) => ArticlePo( id: map['article_id'] ?? 0, title: map['title'] ?? '', type: map['type'] ?? '', status: map['status'] ?? '', create: DateTime.parse(map['create_at']).millisecondsSinceEpoch, update: DateTime.parse(map['update_at']).millisecondsSinceEpoch, subtitle: map['subtitle'] ?? '', url: map['url'] ?? '', cover: map['cover'] ?? '', ); factory ArticlePo.fromCache(dynamic map) => ArticlePo( id: map['article_id'] ?? 0, title: map['title'] ?? '', type: map['type'] ?? '', status: map['status'] ?? '', create: map['create_at'], update: map['update'], subtitle: map['subtitle'] ?? '', url: map['url'] ?? '', cover: map['cover'] ?? '', ); @override Map toJson() { return { 'article_id': id, 'title': title, 'type': type, 'status': status, 'create_at': create, 'update': update, 'url': url, 'cover': cover, }; } } class ArticleCreatePayload { final String subtitle; final String title; final String url; final int type; final String cover; final String createAt; ArticleCreatePayload({ required this.subtitle, required this.title, required this.url, required this.type, required this.cover, required this.createAt, }); Map get apiData => { "title": title, "create_at": createAt, "subtitle": subtitle, "url": url, "type": type, "cover": cover, }; Map toJson() => apiData; } class ArticleUpdatePayload { final String? subtitle; final String? title; final String? url; final String? cover; ArticleUpdatePayload({ this.subtitle, this.title, this.url, this.cover, }); Map get apiData { Map ret = {}; if (title != null) { ret['title'] = title; } if (url != null) { ret['url'] = title; } if (subtitle != null) { ret['subtitle'] = title; } if (cover != null) { ret['cover'] = title; } return ret; } Map toJson() => apiData; } ================================================ FILE: modules/knowledge_system/note/lib/src/repository/model/category.dart ================================================ class Hierarchy { final int cateId; final String name; final int cateType; final int priority; final List children; Hierarchy({ required this.cateId, required this.name, this.cateType = 0, this.priority = 0, this.children = const [], }); factory Hierarchy.fromJson(Map json) { return Hierarchy( cateId: json['cate_id'] as int, name: json['name'] as String, cateType: json['cate_type'] as int? ?? 0, priority: json['priority'] as int? ?? 0, children: (json['children'] as List?) ?.map((child) => Hierarchy.fromJson(child)) .toList() ?? [], ); } } ================================================ FILE: modules/knowledge_system/note/lib/src/repository/model/model.dart ================================================ export 'status.dart'; export 'article.dart'; export 'category.dart'; export 'query.dart'; ================================================ FILE: modules/knowledge_system/note/lib/src/repository/model/query.dart ================================================ class SizeFilter { final int pageSize; final int page; const SizeFilter({ this.pageSize = 20, this.page = 1, }); } ================================================ FILE: modules/knowledge_system/note/lib/src/repository/model/status.dart ================================================ sealed class TaskStatus { const TaskStatus(); } class TaskNone extends TaskStatus { const TaskNone(); } class TaskLoading extends TaskStatus { const TaskLoading(); } class TaskSuccess extends TaskStatus { const TaskSuccess(); } class TaskFailed extends TaskStatus { final Object? error; final StackTrace? trace; const TaskFailed(this.error, [this.trace]); } ================================================ FILE: modules/knowledge_system/note/lib/src/repository/repository.dart ================================================ export 'article_repository.dart'; export 'model/model.dart'; ================================================ FILE: modules/knowledge_system/note/lib/src/view/art_sys_scope.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:note/note.dart'; class ArtSysScope extends StatelessWidget { final Widget child; const ArtSysScope({super.key, required this.child}); @override Widget build(BuildContext context) { return BlocProvider( create: (_) => ArtSysBloc()..loadFirstFrame(), child: child, ); } } ================================================ FILE: modules/knowledge_system/note/lib/src/view/article_admin.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:note/note.dart'; import 'package:tolyui/tolyui.dart'; import 'package:app/app.dart'; import 'article_editor.dart'; import 'article_list.dart'; import 'desktop/article_display.dart'; class ArticleAdmin extends StatefulWidget { const ArticleAdmin({super.key}); @override State createState() => _ArticleAdminState(); } class _ArticleAdminState extends State { @override Widget build(BuildContext context) { ArtSysBloc bloc = context.watch(); ListStatus status = bloc.state.status; bool hasActive = bloc.state.active != null; Widget table = switch (status) { LoadingStatus() => const CupertinoActivityIndicator(), SuccessStatus() => ArticleList( articles: bloc.state.articles, activeId: bloc.state.active?.id ?? -1, onTap: bloc.select, onUpdateTitle: bloc.updateTitle, ), FailedStatus() => Text("Error:${status.error}"), }; return Scaffold( backgroundColor: Colors.white, body: Row( children: [ Container( width: 240, decoration: BoxDecoration(color: Color(0xfffafbfc) // gradient: LinearGradient(colors: [ // Color(0xffe9f1f8), // Color(0xffebf2f8), // ]) ), child: Column( children: [ DragToMoveWrapper( child: Container( padding: EdgeInsets.symmetric(horizontal: 12), height: 46, child: Row( spacing: 6, children: [ Icon( Icons.note_alt_outlined, color: Theme.of(context).primaryColor, ), Text( '匠心巧记', style: TextStyle( fontWeight: FontWeight.bold, color: Color(0xff242a39)), ), Spacer(), TolyAction( child: Icon( Icons.sync, size: 20, color: Color(0xff242a39), ), onTap: () async { bloc.loadFirstFrame(); }, ), ], ), ), ), Row( children: [ Expanded( child: Padding( padding: EdgeInsets.symmetric(horizontal: 12), child: ElevatedButton( onPressed: bloc.newArticle, child: Wrap( spacing: 6, crossAxisAlignment: WrapCrossAlignment.center, children: [ Icon( Icons.add, color: Colors.white, ), Text( "新建", style: TextStyle(fontSize: 12), ), ], ), style: FillButtonPalette( padding: EdgeInsets.symmetric(vertical: 0), foregroundPalette: Palette.all(Colors.white), borderRadius: BorderRadius.circular(6), backgroundPalette: const Palette( normal: Color(0xff1890ff), hover: Color(0xff40a9ff), pressed: Color(0xff096dd9), ), ).style, ), ), ), ], ), const SizedBox(height: 16), Expanded(child: table) ], ), ), VerticalDivider(), Expanded( child: Column( children: [ Container( height: 46, child: Row( children: [ if (hasActive) Expanded( child: Padding( padding: const EdgeInsets.only(left: 12.0), child: TextField( onTapOutside: (_) => bloc.updateTitleV2(), onSubmitted: (_) => bloc.updateTitleV2(), controller: bloc.titleCtrl, decoration: InputDecoration(border: InputBorder.none), style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), if (!hasActive) Spacer(), WindowButtons() ], ), ), Divider(), // Expanded(child: RichEditor()), Expanded(child: ArticleDisplay()), ], )) // Expanded( // child: Column( // children: [ // Container( // height: 52, // child: Padding( // padding: const EdgeInsets.symmetric(horizontal: 12.0), // child: Row( // children: [ // Spacer(), // Button(onPressed: () => showAddDialog(context)), // ], // ), // ), // ), // Expanded(child: table), // Padding( // padding: const EdgeInsets.all(8.0), // child: TolyPagination( // pageSize: 20, // total: (total*1.0), // initIndex: currentIndex, // onPageChanged: _onPageChanged, // ), // ) // ], // ), // ), ], ), ); } // void _onPageChanged(int value) { // queryArticle(value); // } // Future queryArticle(int page) async { // setState(() { // status = const TaskLoading(); // }); // ApiRet> ret = // await _repository.list(SizeFilter(page: page)); // if (ret.success) { // articles = ret.data.list; // total = ret.data.total; // setState(() { // status = const TaskSuccess(); // }); // } else { // status = TaskFailed(ret.trace); // setState(() {}); // } // } void showAddDialog(BuildContext context) { showDialog( context: context, builder: (context) { return EditArticleDialog( onCreate: (payload) async { // 在这里处理更新后的文章 // ApiRet ret = await _repository.create(payload); // if (ret.success) { // currentIndex = 1; // queryArticle(currentIndex); // } }, ); }, ); } } class RichEditor extends StatefulWidget { const RichEditor({super.key}); @override State createState() => _RichEditorState(); } class _RichEditorState extends State { QuillController _controller = QuillController.basic(); @override Widget build(BuildContext context) { return Column( children: [ QuillSimpleToolbar( controller: _controller, config: const QuillSimpleToolbarConfig(), ), Expanded( child: QuillEditor.basic( controller: _controller, config: const QuillEditorConfig(), ), ) ], ); } } ================================================ FILE: modules/knowledge_system/note/lib/src/view/article_editor.dart ================================================ import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; import '../repository/model/model.dart'; class EditArticleDialog extends StatefulWidget { final ArticlePo? article; final Function(ArticlePo po)? onSave; final Function(ArticleCreatePayload payload)? onCreate; const EditArticleDialog({ this.article, this.onSave, this.onCreate, }); @override _EditArticleDialogState createState() => _EditArticleDialogState(); } class _EditArticleDialogState extends State { late TextEditingController _titleController; late TextEditingController _subtitleController; late TextEditingController _urlController; late TextEditingController _coverController; late TextEditingController _columnController; @override void initState() { super.initState(); if (widget.article != null) { _titleController = TextEditingController(text: widget.article!.title); _subtitleController = TextEditingController(text: widget.article!.subtitle); _urlController = TextEditingController(text: widget.article!.url); _coverController = TextEditingController(text: widget.article!.cover); _columnController = TextEditingController(); } else { _titleController = TextEditingController(); _subtitleController = TextEditingController(); _urlController = TextEditingController(); _coverController = TextEditingController(); _columnController = TextEditingController(); } } @override void dispose() { _titleController.dispose(); _subtitleController.dispose(); _urlController.dispose(); _coverController.dispose(); super.dispose(); } bool get editModel => widget.article != null; @override Widget build(BuildContext context) { Palette foreground = const Palette( normal: Color(0xff606266), hover: Color(0xff096dd9), pressed: Color(0xff096dd9)); Palette border = const Palette( normal: Color(0xffd9d9d9), hover: Color(0x44409eff), pressed: Color(0xff096dd9)); Palette bg = const Palette( normal: Color(0xff1890ff), hover: Color(0xffecf5ff), pressed: Color(0xffecf5ff)); return AlertDialog( title: Text( editModel ? '编辑文章' : '新增文章', style: TextStyle(fontSize: 16), ), content: SizedBox( width: 420, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, spacing: 8, children: [ Row( children: [ Text('文章标题: '), const SizedBox(width: 6), Expanded( child: TolyInput( controller: _titleController, ), ), ], ), // TextField( // controller: _titleController, // decoration: InputDecoration(labelText: 'Title'), // ), Row( children: [ Text('文章摘要: '), const SizedBox(width: 6), Expanded( child: TolyInput( controller: _subtitleController, ), ), ], ), Row( children: [ Text('文章链接: '), const SizedBox(width: 6), Expanded( child: TolyInput( controller: _urlController, ), ), ], ), Row( children: [ Text('专栏收录: '), const SizedBox(width: 6), Expanded( child: TolyInput( controller: _columnController, ), ), ], ), if (_coverController.text.isNotEmpty) Image.network(_coverController.text), if (_coverController.text.isEmpty) Row( children: [ Text('文章封面: '), const SizedBox(width: 6), Expanded( child: TolyInput( controller: _coverController, ), ), ], ), ], ), ), ), actions: [ ElevatedButton( onPressed: () { Navigator.of(context).pop(); }, child: Text("取消"), style: OutlineButtonPalette( foregroundPalette: foreground, borderPalette: border, backgroundPalette: bg, ).style, ), ElevatedButton( onPressed: () { ArticleCreatePayload payload = ArticleCreatePayload( subtitle: _subtitleController.text, title: _titleController.text, url: _urlController.text, cover: _coverController.text, type: 1, createAt: DateTime.now().toIso8601String(), ); widget.onCreate?.call(payload); }, child: Wrap( spacing: 6, crossAxisAlignment: WrapCrossAlignment.center, children: [ Text( "保存", style: TextStyle(fontSize: 14), ), ], ), style: FillButtonPalette( padding: EdgeInsets.symmetric(vertical: 0), foregroundPalette: Palette.all(Colors.white), borderRadius: BorderRadius.circular(6), backgroundPalette: const Palette( normal: Color(0xff1890ff), hover: Color(0xff40a9ff), pressed: Color(0xff096dd9), ), ).style, ) ], ); } } ================================================ FILE: modules/knowledge_system/note/lib/src/view/article_item.dart ================================================ // import 'package:flutter/material.dart'; // import 'package:tolyui/tolyui.dart'; // // import '../repository/model/model.dart'; // import 'article_editor.dart'; // // class ArticleItem extends StatelessWidget { // final ArticlePo article; // final int index; // const ArticleItem({super.key, required this.article, required this.index}); // // @override // Widget build(BuildContext context) { // return Container( // color: index % 2 == 0 ? const Color(0xfff7f9f9) : Colors.white, // padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12), // child: Row( // children: [ // Text( // article.title, // style: const TextStyle(fontWeight: FontWeight.bold), // ), // const Spacer(), // TolyAction( // child: const Icon(Icons.edit, size: 20), // onTap: () { // showDialog( // context: context, // builder: (context) { // return EditArticleDialog( // article: article, // onSave: (updatedArticle) { // // 在这里处理更新后的文章 // // print('Updated Article: ${updatedArticle.}'); // }, // ); // }, // ); // }) // ], // ), // ); // } // } // ================================================ FILE: modules/knowledge_system/note/lib/src/view/article_list.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/src/gestures/events.dart'; import 'package:note/src/repository/model/model.dart'; import 'package:tolyui/tolyui.dart'; class ArticleList extends StatelessWidget { final List articles; final ValueChanged onTap; final int activeId; final OnUpdateTitle onUpdateTitle; const ArticleList( {super.key, required this.articles, required this.activeId, required this.onTap, required this.onUpdateTitle}); @override Widget build(BuildContext context) { return ListView.builder( itemCount: articles.length, itemBuilder: (_, index) => ArticleItem( onTap: onTap, active: articles[index].id == activeId, article: articles[index], onUpdateTitle: onUpdateTitle, ), ); } } typedef OnUpdateTitle = Function(ArticlePo article, String title); class ArticleItem extends StatefulWidget { final bool active; final ArticlePo article; final ValueChanged onTap; final OnUpdateTitle onUpdateTitle; const ArticleItem({ super.key, required this.active, required this.article, required this.onTap, required this.onUpdateTitle, }); @override State createState() => _ArticleItemState(); } class _ArticleItemState extends State { @override Widget build(BuildContext context) { return MouseRegion( onEnter: _onEnter, onExit: _onExit, child: GestureDetector( onTap: () => widget.onTap(widget.article), child: Container( alignment: Alignment.centerLeft, padding: EdgeInsets.symmetric(horizontal: 8), margin: EdgeInsets.symmetric(horizontal: 6), width: 240, decoration: BoxDecoration( color: widget.active ? Color(0xffd7e2ff) : _hovered ? Color(0xffe1e6ed) : null, borderRadius: BorderRadius.circular(4)), height: 32, child: Row( spacing: 6, children: [ Icon( widget.article.type == 1 ? Icons.event_note : Icons.wordpress, size: 20, color: widget.active ? Color(0xff5b89fe) : Color(0xffa6aebd), ), Expanded( child: _editMode ? TextField( focusNode: _focusNode, decoration: InputDecoration( isCollapsed: true, border: InputBorder.none), style: TextStyle( fontSize: 14, fontWeight: widget.active ? FontWeight.bold : null, color: widget.active ? Color(0xff5b89fe) : null), onTapOutside: (_) { _updateTitle(); setState(() { _editMode = false; }); }, onSubmitted: (v) { _updateTitle(); setState(() { _editMode = false; }); }, controller: _ctrl, ) : Text( widget.article.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: widget.active ? FontWeight.bold : null, color: widget.active ? Color(0xff5b89fe) : null), ), ), if (_hovered) TolyAction( child: Icon( _editMode ? Icons.check : Icons.edit, size: 16, color: Color(0xff4e5a70), ), onTap: () { if (_editMode) { _updateTitle(); setState(() { _editMode = false; }); return; } widget.onTap(widget.article); _ctrl = TextEditingController(text: widget.article.title); _focusNode = FocusNode(); Future.delayed(Duration(milliseconds: 20)).then((_) { _focusNode!.requestFocus(); _ctrl!.value = _ctrl!.value.copyWith( selection: TextSelection( baseOffset: 0, extentOffset: _ctrl!.text.length)); }); setState(() { _editMode = true; }); }), ], ), ), ), ); } void _updateTitle() { String value = _ctrl?.text ?? ''; if (value.isEmpty) return; widget.onUpdateTitle(widget.article, _ctrl?.text ?? ''); } bool _hovered = false; bool _editMode = false; TextEditingController? _ctrl; FocusNode? _focusNode; void _onEnter(PointerEnterEvent event) { setState(() { _hovered = true; }); } void _onExit(PointerExitEvent event) { setState(() { _hovered = false; }); } } ================================================ FILE: modules/knowledge_system/note/lib/src/view/components/button/button.dart ================================================ import 'package:flutter/material.dart'; import 'package:tolyui/tolyui.dart'; class Button extends StatelessWidget { final VoidCallback onPressed; const Button({super.key, required this.onPressed}); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: onPressed, child: const Wrap( spacing: 6, crossAxisAlignment: WrapCrossAlignment.center, children: [ Icon( Icons.add, size: 20, color: Colors.white, ), Text( "新增", style: TextStyle(fontSize: 14), ), ], ), style: FillButtonPalette( foregroundPalette: const Palette.all(Colors.white), borderRadius: BorderRadius.circular(6), backgroundPalette: const Palette( normal: Color(0xff1890ff), hover: Color(0xff40a9ff), pressed: Color(0xff096dd9), ), ).style, ); } } ================================================ FILE: modules/knowledge_system/note/lib/src/view/desktop/article_display.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:note/note.dart'; import 'package:tolyui/tolyui.dart'; import '../../repository/model/article.dart'; class ArticleDisplay extends StatelessWidget { const ArticleDisplay({super.key}); @override Widget build(BuildContext context) { ArticlePo? selected = context.select((ArtSysBloc bloc) => bloc.state.active); if (selected == null) { return SizedBox(); } if (selected.type == ArticleType.net.index) { return NetworkArticleDisplay( article: selected, ); } return TextField( style: TextStyle(fontSize: 14), onChanged: (text) => context.read().write(text), maxLines: null, minLines: null, controller: context.read().ctrl, expands: true, decoration: const InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 8, )), ); return const Placeholder(); } } class NetworkArticleDisplay extends StatelessWidget { final ArticlePo article; const NetworkArticleDisplay({super.key, required this.article}); @override Widget build(BuildContext context) { return Center( child: Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), width: 300, decoration: BoxDecoration( border: Border.all(), borderRadius: BorderRadius.circular(8)), child: Column( spacing: 12, mainAxisSize: MainAxisSize.min, children: [ Text( article.title, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), if (article.subtitle != null) Text( article.subtitle!, style: TextStyle(fontSize: 12, color: Colors.grey), ), Image.network(article.cover ?? ''), ElevatedButton( onPressed: () {}, child: Text('前往查看'), style: FillButtonPalette( padding: EdgeInsets.symmetric(vertical: 0, horizontal: 24), foregroundPalette: Palette.all(Colors.white), borderRadius: BorderRadius.circular(6), backgroundPalette: const Palette( normal: Color(0xff1890ff), hover: Color(0xff40a9ff), pressed: Color(0xff096dd9), ), ).style, ), ], ), ), ); } } ================================================ FILE: modules/knowledge_system/note/lib/src/view/mobile/mobile_article_list.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/src/gestures/events.dart'; import 'package:note/src/repository/model/model.dart'; import 'package:tolyui/tolyui.dart'; class MobileArticleList extends StatelessWidget { final List articles; final ValueChanged onTap; final int activeId; final OnUpdateTitle onUpdateTitle; const MobileArticleList( {super.key, required this.articles, required this.activeId, required this.onTap, required this.onUpdateTitle}); @override Widget build(BuildContext context) { return ListView.separated( padding: EdgeInsets.symmetric(vertical: 8), separatorBuilder: (_, __) => SizedBox( height: 6, ), itemCount: articles.length, itemBuilder: (_, index) => MobileArticleItem( onTap: onTap, active: articles[index].id == activeId, article: articles[index], onUpdateTitle: onUpdateTitle, ), ); } } typedef OnUpdateTitle = Function(int id, String title); class MobileArticleItem extends StatefulWidget { final bool active; final ArticlePo article; final ValueChanged onTap; final OnUpdateTitle onUpdateTitle; const MobileArticleItem({ super.key, required this.active, required this.article, required this.onTap, required this.onUpdateTitle, }); @override State createState() => _MobileArticleItemState(); } class _MobileArticleItemState extends State { @override Widget build(BuildContext context) { return GestureDetector( onTap: () => widget.onTap(widget.article), child: Container( alignment: Alignment.centerLeft, padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), margin: EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4)), child: Column( spacing: 4, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( spacing: 6, children: [ Icon( widget.article.type == 1 ? Icons.event_note : Icons.wordpress, size: 20, color: Color(0xffa6aebd), ), Expanded( child: Text( widget.article.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: FontWeight.bold,), ), ), ], ), Row( children: [ Text( style: TextStyle(fontSize: 12), widget.article.createDate, ), Text(" → "), Text( style: TextStyle(fontSize: 12), widget.article.updateDate, ) ], ) ], ), ), ); } void _updateTitle() { String value = _ctrl?.text ?? ''; if (value.isEmpty) return; widget.onUpdateTitle(widget.article.id, _ctrl?.text ?? ''); } TextEditingController? _ctrl; FocusNode? _focusNode; } // Container( // alignment: Alignment.centerLeft, // padding: EdgeInsets.symmetric(horizontal: 8), // margin: EdgeInsets.symmetric(horizontal: 6), // height: 32, // width: 200, // decoration: BoxDecoration( // color: Color(0xffd7e2ff), // borderRadius: BorderRadius.circular(4) // ), // child: Text('第一篇',style: TextStyle(fontWeight: FontWeight.bold,color: Color(0xff5b89fe)),), // ), // Container( // alignment: Alignment.centerLeft, // padding: EdgeInsets.symmetric(horizontal: 8), // margin: EdgeInsets.symmetric(horizontal: 6), // height: 32, // width: 200, // decoration: BoxDecoration( // // color: Color(0xffd7e2ff), // borderRadius: BorderRadius.circular(4) // ), // child: Text('第二篇',style: TextStyle(color: Colors.black),), // ), ================================================ FILE: modules/knowledge_system/note/lib/src/view/mobile/mobile_article_page.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:fx_dio/fx_dio.dart'; import '../../repository/article_repository.dart'; import '../../repository/model/model.dart'; import '../article_list.dart'; import 'mobile_article_list.dart'; import 'mobile_editor.dart'; import 'note.dart'; class MobileArticlePage extends StatefulWidget { const MobileArticlePage({super.key}); @override State createState() => _MobileArticlePageState(); } class _MobileArticlePageState extends State { ArticleRepository _repository = HttpArticleRepository(); @override void initState() { super.initState(); _queryScienceArticle(); } List articles = []; int total = 0; int currentIndex = 1; ArticlePo? active; TaskStatus status = const TaskNone(); TextEditingController ctrl = TextEditingController(); TextEditingController titleCtrl = TextEditingController(); Future _queryScienceArticle() async { setState(() { status = const TaskLoading(); }); ApiRet> ret = await _repository.list(SizeFilter()); if (ret.success) { articles = ret.data; total = ret.paginate?.total ?? 0; setState(() { status = TaskSuccess(); }); } else { status = TaskFailed(ret.trace); setState(() {}); } } @override Widget build(BuildContext context) { return PinnedHeaderSliverNode2(); Widget body = switch (status) { TaskNone() => SizedBox(), TaskLoading() => const CupertinoActivityIndicator(), TaskSuccess() => MobileArticleList( articles: articles, activeId: active?.id ?? -1, onTap: (ArticlePo article) { if (article.type == 1) { Navigator.of(context).push(MaterialPageRoute(builder: (ctx) { return MobileEditor( article: article, ); })); // _loadArticleContent(article.id); } else {} titleCtrl.text = article.title; setState(() { active = article; }); }, onUpdateTitle: updateTitle, ), TaskFailed() => Scaffold(), }; return Scaffold( backgroundColor: Color(0xfff5f5f5), bottomNavigationBar: Container( height: 56, ), floatingActionButton: FloatingActionButton( shape: StadiumBorder(), mini: true, elevation: 4, backgroundColor: Theme.of(context).primaryColor, foregroundColor: Colors.white, onPressed: () {}, child: Icon(Icons.add), ), appBar: AppBar( title: Text('匠心巧记'), ), body: body, ); } updateTitle(int id, String title) {} } ================================================ FILE: modules/knowledge_system/note/lib/src/view/mobile/mobile_editor.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fx_dio/fx_dio.dart'; import 'package:note/note.dart'; import '../../repository/article_repository.dart'; import '../../repository/model/model.dart'; class MobileEditor extends StatefulWidget { final ArticlePo article; const MobileEditor({super.key, required this.article}); @override State createState() => _MobileEditorState(); } class _MobileEditorState extends State { TextEditingController ctrl = TextEditingController(); ArticleRepository _repository = HttpArticleRepository(); FocusNode titleFocusNode = FocusNode(); @override void initState() { super.initState(); // _loadArticleContent(widget.article.id); titleFocusNode.addListener(_titleFocusNode); } // void _loadArticleContent(int id) async { // ApiRet ret = await _repository.open(id); // if (ret.success) { // ctrl.text = ret.data; // } // } @override Widget build(BuildContext context) { ArtSysBloc bloc = context.watch(); return Scaffold( backgroundColor: Color(0xfffafafa), appBar: AppBar( surfaceTintColor: Colors.transparent, backgroundColor: Color(0xfffafafa), // title: Text(widget.article.title), actions: [IconButton(onPressed: () { showBottomTip(context); }, icon: Icon(Icons.more_vert))], bottom: PreferredSize( preferredSize: Size.fromHeight(32), child: Padding( padding: const EdgeInsets.only(bottom: 4.0, left: 18), child: Row( spacing: 8, children: [ Text( '${bloc.state.active?.updateDate}', style: TextStyle( fontSize: 12, color: Color( 0xffadadad, )), ), SizedBox(height: 14, child: VerticalDivider()), Text( '${bloc.ctrl.text.length} 字', style: TextStyle( fontSize: 12, color: Color( 0xffadadad, )), ), SizedBox(height: 14, child: VerticalDivider()), Text( '全部文件', style: TextStyle( fontSize: 12, color: Color( 0xffadadad, )), ), ], ), )), ), body: Column( children: [ Divider(), TextField( controller: bloc.titleCtrl, focusNode: titleFocusNode, maxLines: 4, minLines: 1, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), decoration: InputDecoration( border: InputBorder.none, isCollapsed: true, contentPadding: EdgeInsets.only(left: 16, right: 16, top: 8)), ), Expanded( child: TextField( style: TextStyle(fontSize: 14), onChanged: (String value) async { if (widget.article.id != null) { ApiRet ret = await _repository.write(widget.article.id, value); } }, maxLines: null, minLines: null, controller: bloc.ctrl, expands: true, decoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8)), ), ), ], ), ); } void _titleFocusNode() { print("=====_titleFocusNode============"); if(!titleFocusNode.hasFocus){ ArtSysBloc bloc = context.read(); bloc.updateTitleV2(); } } void showBottomTip(BuildContext context) { showCupertinoModalPopup( context: context, builder: (_) => PopBottomTip( onDelete: () async{ await context.read().delete(); Navigator.of(context).pop(); }, message: '当前文当更多操作', deleteText: '删除文档', ), ); } } class PopBottomTip extends StatelessWidget { final VoidCallback onDelete; final String message; final String deleteText; const PopBottomTip({ super.key, required this.onDelete, required this.message, required this.deleteText, }); @override Widget build(BuildContext context) { return SafeArea( bottom: true, child: Material( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(6), topRight: Radius.circular(6), )), color: Colors.white, child: SizedBox( width: MediaQuery.sizeOf(context).width, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( height: 52, alignment: Alignment.center, decoration: BoxDecoration( border: Border( bottom: BorderSide( width: 0.5, color: Colors.grey.withOpacity(0.2)))), child: Text( message, style: TextStyle(color: Color(0xff8f8f8f)), )), InkWell( splashColor: Colors.white, onTap: () { Navigator.of(context).pop(); onDelete(); }, child: Container( height: 56, alignment: Alignment.center, child: Text( deleteText, style: TextStyle(color: Color(0xfff14835), fontSize: 16), )), ), Container( color: Color(0xfff2f3f5), height: 8, ), InkWell( splashColor: Colors.white, onTap: () => Navigator.of(context).pop(), child: Container( height: 56, alignment: Alignment.center, child: Text( '取消', style: TextStyle(fontSize: 16), )), ), ], ), ), ), ); } } ================================================ FILE: modules/knowledge_system/note/lib/src/view/mobile/note.dart ================================================ // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:note/note.dart'; import '../../repository/model/model.dart'; import 'mobile_article_list.dart'; import 'mobile_editor.dart'; class PinnedHeaderSliverNode2 extends StatefulWidget { const PinnedHeaderSliverNode2({super.key}); @override State createState() => _PinnedHeaderSliverNode2State(); } class _PinnedHeaderSliverNode2State extends State { int count = 0; late final ScrollController scrollController; @override void initState() { super.initState(); scrollController = ScrollController(); } @override void dispose() { scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; ArtSysBloc bloc = context.watch(); ListStatus status = bloc.state.status; List articles = bloc.state.articles; bool hasActive = bloc.state.active != null; return Scaffold( bottomNavigationBar: Container( height: 52, ), floatingActionButton: FloatingActionButton( shape: StadiumBorder(), mini: true, elevation: 4, backgroundColor: Theme.of(context).primaryColor, foregroundColor: Colors.white, onPressed: () async{ ArtSysBloc bloc = context.read(); await bloc.newArticle(); ArticlePo article = bloc.state.articles.first; bloc.select(article); await Navigator.of(context).push(MaterialPageRoute(builder: (ctx) { return BlocProvider.value( value: bloc, child: MobileEditor( article: article, ), ); })); }, child: Icon(Icons.add), ), backgroundColor: Color(0xfff5f5f5), body: CustomScrollView( controller: scrollController, slivers: [ _buildSliverBar(), _buildTitleText(), const PinnedHeaderSliver(child: Divider()), ...ListView.separated( padding: EdgeInsets.symmetric(vertical: 8), separatorBuilder: (_, __) => SizedBox( height: 6, ), itemCount: articles.length, itemBuilder: (_, index) => MobileArticleItem( onTap: _onTap, active: false, article: articles[index], onUpdateTitle: onUpdateTitle, ), ).buildSlivers(context), ], ), ); } Widget _buildSliverBar() { const Icon icon = Icon(Icons.more_vert); const TextStyle style = TextStyle(fontSize: 16, fontWeight: FontWeight.bold); const Text text = Text('匠心巧记', style: style); Widget action = IconButton(onPressed: () {}, icon: icon); return SliverLayoutBuilder(builder: (_, scs) { double factor = (scs.scrollOffset / kToolbarHeight).clamp(0, 1); factor = factor < 0.2 ? 0 : factor; AppBar header = AppBar( backgroundColor: Color(0xfff5f5f5), surfaceTintColor: Colors.transparent, actions: [action], centerTitle: true, title: Opacity(opacity: factor, child: text), ); return PinnedHeaderSliver(child: header); }); } Widget _buildTitleText() { const TextStyle style = TextStyle(fontSize: 20, fontWeight: FontWeight.bold); const Text text = Text('匠心巧记', style: style); return const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.only(left: 12.0, bottom: 8), child: text, ), ); } void _onTap(ArticlePo article) async { if (article.type == 1) { ArtSysBloc bloc = context.read(); bloc.select(article); await Navigator.of(context).push(MaterialPageRoute(builder: (ctx) { return BlocProvider.value( value: context.read(), child: MobileEditor( article: article, ), ); })); // _loadArticleContent(article.id); } else {} } onUpdateTitle(int id, String title) {} } class _ItemList extends StatelessWidget { const _ItemList({ super.key, this.itemCount = 50, }); final int itemCount; @override Widget build(BuildContext context) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return SliverList( delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return ListTile( dense: true, textColor: colorScheme.secondary, title: Text('#$index title'), subtitle: Text('Subtitle in line $index'), ); }, childCount: itemCount, ), ); } } ================================================ FILE: modules/knowledge_system/note/lib/src/view/news/news_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:fx_dio/fx_dio.dart'; import 'package:note/note.dart'; class NewsPage extends StatefulWidget { final String title; const NewsPage({super.key, required this.title}); @override State createState() => _NewsPageState(); } class _NewsPageState extends State { List data = []; final ArticleRepository _repository = HttpArticleRepository(); @override void initState() { super.initState(); _loadData(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: ListView.builder( padding: EdgeInsets.symmetric(vertical: 4), itemExtent: 76, itemCount: data.length, itemBuilder: (_, index) => MouseRegion( cursor: SystemMouseCursors.click, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(4), child: Image.network( data[index].cover ?? '', width: 68, height: 68, fit: BoxFit.cover, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( data[index].title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold), ), Text( data[index].subtitle ?? '', maxLines: 2, style: TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ), ], ), ), ), )); } void _loadData() async { ApiRet> ret = await _repository.getArticlesByTag(1, filter: const SizeFilter( page: 1, pageSize: 40, )); if (ret.success) { data = ret.data; setState(() {}); } } } ================================================ FILE: modules/knowledge_system/note/lib/src/view/view.dart ================================================ export 'article_admin.dart'; export 'art_sys_scope.dart'; export 'mobile/mobile_article_page.dart'; export 'news/news_page.dart'; ================================================ FILE: modules/knowledge_system/note/pubspec.yaml ================================================ name: note description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ^3.6.1 flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter fx_dio: 0.0.4+3 fx_dao: 0.0.3+4 flutter_bloc: ^8.1.6 # 状态管理 two_dimensional_scrollables: ^0.3.3 flutter_quill: ^11.0.0-dev.21 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^4.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/to/asset-from-package # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/to/font-from-package ================================================ FILE: modules/knowledge_system/note/test/note_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:note/note.dart'; ================================================ FILE: modules/knowledge_system/note/test/parser.dart ================================================ import 'dart:convert'; main(){ // 示例 JSON 字符串 final jsonString = ''' { "status": true, "msg": "请求成功!", "data": { "cate_id": 1, "name": "匠心空间", "cate_type": 0, "priority": 0, "children": [ { "cate_id": 2, "name": "全部笔记", "children": [ {"cate_id":4, "name":"我的随笔"}, {"cate_id":5, "name":"编程技术"} ] }, { "cate_id": 3, "name": "网络博文", "children": [ {"cate_id":6, "name":"掘金文章"}, {"cate_id":7, "name":"微信公众号"} ] } ] } } '''; // 解析过程 final Map parsedJson = jsonDecode(jsonString); final Category category = Category.fromJson(parsedJson['data']); print(category); } class Category { final int cateId; final String name; final int cateType; final int priority; final List children; Category({ required this.cateId, required this.name, this.cateType = 0, this.priority = 0, this.children = const [], }); factory Category.fromJson(Map json) { return Category( cateId: json['cate_id'] as int, name: json['name'] as String, cateType: json['cate_type'] as int? ?? 0, priority: json['priority'] as int? ?? 0, children: (json['children'] as List?) ?.map((child) => Category.fromJson(child)) .toList() ?? [], ); } } ================================================ FILE: modules/painting_system/draw_system/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ build/ ================================================ FILE: modules/painting_system/draw_system/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" channel: "stable" project_type: package ================================================ FILE: modules/painting_system/draw_system/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/painting_system/draw_system/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/painting_system/draw_system/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/painting_system/draw_system/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/painting_system/draw_system/lib/draw_system.dart ================================================ library draw_system; export 'src/gallery_detail_page.dart'; export 'src/gallery_unit.dart'; export 'src/bloc/gallery_unit/bloc.dart'; ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/bezier3_player/bezier3_palyer.dart ================================================ import 'dart:ui'; import 'package:flutter/material.dart'; import '../../utils/coordinate.dart'; import 'touch_info.dart'; /// create by 张风捷特烈 on 2020/5/1 /// contact me by email 1981462002@qq.com /// 说明: class Bezier3Player extends StatefulWidget { const Bezier3Player({Key? key}) : super(key: key); @override _Bezier3PlayerState createState() => _Bezier3PlayerState(); } class _Bezier3PlayerState extends State { final TouchInfo touchInfo = TouchInfo(); @override void dispose() { touchInfo.dispose(); super.dispose(); } @override Widget build(BuildContext context) { double size = MediaQuery.of(context).size.shortestSide; return GestureDetector( onPanDown: _onPanDown, onPanUpdate: _onPanUpdate, onDoubleTap: _clear, child: Container( color: Colors.white, child: CustomPaint( size: Size(size,size), painter: PaperPainter(repaint: touchInfo), ), ), ); } void _onPanDown(DragDownDetails details) { if (touchInfo.points.length < 4) { touchInfo.addPoint(details.localPosition); }else{ judgeZone(details.localPosition); } } void _onPanUpdate(DragUpdateDetails details) { judgeZone(details.localPosition,update: true); } ///判断出是否在某点的半径为r圆范围内 bool judgeCircleArea(Offset src, Offset dst, double r) => (src - dst).distance <= r; //判断哪个点被选中 void judgeZone(Offset src,{bool update =false}) { for (int i = 0; i < touchInfo.points.length; i++) { if (judgeCircleArea(src, touchInfo.points[i], 15)) { touchInfo.selectIndex = i; if(update){ touchInfo.updatePoint(i, src); } } } } void _clear () { touchInfo.reset(); } } class PaperPainter extends CustomPainter { final Coordinate coordinate = Coordinate(); final Paint _helpPaint = Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; final TouchInfo repaint; PaperPainter({required this.repaint}) : super(repaint: repaint); List pos=[]; @override void paint(Canvas canvas, Size size) { coordinate.paint(canvas, size); canvas.translate(size.width / 2, size.height / 2); pos = repaint.points .map((e) => e.translate(-size.width / 2, -size.height / 2)) .toList(); Path path = Path(); Paint paint = Paint() ..color = Colors.orange ..style = PaintingStyle.stroke ..strokeWidth = 2; if (pos.length < 4) { canvas.drawPoints(PointMode.points, pos, _helpPaint..strokeWidth = 8); } else { path.moveTo(pos[0].dx, pos[0].dy); path.cubicTo(pos[1].dx, pos[1].dy, pos[2].dx, pos[2].dy, pos[3].dx, pos[3].dy); canvas.drawPath(path, paint); _drawHelp(canvas); _drawSelectPos(canvas,size); } } void _drawHelp(Canvas canvas) { _helpPaint.color = Colors.purple; canvas.drawPoints(PointMode.polygon, pos, _helpPaint..strokeWidth = 1); canvas.drawPoints(PointMode.points, pos, _helpPaint..strokeWidth = 8); } void _drawSelectPos(Canvas canvas,Size size) { Offset? selectPos = repaint.selectPoint; if (selectPos == null) return; selectPos = selectPos.translate(-size.width / 2, -size.height / 2); canvas.drawCircle( selectPos, 10, _helpPaint ..color = Colors.green ..strokeWidth = 2); } @override bool shouldRepaint(PaperPainter oldDelegate) => oldDelegate.repaint != repaint; } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/bezier3_player/touch_info.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/11/4 /// contact me by email 1981462002@qq.com /// 说明: class TouchInfo extends ChangeNotifier { final List _points = []; int _selectIndex = -1; int get selectIndex => _selectIndex; List get points => _points; set selectIndex(int value) { if (_selectIndex == value) return; _selectIndex = value; notifyListeners(); } void addPoint(Offset point) { points.add(point); notifyListeners(); } void updatePoint(int index, Offset point) { points[index] = point; notifyListeners(); } void reset() { _points.clear(); _selectIndex = -1; notifyListeners(); } Offset? get selectPoint => _selectIndex == -1 ? null : _points[_selectIndex]; } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/circle_halo.dart ================================================ import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; class CircleHalo extends StatefulWidget { const CircleHalo({Key? key}) : super(key: key); @override _CircleHaloState createState() => _CircleHaloState(); } class _CircleHaloState extends State with SingleTickerProviderStateMixin { late AnimationController _ctrl; @override void initState() { super.initState(); _ctrl = AnimationController( vsync: this, duration: const Duration(seconds: 2), ); _ctrl.repeat(); } @override void dispose() { _ctrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return CustomPaint( size: const Size(200, 200), painter: CircleHaloPainter(_ctrl), ); } } class CircleHaloPainter extends CustomPainter { Animation animation; CircleHaloPainter(this.animation) : super(repaint: animation); final Animatable rotateTween = Tween(begin: 0, end: 2 * pi) .chain(CurveTween(curve: Curves.easeIn)); final Animatable breatheTween = TweenSequence( >[ TweenSequenceItem( tween: Tween(begin: 0, end: 4), weight: 1, ), TweenSequenceItem( tween: Tween(begin: 4, end: 0), weight: 1, ), ], ).chain(CurveTween(curve: Curves.decelerate)); @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); final Paint paint = Paint() ..strokeWidth = 1 ..style = PaintingStyle.stroke; Path circlePath = Path() ..addOval(Rect.fromCenter(center: const Offset(0, 0), width: 100, height: 100)); Path circlePath2 = Path() ..addOval( Rect.fromCenter(center: const Offset(-1, 0), width: 100, height: 100)); Path result = Path.combine(PathOperation.difference, circlePath, circlePath2); List colors = [ const Color(0xFFF60C0C), const Color(0xFFF3B913), const Color(0xFFE7F716), const Color(0xFF3DF30B), const Color(0xFF0DF6EF), const Color(0xFF0829FB), const Color(0xFFB709F4), ]; colors.addAll(colors.reversed.toList()); final List pos = List.generate(colors.length, (index) => index / colors.length); paint.shader = ui.Gradient.sweep(Offset.zero, colors, pos, TileMode.clamp, 0, 2 * pi); paint.maskFilter = MaskFilter.blur(BlurStyle.solid, breatheTween.evaluate(animation)); canvas.drawPath(circlePath, paint); canvas.save(); canvas.rotate(animation.value * 2 * pi); paint ..style = PaintingStyle.fill ..color = const Color(0xff00abf2); paint.shader=null; canvas.drawPath(result, paint); canvas.restore(); } @override bool shouldRepaint(covariant CircleHaloPainter oldDelegate) => oldDelegate.animation != animation; } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/curve_shower/anim_painter.dart ================================================ import 'dart:ui'; import 'package:flutter/material.dart'; import 'point_data.dart'; class AnimPainter extends CustomPainter { final PointData points; Paint axisPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1; Paint fpsPaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.green; TextPainter textPainter = TextPainter( textAlign: TextAlign.center, textDirection: TextDirection.ltr); AnimPainter(this.points) : super(repaint: points); @override void paint(Canvas canvas, Size size) { canvas.translate(0, size.height); _drawAxis(canvas,size); _drawScale(canvas, size); _drawPoint(points.values, canvas, size); Path fps_60 = Path(); fps_60.moveTo(3.0 * 60, 0); fps_60.relativeLineTo(0, -size.height); canvas.drawPath(fps_60, fpsPaint); textPainter.text = const TextSpan( text: '60 帧', style: TextStyle(fontSize: 12, color: Colors.green)); textPainter.layout(); // 进行布局 textPainter.paint(canvas, Offset(3.0 * 61 + 5, -size.height)); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; void _drawAxis(Canvas canvas, Size size){ Path axisPath = Path(); axisPath.relativeLineTo(size.width, 0); axisPath.relativeLineTo(-10, -4); axisPath.moveTo(size.width, 0); axisPath.relativeLineTo(-10, 4); axisPath.moveTo(0, 0); axisPath.relativeLineTo(0, -size.height); axisPath.relativeLineTo(-4, 10); axisPath.moveTo(0, -size.height); axisPath.relativeLineTo(4, 10); canvas.drawPath(axisPath, axisPaint); textPainter.text = const TextSpan( text: '帧数/f', style: TextStyle(fontSize: 12, color: Colors.black)); textPainter.layout(); // 进行布局 Size textSize = textPainter.size; // 尺寸必须在布局后获取 textPainter.paint(canvas, Offset(size.width - textSize.width, 5)); textPainter.text = const TextSpan( text: '数值/y', style: TextStyle(fontSize: 12, color: Colors.black)); textPainter.layout(); // 进行布局 Size textSize2 = textPainter.size; // 尺寸必须在布局后获取 textPainter.paint(canvas, Offset(-textSize2.width + textSize2.width/2, -size.height - textSize2.height-3)); } void _drawScale(Canvas canvas, Size size) { double step = size.height / 11; if(points.values.isNotEmpty){ canvas.drawLine(Offset(0, -points.values.last*step*10), Offset(280, -points.values.last*step*10), Paint()..color=Colors.purple); canvas.drawCircle(Offset(230, -points.values.last*step*10), 10, Paint()..color=Colors.orange); } Path scalePath = Path(); for (int i = 1; i <= 10; i++) { scalePath ..moveTo(0, -step * i) ..relativeLineTo(5, 0); textPainter.text = TextSpan( text: '${i / 10}', style: const TextStyle(fontSize: 12, color: Colors.black)); textPainter.layout(); // 进行布局 Size textSize = textPainter.size; // 尺寸必须在布局后获取 textPainter.paint( canvas, Offset(-textSize.width - 5, -step * i - textSize.height / 2)); } canvas.drawPath(scalePath, axisPaint); } void _drawPoint(List values, Canvas canvas, Size size) { double stepY = size.height / 11; List drawPoint = []; // print(values.length); for (int i = 0; i < values.length; i++) { drawPoint.add(Offset(3.0 * (i + 1), -values[i] * (size.height - stepY))); } canvas.drawPoints( PointMode.points, drawPoint, Paint() ..style = PaintingStyle.stroke..color=Colors.blue ..strokeWidth = 2); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/curve_shower/curve_anim_shower.dart ================================================ import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'anim_painter.dart'; import 'point_data.dart'; class CurveAnimShower extends StatefulWidget { const CurveAnimShower({Key? key}) : super(key: key); @override _CurveAnimShowerState createState() => _CurveAnimShowerState(); } class _CurveAnimShowerState extends State with SingleTickerProviderStateMixin { PointData points = PointData(); late AnimationController _ctrl; final Duration animDuration = const Duration(milliseconds: 1000); late Animation curveAnim; Map maps = { 'bounceOut': Curves.bounceOut, 'linear': Curves.linear, 'decelerate': Curves.decelerate, 'fastLinearToSlowEaseIn': Curves.fastLinearToSlowEaseIn, 'ease': Curves.ease, 'easeIn': Curves.easeIn, 'easeInToLinear': Curves.easeInToLinear, 'easeInSine': Curves.easeInSine, 'easeInQuad': Curves.easeInQuad, 'easeInCubic': Curves.easeInCubic, 'easeInQuart': Curves.easeInQuart, 'easeInQuint': Curves.easeInQuint, 'easeInExpo': Curves.easeInExpo, 'easeInCirc': Curves.easeInCirc, 'easeInBack': Curves.easeInBack, 'easeOut': Curves.easeOut, 'linearToEaseOut': Curves.linearToEaseOut, 'easeOutSine': Curves.easeOutSine, 'easeOutQuad': Curves.easeOutQuad, 'easeOutCubic': Curves.easeOutCubic, 'easeOutQuart': Curves.easeOutQuart, 'easeOutQuint': Curves.easeOutQuint, 'easeOutExpo': Curves.easeOutExpo, 'easeOutCirc': Curves.easeOutCirc, 'easeOutBack': Curves.easeOutBack, 'easeInOut': Curves.easeInOut, 'easeInOutSine': Curves.easeInOutSine, 'easeInOutQuad': Curves.easeInOutQuad, 'easeInOutCubic': Curves.easeInOutCubic, 'easeInOutQuart': Curves.easeInOutQuart, 'easeInOutExpo': Curves.easeInOutExpo, 'easeInOutQuint': Curves.easeInOutQuint, 'easeInOutCirc': Curves.easeInOutCirc, 'easeInOutBack': Curves.easeInOutBack, 'fastOutSlowIn': Curves.fastOutSlowIn, 'slowMiddle': Curves.slowMiddle, 'bounceIn': Curves.bounceIn, 'bounceInOut': Curves.bounceInOut, 'elasticIn': Curves.elasticIn, 'elasticOut': Curves.elasticOut, 'elasticInOut': Curves.elasticInOut, }; @override void initState() { super.initState(); _ctrl = AnimationController( vsync: this, duration: animDuration, )..addListener(_collectPoint); curveAnim = CurvedAnimation(parent: _ctrl, curve: Curves.bounceOut); } @override void dispose() { _ctrl.dispose(); points.dispose(); super.dispose(); } void _collectPoint() { points.push(curveAnim.value); } void _startAnim() async { points.clear(); await _ctrl.forward(from: 0); } @override Widget build(BuildContext context) { return GestureDetector( onTap: _startAnim, child: Stack( alignment: Alignment.center, children: [ Positioned( right: 5, top: 5, child: DropSelectableWidget( fontSize: 12, data: maps.keys.toList(), iconSize: 20, height: 25, width: 180, disableColor: const Color(0xff1F425F), onDropSelected: (int index) async { curveAnim = CurvedAnimation( parent: _ctrl, curve: maps.values.toList()[index]); _startAnim(); }, )), Padding( padding: const EdgeInsets.all(30.0), child: CustomPaint( painter: AnimPainter(points), size: const Size(200, 200), )), ], ), ); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/curve_shower/point_data.dart ================================================ import 'package:flutter/cupertino.dart'; class PointData extends ChangeNotifier { final List values = []; void push(double value) { values.add(value); notifyListeners(); } void clear() { values.clear(); notifyListeners(); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/draw_path.dart ================================================ import 'dart:math'; import 'dart:ui'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/5/1 /// contact me by email 1981462002@qq.com /// 说明: class DrawPath extends StatefulWidget { const DrawPath({Key? key}) : super(key: key); @override _DrawPathState createState() => _DrawPathState(); } class _DrawPathState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 15), vsync: this, )..repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( color: Colors.white, child: CustomPaint( painter: PaperPainter(_controller), ), ); } } class PaperPainter extends CustomPainter { final Animation repaint; PaperPainter(this.repaint) : super(repaint: repaint) { initPointsWithPolar(); } final List points = []; final Path path = Path(); final double step = 4; final double min = 0; final double max = 360; void initPointsWithPolar() { for (double x = min; x < max; x += step) { double thta = (pi / 180 * x); // 角度转化为弧度 var p = f(thta); points.add(Offset(p * cos(thta), p * sin(thta))); } double thta = (pi / 180 * max); points.add(Offset(f(thta) * cos(thta), f(thta) * sin(thta))); points.add(Offset(f(thta) * cos(thta), f(thta) * sin(thta))); } double f(double thta) { double p = 120 * sin(5 * thta).abs(); return p; } @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); Paint paint = Paint() ..color = Colors.red ..strokeWidth = 1.5 ..style = PaintingStyle.stroke; List colors = const [ Color(0xFFF60C0C), Color(0xFFF3B913), Color(0xFFE7F716), Color(0xFF3DF30B), Color(0xFF0DF6EF), Color(0xFF0829FB), Color(0xFFB709F4), ]; var pos = [1.0 / 7, 2.0 / 7, 3.0 / 7, 4.0 / 7, 5.0 / 7, 6.0 / 7, 1.0]; paint.shader = ui.Gradient.linear( const Offset(0, 0), const Offset(100, 0), colors, pos, TileMode.mirror, ); Offset p1 = points[0]; path.reset(); path.moveTo(p1.dx, p1.dy); for (var i = 1; i < points.length - 1; i++) { double xc = (points[i].dx + points[i + 1].dx) / 2; double yc = (points[i].dy + points[i + 1].dy) / 2; Offset p2 = points[i]; path.quadraticBezierTo(p2.dx, p2.dy, xc, yc); } PathMetrics pms = path.computeMetrics(); for (PathMetric pm in pms) { Tangent? tangent = pm.getTangentForOffset(pm.length * repaint.value); canvas.drawPath(pm.extractPath(0, pm.length * repaint.value), paint); canvas.drawCircle( tangent?.position??Offset.zero, 5, Paint()..color = Colors.blue); } } @override bool shouldRepaint(PaperPainter oldDelegate) => oldDelegate.repaint != repaint; } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/dundun_path.dart ================================================ import 'dart:math'; import 'dart:ui' as ui; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class DunDunPathPage extends StatefulWidget { const DunDunPathPage({Key? key}) : super(key: key); @override _DunDunPathPageState createState() => _DunDunPathPageState(); } class _DunDunPathPageState extends Statewith SingleTickerProviderStateMixin { ui.Image? logo2Image; late AnimationController _controller; @override void initState() { super.initState(); loadImage(); _controller = AnimationController( duration: const Duration(seconds: 3), vsync: this, )..forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } void loadImage() async { ByteData data2 = await rootBundle.load('assets/images/logo1.webp'); logo2Image = await decodeImageFromList(data2.buffer.asUint8List()); if (mounted) setState(() {}); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GestureDetector( onTap: (){ _controller.reset(); _controller.forward(); }, child: CustomPaint( painter: DunDunPainter(logo2Image,_controller), size: const Size(200, 200), ), ), ), ); } } class DunDunPainter extends CustomPainter { final ui.Image? logo2Image; final Animation repaint; DunDunPainter(this.logo2Image,this.repaint):super(repaint: repaint); final Paint helpPaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.blue; final Paint pathPaint = Paint()..style = PaintingStyle.stroke; @override void paint(Canvas canvas, Size size) { if (logo2Image!=null) { Rect src2 = Rect.fromLTWH( 0, 0, logo2Image!.width.toDouble(), logo2Image!.height.toDouble()); Rect dst2 = const Rect.fromLTWH(85, 132, 899/27, 1066/27); canvas.drawImageRect(logo2Image!, src2, dst2, Paint()); } Path dundunPath = Path(); canvas.translate(30, 80); helpPaint.color = Colors.red; Path bodyPath = buildBodyPath(); Path leftHandPath = buildLeftHandPath(); Path rightHandPath = buildRightHandPath(); canvas.save(); Path eyePath = Path(); Matrix4 m = Matrix4.translationValues(46,-12, 0).multiplied( Matrix4.rotationZ(45 / 180 * pi) ); eyePath .addOval(Rect.fromCenter(center: const Offset(0, 0), width: 32, height: 49)); eyePath = eyePath.transform(m.storage); canvas.restore(); Path leftEyePath = Path(); leftEyePath.addOval(Rect.fromCenter(center: const Offset(50, -13), width: 18, height: 18)); Path leftEyePath2 = Path(); leftEyePath2.addOval(Rect.fromCenter(center: const Offset(50, -13), width: 7, height: 7)); Path leftEyePath3 = Path(); leftEyePath3.addOval(Rect.fromCenter(center: const Offset(51, -19), width: 4, height: 4)); Path rightEyePath = Path(); rightEyePath.addOval(Rect.fromCenter(center: const Offset(98, -14), width: 17, height: 17)); Path rightEyePath2 = Path(); rightEyePath2.addOval(Rect.fromCenter(center: const Offset(98, -14), width: 7, height: 7)); Path rightEyePath3 = Path(); rightEyePath3.addOval(Rect.fromCenter(center: const Offset(98, -19), width: 4, height: 4)); Path nosePath = Path(); nosePath.moveTo(79, -0,); nosePath.relativeLineTo(14, -14,); nosePath.relativeLineTo(-28, 0,); nosePath.close(); Path clipCirclePath =Path(); clipCirclePath.addOval(Rect.fromCenter(center: const Offset(79, -10,), width: 14, height: 14)); nosePath = Path.combine(PathOperation.intersect, nosePath, clipCirclePath); Path smaliPath = Path(); smaliPath.moveTo(65, -0,); smaliPath.quadraticBezierTo(78, 15, 90, 0); smaliPath.quadraticBezierTo(78, 6, 65, 0,); Path colorfulPath = Path(); colorfulPath.addOval(Rect.fromCenter(center: const Offset(72, -5,), width: 120, height: 110)); colorfulPath.addOval(Rect.fromCenter(center: const Offset(72, -5,), width: 110, height: 100)); colorfulPath.addOval(Rect.fromCenter(center: const Offset(72, -5,), width: 115, height: 110)); colorfulPath.addOval(Rect.fromCenter(center: const Offset(72, -5,), width: 120, height: 105)); colorfulPath.addOval(Rect.fromCenter(center: const Offset(72, -5,), width: 115, height: 105)); canvas.save(); Path eyePath2 = Path(); Matrix4 m2 = Matrix4.translationValues(105,-12,0).multiplied( Matrix4.rotationZ(-40 / 180 * pi) ); eyePath2 .addOval(Rect.fromCenter(center: const Offset(0, 0), width: 29, height: 48)); eyePath2 = eyePath2.transform(m2.storage); canvas.restore(); Path leftFootPath = buildFootPath(); Path erPath = buildErPath(); //爱心 List arr = []; int len = 50; double a =1; for (int i = 0; i < len; i++) { double step = (i / len) * (pi * 2); //递增的θ Offset offset = Offset( a * (11 * pow(sin(step), 3)).toDouble() , -a * (9 * cos(step) - 5 * cos(2 * step) - 2 * cos(3 * step) - cos(4 * step)), ); arr.add(offset); } Path starPath = Path(); for (int i = 0; i < len; i++) { starPath.lineTo(arr[i].dx, arr[i].dy); } starPath = starPath.shift(const Offset(152,-20)); dundunPath.addPath(bodyPath, Offset.zero); dundunPath.addPath(leftHandPath, Offset.zero); dundunPath.addPath(rightHandPath, Offset.zero); dundunPath.addPath(leftFootPath, Offset.zero); dundunPath.addPath(erPath, Offset.zero); dundunPath.addPath(eyePath, Offset.zero); dundunPath.addPath(eyePath2, Offset.zero); dundunPath.addPath(leftEyePath, Offset.zero); dundunPath.addPath(leftEyePath2, Offset.zero); dundunPath.addPath(leftEyePath3, Offset.zero); dundunPath.addPath(rightEyePath, Offset.zero); dundunPath.addPath(rightEyePath2, Offset.zero); dundunPath.addPath(rightEyePath3, Offset.zero); dundunPath.addPath(nosePath, Offset.zero); dundunPath.addPath(starPath, Offset.zero); dundunPath.addPath(colorfulPath, Offset.zero); dundunPath.addPath(smaliPath, Offset.zero); pathPaint ..strokeWidth = 1 ..color = Colors.cyanAccent; PathMetrics pms = dundunPath.computeMetrics(); for (PathMetric pm in pms) { canvas.drawPath(pm.extractPath(0, pm.length * repaint.value), pathPaint); } } Path buildBodyPath() { Path path = Path(); path.quadraticBezierTo(10, -75, 75, -75); path.quadraticBezierTo(135, -70, 138, 0); path.quadraticBezierTo(130, 90, 65, 98); path.quadraticBezierTo(-5, 85, 0, 0); return path; } Path buildLeftHandPath() { Path path = Path(); path.quadraticBezierTo( -17, 14, -28, 40, ); path.quadraticBezierTo(-32, 58, -15, 62); path.quadraticBezierTo(10, 60, 0, 0); return path; } Path buildRightHandPath() { Path path = Path(); path.moveTo(135, -20); path.quadraticBezierTo(140, -48, 165, -35); path.quadraticBezierTo(180, -17, 135, 22); path.quadraticBezierTo(125, 17, 135, -20); return path; } Path buildFootPath() { Path path = Path(); path.moveTo(18, 78); path.quadraticBezierTo(18, 100, 22, 115); path.quadraticBezierTo(60, 125, 55, 98); path.quadraticBezierTo(35, 80, 18, 78); Path right = path .transform(Matrix4.diagonal3Values(-1, 1, 1).storage) .shift(const Offset(128, 0)); return Path.combine(PathOperation.union, path, right); } Path buildErPath() { Path path = Path(); path.moveTo(13, -40); path.quadraticBezierTo(8, -95, 40, -68); path.quadraticBezierTo(40, -55, 13, -40); Path right = path .transform(Matrix4.diagonal3Values(-1, 1, 1).storage) .shift(const Offset(138, -5)); return Path.combine(PathOperation.union, path, right); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/rotate_by_point/angle_panter.dart ================================================ import 'dart:math'; import 'package:dash_painter/dash_painter.dart'; import 'package:flutter/material.dart'; import 'line.dart'; class AnglePainter extends CustomPainter { final DashPainter dashPainter = const DashPainter(span: 4, step: 4); AnglePainter({required this.line}) : super(repaint: line); final Paint pointPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1; final Paint helpPaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.lightBlue ..strokeWidth = 1; final TextPainter textPainter = TextPainter( textAlign: TextAlign.center, textDirection: TextDirection.ltr, ); final Line line; @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); drawHelp(canvas, size); line.paint(canvas); } void drawHelp(Canvas canvas, Size size) { Path helpPath = Path() ..moveTo(-size.width / 2, 0) ..relativeLineTo(size.width, 0) ..moveTo(0, -size.height / 2) ..relativeLineTo(0, size.height); dashPainter.paint(canvas, helpPath, helpPaint); drawHelpText('0°', canvas, Offset(size.width / 2 - 20, 0)); drawHelpText('p0', canvas, line.start.translate(-20, 0)); drawHelpText('p1', canvas, line.end.translate(-20, 0)); // drawHelpText('p2', canvas, Offset(60, 40).translate(10, 0)); // drawAnchor(canvas, Offset(60, 40)); drawAnchor(canvas, line.percent(0.2)); // drawAnchor(canvas, line.percent(0.5)); // drawAnchor(canvas, line.percent(0.8)); drawHelpText( '角度: ${(line.positiveRad * 180 / pi).toStringAsFixed(2)}°', canvas, Offset( -size.width / 2 + 10, -size.height / 2 + 10, ), ); // canvas.drawArc( // Rect.fromCenter(center: line.start, width: 20, height: 20), // 0, // line.positiveRad, // false, // helpPaint, // ); // canvas.save(); // Offset center = const Offset(60, 60); // canvas.translate(center.dx, center.dy); // canvas.rotate(line.positiveRad); // canvas.translate(-center.dx, -center.dy); // canvas.drawCircle(center, 4, helpPaint); // canvas.drawRect( // Rect.fromCenter(center: center, width: 30, height: 60), helpPaint); // canvas.restore(); } void drawAnchor(Canvas canvas, Offset offset) { canvas.drawCircle(offset, 4, pointPaint..style = PaintingStyle.stroke); canvas.drawCircle(offset, 2, pointPaint..style = PaintingStyle.fill); } void drawHelpText( String text, Canvas canvas, Offset offset, { Color color = Colors.lightBlue, }) { textPainter.text = TextSpan( text: text, style: TextStyle(fontSize: 12, color: color), ); textPainter.layout(maxWidth: 200); textPainter.paint(canvas, offset); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/rotate_by_point/line.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class Line with ChangeNotifier { Line({ this.start = Offset.zero, this.end = Offset.zero, }); Offset start; Offset end; final Paint pointPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1; void paint(Canvas canvas) { canvas.drawLine(start, end, pointPaint); drawAnchor(canvas, start); drawAnchor(canvas, end); } double get rad => (end - start).direction; double get positiveRad => rad < 0 ? 2 * pi + rad : rad; double get length => (end - start).distance; void drawAnchor(Canvas canvas, Offset offset) { canvas.drawCircle(offset, 4, pointPaint..style = PaintingStyle.stroke); canvas.drawCircle(offset, 2, pointPaint..style = PaintingStyle.fill); } double detaRotate = 0; void rotate(double rotate, {Offset? centre}) { detaRotate = rotate - detaRotate; centre = centre ?? start; Line p2p0 = Line(start: centre, end: start); Line p2p1 = Line(start: centre, end: end); p2p0._rotateByStart(detaRotate); p2p1._rotateByStart(detaRotate); start = p2p0.end; end = p2p1.end; detaRotate = rotate; notifyListeners(); } Offset percent(double percent){ return Offset( length*percent*cos(rad), length*percent*sin(rad), )+start; } void _rotateByStart(double rotate) { end = Offset( length * cos(rad + rotate), length * sin(rad + rotate), ) + start; } void tick() { notifyListeners(); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/rotate_by_point/rotate_by_point.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'angle_panter.dart'; import 'line.dart'; class RotateByPointWidget extends StatefulWidget { const RotateByPointWidget({Key? key}) : super(key: key); @override State createState() => _RotateByPointWidgetState(); } class _RotateByPointWidgetState extends State with SingleTickerProviderStateMixin { Line line = Line(start: const Offset(20, 20), end: const Offset(50, 80)); late AnimationController ctrl; @override void initState() { super.initState(); ctrl = AnimationController( vsync: this, duration: const Duration(seconds: 3), )..addListener(_updateLine); } @override void dispose() { line.dispose(); ctrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: () => ctrl.forward(from: 0), child: CustomPaint( painter: AnglePainter(line: line), size: const Size(200,200), ), ); } void _updateLine() { Offset center = line.percent(0.2); line.rotate(ctrl.value * 2 * pi, centre: center); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/anim/spring_widget.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class SpringWidget extends StatefulWidget { const SpringWidget({Key? key}) : super(key: key); @override _SpringWidgetState createState() => _SpringWidgetState(); } const double _kDefaultSpringHeight = 100; //弹簧默认高度 const double _kRateOfMove = 1.5; //移动距离与形变量比值 const double _kK = 3; class _SpringWidgetState extends State with SingleTickerProviderStateMixin { ValueNotifier height = ValueNotifier(_kDefaultSpringHeight); late AnimationController _ctrl; double s = 0; // 移动距离 double laseMoveLen = 0; late Animation animation; final Duration animDuration = const Duration(milliseconds: 500); @override void initState() { super.initState(); _ctrl = AnimationController(vsync: this, duration: animDuration) ..addListener(_updateHeightByAnim); // animation = CurvedAnimation(parent: _ctrl, curve: Curves.bounceOut); animation = CurvedAnimation(parent: _ctrl, curve: const Interpolator()); } void _updateHeightByAnim() { s = laseMoveLen * (1 - animation.value); height.value = _kDefaultSpringHeight + (-s / _kRateOfMove); } @override void dispose() { _ctrl.dispose(); height.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( onVerticalDragUpdate: _updateSpace, onVerticalDragEnd: _animateToDefault, child: Container( width: 200, height: 200, color: Colors.grey.withAlpha(11), child: CustomPaint( painter: SpringPainter(height: height)), ), ); } double f = 0; void _updateSpace(DragUpdateDetails details) { s += details.delta.dy; height.value = _kDefaultSpringHeight + dx; } double get dx => -s / _kRateOfMove; double get k => _kK; void _animateToDefault(DragEndDetails details) { // f = k * dx; // print('----[弹性系数:$_kK]---[移动了:$dx]----[可提供弹力:$f]------------'); laseMoveLen = s; _ctrl.forward(from: 0); } } class Interpolator extends Curve { const Interpolator(); @override double transformInternal(double t) { t -= 1.0; return t * t * t * t * t + 1.0; } } const double _kSpringWidth = 30; class SpringPainter extends CustomPainter { final int count; final ValueListenable height; SpringPainter({this.count = 20,required this.height}):super(repaint: height); final Paint _paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1; @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2+_kSpringWidth / 2, size.height); Path springPath = Path(); springPath.relativeLineTo(-_kSpringWidth, 0); double space = height.value/(count+1); for (int i = 1; i < count; i++) { if (i % 2 == 1) { springPath.relativeLineTo(_kSpringWidth, -space); } else { springPath.relativeLineTo(-_kSpringWidth, -space); } } if(count.isOdd){ springPath.relativeLineTo(_kSpringWidth, 0); }else{ springPath.relativeLineTo(-_kSpringWidth, 0); } canvas.drawPath(springPath, _paint); } @override bool shouldRepaint(covariant SpringPainter oldDelegate) => oldDelegate.count != count || oldDelegate.height != height; } ================================================ FILE: modules/painting_system/draw_system/lib/src/art/circle_packing.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import '../utils/colors.dart'; class Circle { Point center = const Point(0,0); double radius = 10; Color color = Colors.black; } class CirclePacking extends StatefulWidget { const CirclePacking({Key? key}) : super(key: key); @override _CirclePackingState createState() => _CirclePackingState(); } class _CirclePackingState extends State { @override Widget build(BuildContext context) { return GestureDetector( onTap: (){ setState(() { }); }, child: CustomPaint( painter: CirclePackingPainter(), ), ); } } class CirclePackingPainter extends CustomPainter { List circles = []; double minRaidus = 2; double maxRaidus = 100; int totalCircles = 500; int createCircleAttemps = 500; Random random = Random(); void _createCircles(Canvas canvas, Size size) { Circle circle= Circle(); bool circleSafeToDraw = false; for (int i = 0; i < createCircleAttemps; i++) { circle ..radius = minRaidus ..center = Point( random.nextDouble() * size.width, random.nextDouble() * size.height, ) ..color = colors[Random().nextInt(colors.length)]; if (_doesHaveACollision(circle, size)) { continue; } else { circleSafeToDraw = true; break; } } if (!circleSafeToDraw) { return; } for (double i = minRaidus; i < maxRaidus; i++) { circle.radius = i; if (_doesHaveACollision(circle, size)) { circle.radius--; break; } } circles.add(circle); } void _drawCircles(Canvas canvas) { Paint paint = Paint() ..strokeWidth = 0.5 ..isAntiAlias = true ..style = PaintingStyle.stroke; circles.asMap().forEach((key, circle) { paint.color = Colors.black; Offset offset = Offset(circle.center.x.toDouble(), circle.center.y.toDouble()); canvas.drawCircle(offset, circle.radius, paint); }); } bool _doesHaveACollision(Circle circle, Size size) { for (int i = 0; i < circles.length; i++) { Circle otherCircle = circles[i]; double r2 = circle.radius + otherCircle.radius; if (r2 >= circle.center.distanceTo(otherCircle.center) - 1) { return true; } } if (circle.center.x + circle.radius >= size.width || circle.center.x - circle.radius <= 0) { return true; } if (circle.center.y + circle.radius >= size.height || circle.center.y - circle.radius <= 0) { return true; } return false; } @override void paint(Canvas canvas, Size size) { for (int i = 0; i < totalCircles; i++) { _createCircles(canvas, size); _drawCircles(canvas); } } @override bool shouldRepaint(CirclePackingPainter oldDelegate) => true; } ================================================ FILE: modules/painting_system/draw_system/lib/src/art/cubic_disarray.dart ================================================ import 'dart:math'; import 'package:flutter/widgets.dart'; class CubicDisarray extends StatefulWidget { const CubicDisarray({Key? key}) : super(key: key); @override _CubicDisarrayState createState() => _CubicDisarrayState(); } class _CubicDisarrayState extends State { @override Widget build(BuildContext context) { return GestureDetector( onTap: (){ setState(() { }); }, child: CustomPaint( painter: CubicDisarrayPainter(), ), ); } } class CubicDisarrayPainter extends CustomPainter { void _draw(double width, double height, Canvas canvas, Size size) { Paint paint = Paint() ..strokeWidth = 2 ..style = PaintingStyle.stroke; Rect rect = Rect.fromLTWH( -width / 2, -height / 2, width, height, ); canvas.drawRect(rect, paint); } @override void paint(Canvas canvas, Size size) { double squareSize = 40.0; double randomDisplacement = 20.0; double rotateMultiplier = 20.0; double offset = 20.0; double canvasSize = size.width - offset; for (double i = squareSize; i <= canvasSize - squareSize; i += squareSize) { for (double j = squareSize; j <= canvasSize; j += squareSize) { double plusOrMinus = Random().nextBool() ? -1 : 1; double rotateAmt = j / canvasSize * pi / 180 * plusOrMinus * Random().nextDouble() * rotateMultiplier; plusOrMinus = Random().nextBool() ? -1 : 1; double translateAmt = j / canvasSize * plusOrMinus * Random().nextDouble() * randomDisplacement; canvas.save(); canvas.translate(i + translateAmt + offset, j); canvas.rotate(rotateAmt); _draw(squareSize, squareSize, canvas, size); canvas.restore(); } } } @override bool shouldRepaint(CubicDisarrayPainter oldDelegate) => true; } ================================================ FILE: modules/painting_system/draw_system/lib/src/art/hypnotic_squares.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import '../utils/colors.dart'; class HypnoticSquares extends StatefulWidget { const HypnoticSquares({Key? key}) : super(key: key); @override _HypnoticSquaresState createState() => _HypnoticSquaresState(); } class _HypnoticSquaresState extends State { @override Widget build(BuildContext context) { return GestureDetector( onTap: (){ setState(() { }); }, child: CustomPaint( painter: HypnoticSquaresPainter(), ), ); } } class HypnoticSquaresPainter extends CustomPainter { double startSteps = 1; double offset = 4.0; double finalSize = 10.0; Random random = Random(); List directions = [-1.0, 0.0, 1.0]; void _drawRect(Canvas canvas, double x, double y, double size) { Paint paint = Paint() ..strokeWidth = 2.0 ..style = PaintingStyle.stroke ..color = colors[Random().nextInt(colors.length)]; Rect rect = Rect.fromLTWH(x, y, size, size); canvas.drawRect(rect, paint); } void _draw( double x, double y, double size, double xMovement, double yMovement, double steps, Canvas canvas, ) { _drawRect(canvas, x, y, size); if (steps >= 0) { final newSize = size * (steps / startSteps) + finalSize; double newX = x + (size - newSize) / 2; double newY = y + (size - newSize) / 2; newX = newX - ((x - newX) / (steps + 2)) * xMovement; newY = newY - ((y - newY) / (steps + 2)) * yMovement; _draw( newX, newY, newSize, xMovement, yMovement, steps - 1, canvas, ); } } @override void paint(Canvas canvas, Size size) { double canvasSize = size.width; double tileStep = (canvasSize - offset * 2) / 6; double startSize = tileStep; for (double x = offset; x < canvasSize - offset; x += tileStep) { for (double y = offset; y < canvasSize - offset; y += tileStep) { startSteps = 2.0 + random.nextInt(10); double xDirection = directions[random.nextInt(directions.length)]; double yDirection = directions[random.nextInt(directions.length)]; _draw( x, y, startSize, xDirection, yDirection, startSteps - 1, canvas, ); } } } @override bool shouldRepaint(HypnoticSquaresPainter oldDelegate) => true; } ================================================ FILE: modules/painting_system/draw_system/lib/src/art/joy_division.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; class JoyDivision extends StatefulWidget { const JoyDivision({Key? key}) : super(key: key); @override _JoyDivisionState createState() => _JoyDivisionState(); } class _JoyDivisionState extends State { @override Widget build(BuildContext context) { return GestureDetector( onTap: (){ setState(() { }); }, child: CustomPaint( painter: JoyDivisionPainter(), ), ); } } class JoyDivisionPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { double width = size.width; double step = 10; List> lines = []; for (double i = step; i <= width - step; i += step) { List line = []; for (double j = step; j <= width - step; j += step) { double distanceToCenter = (j - width / 2).abs(); double variance = max(width / 2 - 50 - distanceToCenter, 0); double random = Random().nextDouble() * variance / 2 * -1; Point point = Point(j, i + random); line.add(point); } lines.add(line); } Paint paint = Paint() ..strokeWidth = 1.3 ..color = Colors.black ..style = PaintingStyle.stroke // ..strokeCap=StrokeCap.round ..isAntiAlias = true; for (int i = 5; i < lines.length; i++) { Point p1 = lines[i][0]; Path path = Path()..moveTo(p1.x.toDouble(), p1.y.toDouble()); for (int j = 0; j < lines[i].length - 2; j++) { double xc = (lines[i][j].x + lines[i][j + 1].x) / 2; double yc = (lines[i][j].y + lines[i][j + 1].y) / 2; Point p2 = lines[i][j]; path.quadraticBezierTo(p2.x.toDouble(), p2.y.toDouble(), xc, yc); canvas.drawPath(path, paint); } } } @override bool shouldRepaint(JoyDivisionPainter oldDelegate) => true; } ================================================ FILE: modules/painting_system/draw_system/lib/src/art/piet_mondrian.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import '../utils/colors.dart'; class PietMondrian extends StatefulWidget { const PietMondrian({Key? key}) : super(key: key); @override _PietMondrianState createState() => _PietMondrianState(); } class _PietMondrianState extends State { @override Widget build(BuildContext context) { return GestureDetector( onTap: (){ setState(() { }); }, child: CustomPaint( painter: PietMondrianPainnter(), ), ); } } class Square { final double x; final double y; final double width; final double height; final Color color; Square({ required this.x, required this.y, required this.width, required this.height, required this.color, }); Map toJson() { return { "x": x, "y": y, "width": width, "height": height, "color": color.toString(), }; } } class PietMondrianPainnter extends CustomPainter { void _draw(Canvas canvas, List squares) { final Paint paint = Paint() ..strokeWidth = 2 ..isAntiAlias = true ..style = PaintingStyle.fill; final Paint paint2 = Paint() ..strokeWidth = 2 ..isAntiAlias = true ..color = Colors.black54 ..style = PaintingStyle.stroke; for (int i = 0; i < squares.length; i++) { final Square square = squares[i]; final Rect rect = Rect.fromLTWH( square.x, square.y, square.width, square.height, ); paint.color = square.color; // paint.color = Color.lerp(a, b, t); // paint.color = Random().nextBool() // ? Colors.white // : colors[Random().nextInt(colors.length)].withOpacity(.8); canvas.drawRect(rect, paint); canvas.drawRect(rect, paint2); } } void _splitSquaresWith(Map coordinates, List squares) { final double x = coordinates["x"]??0; final double y = coordinates["y"]??0; for (int i = squares.length - 1; i >= 0; i--) { final Square square = squares[i]; if (x > square.x && x < square.x + square.width) { if (Random().nextBool()) { squares.removeAt(i); _splitOnX(square, x, squares); } } if (y > square.y && y < square.y + square.height) { if (Random().nextBool()) { squares.removeAt(i); _splitOnY(square, y, squares); } } } } void _splitOnX(Square square, double splitAt, List squares) { final Square squareA = Square( x: square.x, y: square.y, width: square.width - (square.width - splitAt + square.x), height: square.height, color: colors[Random().nextInt(colors.length)].withOpacity(.8), ); final Square squareB = Square( x: splitAt, y: square.y, width: square.width - splitAt + square.x, height: square.height, color: colors[Random().nextInt(colors.length)].withOpacity(.8), ); squares.add(squareA); squares.add(squareB); } void _splitOnY(Square square, double splitAt, List squares) { final Square squareA = Square( x: square.x, y: square.y, width: square.width, height: square.height - (square.height - splitAt + square.y), color: colors[Random().nextInt(colors.length)].withOpacity(.8), ); final Square squareB = Square( x: square.x, y: splitAt, width: square.width, height: square.height - splitAt + square.y, color: colors[Random().nextInt(colors.length)].withOpacity(.8), ); squares.add(squareA); squares.add(squareB); } @override void paint(Canvas canvas, Size size) { final List squares = [ Square( x: 0, y: 0, width: size.width, height: size.height, color: Colors.white, ), ]; double step = size.width / 7; for (double i = 0; i < size.width; i += step) { _splitSquaresWith({"y": i}, squares); _splitSquaresWith({"x": i}, squares); } _draw(canvas, squares); } @override bool shouldRepaint(PietMondrianPainnter oldDelegate) => true; } ================================================ FILE: modules/painting_system/draw_system/lib/src/art/tiled_lines.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; class TiledLines extends StatefulWidget { const TiledLines({Key? key}) : super(key: key); @override _TiledLinesState createState() => _TiledLinesState(); } class _TiledLinesState extends State { @override Widget build(BuildContext context) { return GestureDetector( onTap: (){ setState(() { }); }, child: CustomPaint( painter: TiledLinesPainter(20), ), ); } } class TiledLinesPainter extends CustomPainter { final double step; TiledLinesPainter(this.step); void _drawLine( Canvas canvas, double x, double y, double width, double height, ) { final bool isLeftToRight = Random().nextBool(); final Paint paint = Paint() ..strokeCap = StrokeCap.square ..strokeWidth = 2; Offset p1; Offset p2; if (isLeftToRight) { p1 = Offset(x, y); p2 = Offset(x + width, y + height); } else { p1 = Offset(x + width, y); p2 = Offset(x, y + height); } canvas.drawLine(p1, p2, paint); } @override void paint(Canvas canvas, Size size) { canvas.clipRect(Rect.fromPoints(Offset.zero, Offset(size.width,size.height))); for (double x = 0; x < size.width; x += step) { for (double y = 0; y < size.height; y += step) { _drawLine(canvas, x, y, step, step); } } } @override bool shouldRepaint(TiledLinesPainter oldDelegate) => true; } ================================================ FILE: modules/painting_system/draw_system/lib/src/art/triangular_mesh.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import '../utils/colors.dart'; class Point { double x=0; double y=0; } class TriangularMesh extends StatefulWidget { const TriangularMesh({Key? key}) : super(key: key); @override _TriangularMeshState createState() => _TriangularMeshState(); } class _TriangularMeshState extends State { @override Widget build(BuildContext context) { return GestureDetector( onTap: (){ setState(() { }); }, child: CustomPaint( painter: TriangularMeshPainter(), ), ); } } class TriangularMeshPainter extends CustomPainter { void _drawTriangle(Canvas canvas, Point pointA, Point pointB, Point pointC) { Path path = Path(); Paint fill = Paint() // ..color = Colors.black.withOpacity(Random().nextDouble() * .9) ..color = colors[Random().nextInt(colors.length)].withOpacity(.8) ..style = PaintingStyle.fill ..strokeJoin = StrokeJoin.bevel ..isAntiAlias = true; Paint line = Paint() ..color = Colors.black ..style = PaintingStyle.stroke ..strokeJoin = StrokeJoin.bevel ..isAntiAlias = true; path.moveTo(pointA.x, pointA.y); path.lineTo(pointB.x, pointB.y); path.lineTo(pointC.x, pointC.y); path.lineTo(pointA.x, pointA.y); canvas.drawPath(path, fill); canvas.drawPath(path, line); } @override void paint(Canvas canvas, Size size) { List> lines = []; bool odd = false; List line; double gap = size.width / 8; for (double y = gap / 2; y <= size.height; y += gap) { odd = !odd; line = []; for (double x = gap / 4; x <= size.width; x += gap) { Point point = Point(); double random = (Random().nextDouble() * .8 - .4) * gap; point.x = x + random + (odd ? gap / 2 : 0); point.y = y + (Random().nextDouble() * .8 - .4) * gap; line.add(point); } lines.add(line); } odd = true; List dotLine; for (int y = 0; y < lines.length - 1; y++) { odd = !odd; dotLine = []; for (int i = 0; i < lines[y].length; i++) { dotLine.add(odd ? lines[y][i] : lines[y + 1][i]); dotLine.add(odd ? lines[y + 1][i] : lines[y][i]); } for (int i = 0; i < dotLine.length - 2; i++) { _drawTriangle(canvas, dotLine[i], dotLine[i + 1], dotLine[i + 2]); } } } @override bool shouldRepaint(TriangularMeshPainter oldDelegate) => true; } ================================================ FILE: modules/painting_system/draw_system/lib/src/art/un_deux_trois.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; class UnDeuxTrois extends StatefulWidget { const UnDeuxTrois({Key? key}) : super(key: key); @override _UnDeuxTroisState createState() => _UnDeuxTroisState(); } class _UnDeuxTroisState extends State { @override Widget build(BuildContext context) { return GestureDetector( onTap: (){ setState(() { }); }, child: CustomPaint( painter: UnDeuxTroisPainter(), ), ); } } class UnDeuxTroisPainter extends CustomPainter { void _draw( double x, double y, double width, double height, List positions, Canvas canvas, ) { canvas.save(); canvas.translate(x + width / 2, y + height / 2); canvas.rotate(Random().nextDouble() * 5); canvas.translate(-width / 2, -height / 2); Paint paint = Paint() ..isAntiAlias = true ..strokeWidth = 4 ..strokeCap = StrokeCap.round; for (int i = 0; i < positions.length; i++) { Offset p1 = Offset(positions[i] * width, 0); Offset p2 = Offset(positions[i] * width, height); canvas.drawLine(p1, p2, paint); } canvas.restore(); } @override void paint(Canvas canvas, Size size) { double height = size.height; double width = size.width; double step = 30; double aThirdOfHeight = size.height / 3; for (double y = step; y < height - step; y += step) { for (double x = step; x < width - step; x += step) { if (y < aThirdOfHeight) { _draw(x, y, step, step, [0.5], canvas); } else if (y < aThirdOfHeight * 2) { _draw(x, y, step, step, [0.2, 0.8], canvas); } else { _draw(x, y, step, step, [0.1, 0.5, 0.9], canvas); } } } } @override bool shouldRepaint(UnDeuxTroisPainter oldDelegate) => true; } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/clock_widget.dart ================================================ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; /// create by 张风捷特烈 on 2021/2/7 /// contact me by email 1981462002@qq.com /// 说明: class ClockWidget extends StatefulWidget { final double radius; const ClockWidget({Key? key, this.radius = 100}) : super(key: key); @override _ClockWidgetState createState() => _ClockWidgetState(); } class _ClockWidgetState extends State with SingleTickerProviderStateMixin { late Ticker _ticker; ValueNotifier time = ValueNotifier(DateTime.now()); @override void initState() { super.initState(); _ticker = createTicker(_tick)..start(); } @override void dispose() { _ticker.dispose(); time.dispose(); super.dispose(); } void _tick(Duration duration) { if (time.value.second != DateTime.now().second) { time.value = DateTime.now(); } } @override Widget build(BuildContext context) { return Stack( alignment: Alignment.center, children: [ CustomPaint( size: Size(widget.radius * 2, widget.radius * 2), painter: ClockBgPainter(radius: widget.radius), ), RepaintBoundary( child: CustomPaint( size: Size(widget.radius * 2, widget.radius * 2), painter: ClockPainter(listenable: time, radius: widget.radius), ), ) ], ); } } class ClockBgPainter extends CustomPainter { final Paint _paint = Paint()..style = PaintingStyle.stroke; final double radius; ClockBgPainter({this.radius = 100}); @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); drawOuterCircle(canvas); drawScale(canvas); drawText(canvas); } @override bool shouldRepaint(covariant ClockBgPainter oldDelegate) { return oldDelegate.radius != radius; } void drawOuterCircle(Canvas canvas) { _paint ..strokeWidth = 4 ..color = const Color(0xffD5D5D5); for (int i = 0; i < 4; i++) { _paintArc(canvas); canvas.rotate(pi / 2); } } final Paint arcPaint = Paint() ..style = PaintingStyle.fill ..color = const Color(0xff00abf2); void _paintArc(Canvas canvas) { arcPaint.maskFilter = MaskFilter.blur(BlurStyle.solid, logic1); final Path circlePath = Path() ..addArc( Rect.fromCenter( center: const Offset(0, 0), width: radius * 2, height: radius * 2), 10 / 180 * pi, pi / 2 - 20 / 180 * pi); Path circlePath2 = Path() ..addArc( Rect.fromCenter( center: Offset(-logic1, 0), width: radius * 2, height: radius * 2), 10 / 180 * pi, pi / 2 - 20 / 180 * pi); //联合路径 Path result = Path.combine(PathOperation.difference, circlePath, circlePath2); canvas.drawPath(result, arcPaint); //绘制 } void drawScale(Canvas canvas) { _paint ..strokeCap = StrokeCap.round ..style = PaintingStyle.fill; double count = 60; double perAngle = 2 * pi / count; for (int i = 0; i < count; i++) { if (i % 5 == 0) { _paint ..strokeWidth = longLineWidth ..color = Colors.blue; canvas.drawLine(Offset(radius - scaleSpace, 0), Offset(radius - scaleSpace - longScaleLen, 0), _paint); canvas.drawCircle( Offset(radius - scaleSpace - longScaleLen - logic1 * 5, 0), longLineWidth, _paint..color = Colors.orange); } else { _paint ..strokeWidth = shortLenWidth ..color = Colors.black; canvas.drawLine(Offset(radius - scaleSpace, 0), Offset(radius - scaleSpace - shortScaleLen, 0), _paint); } canvas.rotate(perAngle); } } double get logic1 => radius * 0.01; // 刻度与外圈的间隔 double get scaleSpace => logic1 * 11; // 短刻度线长 double get shortScaleLen => logic1 * 7; // 短刻度线长 double get shortLenWidth => logic1; // 长刻度线长 double get longScaleLen => logic1 * 11; // 长刻度线宽 double get longLineWidth => logic1 * 2; final TextPainter _textPainter = TextPainter( textAlign: TextAlign.center, textDirection: TextDirection.ltr); void drawText(Canvas canvas) { _drawCircleText(canvas, 'Ⅸ', offsetX: -radius); _drawCircleText(canvas, 'Ⅲ', offsetX: radius); _drawCircleText(canvas, 'Ⅵ', offsetY: radius); _drawCircleText(canvas, 'Ⅻ', offsetY: -radius); _drawLogoText(canvas, offsetY: -radius * 0.5); } _drawCircleText(Canvas canvas, String text, {double offsetX = 0, double offsetY = 0}) { _textPainter.text = TextSpan( text: text, style: TextStyle(fontSize: radius * 0.15, color: Colors.blue)); _textPainter.layout(); _textPainter.paint( canvas, Offset.zero.translate(-_textPainter.size.width / 2 + offsetX, -_textPainter.height / 2 + offsetY)); } _drawLogoText(Canvas canvas, {double offsetX = 0, double offsetY = 0}) { _textPainter.text = TextSpan( text: 'Toly', style: TextStyle( fontSize: radius * 0.2, color: Colors.blue, fontFamily: 'CHOPS')); _textPainter.layout(); _textPainter.paint( canvas, Offset.zero.translate(-_textPainter.size.width / 2 + offsetX, -_textPainter.height / 2 + offsetY)); } } class ClockPainter extends CustomPainter { final Paint _paint = Paint()..style = PaintingStyle.stroke; final double radius; final ValueListenable listenable; ClockPainter({required this.listenable, this.radius = 100}) : super(repaint: listenable); @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); drawArrow(canvas, listenable.value); } void drawArrow(Canvas canvas, DateTime time) { int sec = time.second; int min = time.minute; int hour = time.hour; double perAngle = 360 / 60; double secondRad = (sec * perAngle) / 180 * pi; double minusRad = ((min + sec / 60) * perAngle) / 180 * pi; double hourRad = ((hour + min / 60 + sec / 3600) * perAngle * 5) / 180 * pi; canvas.save(); canvas.rotate(-pi / 2); canvas.save(); canvas.rotate(minusRad); drawMinus(canvas); canvas.restore(); canvas.save(); canvas.rotate(hourRad); drawHour(canvas); canvas.restore(); canvas.save(); canvas.rotate(secondRad); drawSecond(canvas); canvas.restore(); canvas.restore(); } @override bool shouldRepaint(covariant ClockPainter oldDelegate) { return oldDelegate.radius != radius || oldDelegate.listenable != listenable; } // 分针长 double get minusLen => logic1 * 60; // 时针长 double get hourLen => logic1 * 45; // 秒针长 double get secondLen => logic1 * 68; // 时针线宽 double get hourLineWidth => logic1 * 3; // 分针线宽 double get minusLineWidth => logic1 * 2; // 秒针线宽 double get logic1 => radius * 0.01; double get secondLineWidth => logic1; // 长刻度线宽 double get longLineWidth => logic1 * 2; void drawHour(Canvas canvas) { _paint ..strokeWidth = hourLineWidth ..color = const Color(0xff8FC552) ..strokeCap = StrokeCap.round; canvas.drawLine(Offset.zero, Offset(hourLen, 0), _paint); } void drawMinus(Canvas canvas) { _paint ..strokeWidth = minusLineWidth ..color = const Color(0xff87B953) ..strokeCap = StrokeCap.round; canvas.drawLine( Offset.zero, Offset( minusLen, 0, ), _paint); } void drawSecond(Canvas canvas) { _paint ..strokeWidth = logic1 * 2.5 ..color = const Color(0xff6B6B6B) ..strokeCap = StrokeCap.square ..style = PaintingStyle.stroke; Path path = Path(); canvas.save(); canvas.rotate((360 - 270) / 2 / 180 * pi); path.addArc( Rect.fromPoints( Offset(-logic1 * 9, -logic1 * 9), Offset(logic1 * 9, logic1 * 9)), 0, 270 / 180 * pi); canvas.drawPath(path, _paint); canvas.restore(); _paint.strokeCap = StrokeCap.round; canvas.drawLine(Offset(-logic1 * 9, 0), Offset(-logic1 * 20, 0), _paint); _paint ..strokeWidth = logic1 ..color = Colors.black; canvas.drawLine(Offset.zero, Offset(secondLen, 0), _paint); _paint ..strokeWidth = logic1 * 3 ..color = const Color(0xff6B6B6B); canvas.drawCircle(Offset.zero, logic1 * 5, _paint); _paint ..color = const Color(0xff8FC552) ..style = PaintingStyle.fill; canvas.drawCircle(Offset.zero, logic1 * 4, _paint); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/digital/digital_painter.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'digital_path.dart'; class DigitalPainter extends CustomPainter { final int value; final Color color; final DigitalPath digitalPath; late Paint _mainPainter; DigitalPainter({required this.value,required this.digitalPath,required this.color}){ _mainPainter = Paint()..style=PaintingStyle.fill..color=color; } @override void paint(Canvas canvas, Size size) { Path path = digitalPath.buildPath(value, size.width); canvas.drawPath(path, _mainPainter); } @override bool shouldRepaint(covariant DigitalPainter oldDelegate) { return oldDelegate.value != value; } } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/digital/digital_path.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; class DigitalPath { static const double kDigitalRate = 169/104; // final Map _digitalPathMap = {}; DigitalPath(){ _initDigitalPathMap(); } Path buildPath(int value,double width){ double rate = width/104; Matrix4 matrix4 = Matrix4.identity(); matrix4.scale(rate,rate,0.0); return _digitalPathMap[value.toString()]!.transform(matrix4.storage); } Path combineAll(List paths, {PathOperation operation = PathOperation.union}) { if (paths.isEmpty) return Path(); if (paths.length <= 1) return paths.first; Path result = paths.first; for (int i = 1; i < paths.length; i++) { result = Path.combine(operation, paths[i], result); } return result; } void _initDigitalPathMap(){ Map map = {}; _digitalPathMap.clear(); double strokeWidth = 26; double width = 104; double height = 169; double gap = 5; double angle = 43; Path path1 = Path() ..moveTo(gap, 0) ..relativeLineTo(width-gap*2 , 0) ..relativeLineTo(-strokeWidth/tan(angle*pi/180), strokeWidth) ..lineTo(strokeWidth/tan(angle*pi/180)+gap, strokeWidth)..close(); Path path2 = Path() ..moveTo(0, 2) ..lineTo(0 , 74) ..lineTo(13 , 83) ..lineTo(26 , 71) ..lineTo(26 , 27) ..close(); Matrix4 mirrorY = Matrix4.identity(); mirrorY.translate(width/2,0.0); mirrorY.scale(-1.0,1.0,0.0); mirrorY.translate(-width/2,0.0); Path path4 = path2.transform(mirrorY.storage); Matrix4 mirrorX = Matrix4.identity(); mirrorX.translate(0.0,height/2); mirrorX.scale(1.0,-1.0,0.0); mirrorX.translate(0.0,-height/2); Path path5 = path2.transform(mirrorX.storage); Path path7 = path5.transform(mirrorY.storage); Path path6 = path1.transform(mirrorX.storage); Path path3 = Path() ..moveTo(18, 84) ..lineTo(31 , 97) ..lineTo(73 , 97) ..lineTo(86 , 85) ..lineTo(75 , 74) ..lineTo(31 , 74) ..close(); map[1] = path1; map[2] = path2; map[3] = path3; map[4] = path4; map[5] = path5; map[6] = path6; map[7] = path7; digitalMap.forEach((key, v) { List paths = v.map((value) => map[value]!).toList(); Path path = combineAll(paths); _digitalPathMap[key] = path; }); } } Map> digitalMap = { '0': [1,2,4,5,6,7], '1': [4,7], '2': [1,4,3,5,6], '3': [1,4,3,6,7], '4': [2,3,4,7], '5': [1,2,3,6,7], '6': [1,2,3,5,6,7], '7': [1,4,7], '8': [1,2,3,4,5,6,7], '9': [1,2,3,4,6,7], }; ================================================ FILE: modules/painting_system/draw_system/lib/src/base/digital/digital_shower.dart ================================================ import 'package:flutter/material.dart'; import 'digital_path.dart'; import 'digital_widget.dart'; class DigitalShower extends StatefulWidget { const DigitalShower({Key? key}) : super(key: key); @override State createState() => _DigitalShowerState(); } class _DigitalShowerState extends State { int _count = 0; final DigitalPath digitalPath = DigitalPath(); @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: () { _count++; setState(() {}); }, ), body: Center( child: MultiDigitalWidget( colors: [Colors.indigo,], width: 50, spacing: 16, count: 4, value: _count, ) ), ); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/digital/digital_widget.dart ================================================ import 'package:flutter/material.dart'; import 'digital_painter.dart'; import 'digital_path.dart'; import 'dart:math' as math; class SingleDigitalWidget extends StatelessWidget { final double width; final Color color; final int value; final DigitalPath digitalPath; SingleDigitalWidget( {Key? key, required this.width, required this.value, DigitalPath? digitalPath, this.color = Colors.black}) : digitalPath = digitalPath ?? DigitalPath(), super(key: key); @override Widget build(BuildContext context) { return CustomPaint( size: Size(width, width * DigitalPath.kDigitalRate), painter: DigitalPainter( color: color, value: value, digitalPath: digitalPath, ), ); } } // 展示若干位数字 class MultiDigitalWidget extends StatelessWidget { final int count; final int value; final DigitalPath digitalPath; final double spacing; final double runSpacing; final double width; final List colors; MultiDigitalWidget({ Key? key, required this.count, required this.value, this.spacing = 26, this.runSpacing = 26, required this.width, this.colors = const [], DigitalPath? digitalPath, }) : digitalPath = digitalPath ?? DigitalPath(), super(key: key); @override Widget build(BuildContext context) { int max = math.pow(10, count).toInt(); String numStr = (value % max).toString().padLeft(count, "0"); Color color = Colors.black; return Wrap( spacing: spacing, runSpacing: runSpacing, children: List.generate( count, (index) { if(index false; } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/draw_path_fun.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'dart:ui' as ui; // import '../coordinate_pro.dart'; /// create by 张风捷特烈 on 2020/5/1 /// contact me by email 1981462002@qq.com /// 说明: class DrawPathFun extends StatelessWidget { const DrawPathFun({Key? key}) : super(key: key); @override Widget build(BuildContext context) { double size = MediaQuery.of(context).size.shortestSide; return Container( color: Colors.white, child: CustomPaint( size: Size(size, size), // 使用CustomPaint painter: PaperPainter(), ), ); } } class PaperPainter extends CustomPainter { // final Coordinate coordinate = Coordinate(); final List points = []; final double step = 6; final double min = -240; final double max = 240; final List colors = const[ Color(0xFFF60C0C), Color(0xFFF3B913), Color(0xFFE7F716), Color(0xFF3DF30B), Color(0xFF0DF6EF), Color(0xFF0829FB), Color(0xFFB709F4), ]; void initPoints() { for (double x = min; x < max; x += step) { points.add(Offset(x, f(x))); } points.add(Offset(max, f(max))); points.add(Offset(max, f(max))); } void initPointsWithPolar() { for (double x = min; x < max; x += step) { double thta = (pi / 180 * x); // 角度转化为弧度 var p = f(thta); points.add(Offset(p * cos(thta), p * sin(thta))); } } double f(double thta) { double p = 40 * (pow(e, cos(thta)) - 2 * cos(4 * thta) + pow(sin(thta / 12), 5)); return p; } @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); initPointsWithPolar(); Paint paint = Paint() ..color = Colors.red ..strokeWidth = 1.5 ..style = PaintingStyle.stroke; var pos = [1.0 / 7, 2.0 / 7, 3.0 / 7, 4.0 / 7, 5.0 / 7, 6.0 / 7, 1.0]; paint.shader = ui.Gradient.linear( const Offset(0, 0), const Offset(100, 0), colors, pos, TileMode.mirror); Offset p1 = points[0]; Path path = Path()..moveTo(p1.dx, p1.dy); for (var i = 1; i < points.length - 1; i++) { double xc = (points[i].dx + points[i + 1].dx) / 2; double yc = (points[i].dy + points[i + 1].dy) / 2; Offset p2 = points[i]; path.quadraticBezierTo(p2.dx, p2.dy, xc, yc); } canvas.drawPath(path, paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => false; } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/draw_picture.dart ================================================ import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'dart:ui' as ui; import 'package:flutter/services.dart'; /// create by 张风捷特烈 on 2020/10/10 /// contact me by email 1981462002@qq.com /// 说明: class DrawPicture extends StatefulWidget { const DrawPicture({Key? key}) : super(key: key); @override _DrawPictureState createState() => _DrawPictureState(); } class _DrawPictureState extends State { ui.Image? _image; @override void initState() { super.initState(); _loadImage(); } void _loadImage() async { _image = await loadImageFromAssets('assets/images/sabar.webp'); if (mounted) setState(() {}); } //读取 assets 中的图片 Future loadImageFromAssets(String path) async { ByteData data = await rootBundle.load(path); List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); return decodeImageFromList(Uint8List.fromList(bytes)); } @override Widget build(BuildContext context) { double size = MediaQuery.of(context).size.shortestSide; return CustomPaint(size: Size(size, size), painter: PaperPainter(_image)); } } class PaperPainter extends CustomPainter { final Paint _paint; final double strokeWidth = 0.5; final Color color = Colors.blue; final ui.Image? image; PaperPainter(this.image) : _paint = Paint() ..filterQuality = FilterQuality.high ..color = Colors.black.withAlpha(180); @override void paint(Canvas canvas, Size size) { canvas.clipRect(Offset.zero & size); _drawImage(canvas, size); _drawLine(size, canvas); } void _drawLine(Size size, Canvas canvas) { _paint.color = const Color(0xFFF0F0F0); double step = 10.0; for (int i = 1; i <= size.height / step; i++) { canvas.drawLine(Offset(step * i, 0), Offset(0, step * i), _paint); canvas.drawLine( Offset(step * i, size.height), Offset(size.width, step * i), _paint); } } void _drawImage(Canvas canvas, Size size) { if (image != null) { canvas.drawImageRect( image!, Rect.fromCenter( center: Offset(image!.width / 2, image!.height / 2), width: image!.width * 1.0, height: image!.width * 1.0), Rect.fromLTRB(0, 0, size.width, size.height), _paint); } } @override bool shouldRepaint(PaperPainter oldDelegate) => oldDelegate.image != image; } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/n_side/n_side_page.dart ================================================ import 'package:flutter/material.dart'; import 'shape_painter.dart'; class NSidePage extends StatefulWidget { final int count; const NSidePage({Key? key, this.count = 9}) : super(key: key); @override State createState() => _NSidePageState(); } class _NSidePageState extends State { int _count = 0; @override void initState() { super.initState(); _count = widget.count; } @override Widget build(BuildContext context) { return Stack( fit: StackFit.expand, children: [ CustomPaint( painter: ShapePainter(_count), size: const Size(200, 200), ), Positioned( left: 0, top: 0, child: IconButton( onPressed: () { setState(() { if(_count<=3)return; _count--; }); }, icon: const Icon(Icons.remove), )), Positioned( right: 0, top: 0, child: IconButton( onPressed: () { setState(() { _count++; }); }, icon: const Icon(Icons.add), )) ], ); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/n_side/shape_painter.dart ================================================ import 'dart:math'; import 'package:dash_painter/dash_painter.dart'; import 'package:flutter/material.dart'; class ShapePainter extends CustomPainter { final int n; ShapePainter(this.n); @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); int count = n; double radius = 140 / 2; drawAxis(canvas, size); List points = []; double rotate = -pi / 2; for (int i = 0; i < count; i++) { double perRad = 2 * pi / count * i; points.add(Offset( radius * cos(perRad + rotate), radius * sin(perRad + rotate), )); } _drawShape(canvas, points); _drawShapeHelper(canvas, points, radius); } final Paint shapePaint = Paint()..style = PaintingStyle.stroke..color=Colors.blue..strokeWidth=2; void _drawShape(Canvas canvas, List points) { Path shapePath = Path(); shapePath.moveTo(points[0].dx, points[0].dy); for (int i = 1; i < points.length; i++) { shapePath.lineTo(points[i].dx, points[i].dy); } shapePath.close(); canvas.drawPath(shapePath, shapePaint); } void _drawShapeHelper(Canvas canvas, List points, double radius) { Path helpPath = Path() ..addOval(Rect.fromCenter( center: Offset.zero, width: radius * 2, height: radius * 2)); dashPainter.paint(canvas, helpPath, helpPaint); for (Offset point in points) { drawAnchor(canvas, point); } } final Paint pointPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1; void drawAnchor(Canvas canvas, Offset offset) { canvas.drawCircle(offset, 4, pointPaint..style = PaintingStyle.stroke); canvas.drawCircle(offset, 2, pointPaint..style = PaintingStyle.fill); } @override bool shouldRepaint(covariant ShapePainter oldDelegate) { return oldDelegate.n != n; } final Paint helpPaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.grey ..strokeWidth = 1; final DashPainter dashPainter = const DashPainter(span: 4, step: 4); Paint axisPaint = Paint()..color = Colors.black; void drawAxis(Canvas canvas, Size size) { axisPaint.style = PaintingStyle.stroke; Path helpPath = Path() ..moveTo(-size.width / 2, 0) ..relativeLineTo(size.width, 0) ..moveTo(0, -size.height / 2) ..relativeLineTo(0, size.height); dashPainter.paint(canvas, helpPath, axisPaint); axisPaint.style = PaintingStyle.fill; Path arrowXPath = Path(); arrowXPath.moveTo(size.width / 2, 0); arrowXPath.relativeLineTo(-8, 4); arrowXPath.relativeLineTo(0, -8); arrowXPath.close(); canvas.drawPath(arrowXPath, axisPaint); Path arrowYPath = Path(); arrowYPath.moveTo(0, size.height / 2); arrowYPath.relativeLineTo(4, -8); arrowYPath.relativeLineTo(-8, 0); arrowYPath.close(); canvas.drawPath(arrowYPath, axisPaint); drawHelpText("x", canvas, Offset(size.width / 2 - 8, 2)); drawHelpText("y", canvas, Offset(6, size.height / 2 - 15)); } final TextPainter textPainter = TextPainter( textAlign: TextAlign.center, textDirection: TextDirection.ltr, ); void drawHelpText( String text, Canvas canvas, Offset offset, { Color color = Colors.black, }) { textPainter.text = TextSpan( text: text, style: TextStyle(fontSize: 11, color: color,fontWeight: FontWeight.bold), ); textPainter.layout(maxWidth: 200); textPainter.paint(canvas, offset); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/polar/angle_painter.dart ================================================ import 'dart:math'; import 'package:dash_painter/dash_painter.dart'; import 'package:flutter/material.dart'; import 'polar.dart'; class PolarPainter extends CustomPainter { final DashPainter dashPainter = const DashPainter(span: 4, step: 4); final Paint helpPaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.grey ..strokeWidth = 1; Polar2D p1 = Polar2D(pi / 4, 75); final TextPainter textPainter = TextPainter( textAlign: TextAlign.center, textDirection: TextDirection.ltr, ); @override void paint(Canvas canvas, Size size) { Paint paint = Paint()..color=Colors.blue; canvas.translate(size.width / 2, size.height / 2); drawHelp(canvas, size); collect().forEach((Polar2D point) { canvas.drawCircle(point.offset, 1, paint); }); } List collect() { List points = []; for (int i = 0; i < 360; i++) { double rad = i * pi / 180; double length = 100 * cos(4 * rad); points.add(Polar2D(rad, length)); } return points; } void drawHelp(Canvas canvas, Size size) { Path helpPath = Path() ..moveTo(-size.width / 2, 0) ..relativeLineTo(size.width, 0) ..moveTo(0, -size.height / 2) ..relativeLineTo(0, size.height); for (int i = 0; i < 5; i++) { helpPath.addOval(Rect.fromCenter(center: Offset.zero, width: 50.0 * i, height: 50.0 * i)); drawHelpText('${25 * i}', canvas, Offset(25.0 * i - 10, 0)); } dashPainter.paint(canvas, helpPath, helpPaint); drawHelpText('90°', canvas, Offset(0 - 8, size.height*0.85 / 2), color: Colors.black); drawHelpText('180°', canvas, Offset(-size.width*0.8 / 2 - 30, 0 - 8), color: Colors.black); drawHelpText('270°', canvas, Offset(0 - 8, -size.height*0.85 / 2 - 16), color: Colors.black); drawHelpText('0°', canvas, Offset(size.width*0.85 / 2 + 10, 0 - 8), color: Colors.black); } void drawHelpText( String text, Canvas canvas, Offset offset, { Color color = Colors.black, }) { textPainter.text = TextSpan( text: text, style: TextStyle(fontSize: 11, color: color,fontWeight: FontWeight.bold), ); textPainter.layout(maxWidth: 200); textPainter.paint(canvas, offset); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/polar/polar.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; class Polar2D { final double rad; final double length; final Offset coo; Polar2D(this.rad, this.length,{this.coo=Offset.zero}); Offset get offset => Offset( cos(rad) * length-coo.dx, sin(rad) * length-coo.dy, ); } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/polar/polar_painter_widget.dart ================================================ import 'package:flutter/material.dart'; import 'angle_painter.dart'; class PolarPainterWidget extends StatelessWidget { const PolarPainterWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return CustomPaint( painter: PolarPainter(), size: const Size(200,200), ); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/base/windmill.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; class WindmillWidget extends StatefulWidget { const WindmillWidget({Key? key}) : super(key: key); @override State createState() => _WindmillWidgetState(); } class _WindmillWidgetState extends State with SingleTickerProviderStateMixin { late final AnimationController _ctrl = AnimationController( vsync: this, duration: Duration(seconds: (3 * 1.5).toInt()), ); late Animation rotate; @override void initState() { rotate = Tween(begin: 0, end: 3 * 2 * pi) .animate(CurveTween(curve: Curves.easeIn).animate(_ctrl)); super.initState(); } @override void dispose() { _ctrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: () async { await _ctrl.forward(from: 0); }, child: CustomPaint( size: const Size(200, 200), painter: WindmillPainter(rotate), ), ); } } const List kColors = [ Color(0xffE74437), Color(0xffFBBD19), Color(0xff3482F0), Color(0xff30A04C) ]; class WindmillPainter extends CustomPainter { final Animation rotate; WindmillPainter(this.rotate) : super(repaint: rotate); @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); double d = size.width * 0.4; canvas.rotate(rotate.value); Paint paint = Paint(); for (Color color in kColors) { Path path1 = Path() ..moveTo(0, -d * 46 / 203) ..lineTo(0, -d * 203 / 203) ..lineTo(102 / 203 * d, -102 / 203 * d) ..lineTo(12 / 203 * d, -12 / 203 * d) ..close(); canvas.drawPath(path1, paint..color = color); Path path2 = Path() ..moveTo(12 / 203 * d, -12 / 203 * d) ..lineTo(102 / 203 * d, -102 / 203 * d) ..lineTo(102 / 203 * d, 0) ..lineTo(46 / 203 * d, 0) ..close(); canvas.drawPath(path2, paint..color = color.withOpacity(0.2)); canvas.rotate(pi / 2); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } } ================================================ FILE: modules/painting_system/draw_system/lib/src/bloc/gallery_unit/bloc.dart ================================================ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class GalleryUnitBloc extends Cubit{ GalleryUnitBloc() : super(''); void loadGalleryInfo() async{ String result = await rootBundle.loadString('assets/data/gallery_info.json'); emit(result); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/desk_ui/desk_frame.dart ================================================ import 'package:flutter/material.dart'; import '../picture_frame.dart'; import 'package:l10n/l10n.dart'; /// create by 张风捷特烈 on 2020/12/4 /// contact me by email 1981462002@qq.com /// 说明: class DeskFrameShower extends StatelessWidget { final String title; final String author; final String srcUrl; final String info; final Widget content; const DeskFrameShower( {Key? key, this.title = "", this.author = "", this.srcUrl = "", this.info = "", required this.content}) : super(key: key); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( child: Align( alignment: const Alignment(0,-0.7), child: Wrap( direction: Axis.vertical, spacing: 5, children: [ Text( title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), Text( "作者: $author ", style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold), ), Text( "${context.l10n.srcPath} ", style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.blueAccent), ), ], ), ), ), PictureFrame( width: MediaQuery.of(context).size.shortestSide*0.6, height: MediaQuery.of(context).size.shortestSide*0.6, child: content, ), Expanded( child: Column( children: [ const Spacer(flex: 70,), Padding( padding: const EdgeInsets.all(20.0), child: Text( info, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold,color: Colors.grey), ), ), const Spacer(flex: 20,), ], ), ), ], ); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/desk_ui/desk_gallery_unit.dart ================================================ import 'dart:convert'; import 'package:app/app.dart'; import 'package:components/components.dart'; import 'package:flutter/material.dart'; import 'package:l10n/l10n.dart'; import 'package:toly_ui/toly_ui.dart'; import '../gallery_card_item.dart'; import '../gallery_detail_page.dart'; import '../gallery_factory.dart'; /// create by 张风捷特烈 on 2020/11/28 /// contact me by email 1981462002@qq.com /// 说明: class DeskGalleryUnit extends StatefulWidget { const DeskGalleryUnit({Key? key}) : super(key: key); @override _DeskGalleryUnitState createState() => _DeskGalleryUnitState(); } class _DeskGalleryUnitState extends State { final ValueNotifier factor = ValueNotifier(0); @override void dispose() { factor.dispose(); super.dispose(); } final ScrollController controller = ScrollController(); Color get color => Colors.blue; Color get nextColor =>Colors.orangeAccent; BoxDecoration get boxDecoration => const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), ), ); @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ SimpleDeskTopBar( leading: Text( 'Flutter ${context.l10n.paintCollection}', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), Expanded(child: _buildContent()), ], ), ); } Widget _buildContent() { final List widgets = (json.decode(StrUnit.galleryDesc(context)) as List).map((e) { GalleryInfo info = GalleryInfo.fromJson(e); List children = GalleryFactory.getGalleryByName(info.type,context); return FeedbackWidget( a: 0.95, onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (ctx) => GalleryDetailPage( galleryInfo: info, children: children, ))); }, child: GalleryCardItem( galleryInfo: info, count: children.length, ), ); }).toList(); SliverGridDelegate gridDelegate = const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 320, mainAxisSpacing: 8, mainAxisExtent: 340, crossAxisSpacing: 8, ); return GridView.builder( controller: controller, gridDelegate: gridDelegate, padding: const EdgeInsets.all(12), itemCount: widgets.length, itemBuilder: (ctx, index) => widgets[index]); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/fun/bufeng/bufeng_panel.dart ================================================ import 'package:flutter/material.dart'; import 'painter.dart'; import 'config.dart'; class BufengPanel extends StatefulWidget { const BufengPanel({Key? key}) : super(key: key); @override State createState() => _BufengPanelState(); } class _BufengPanelState extends State { final Config config = Config( size: const Size(200, 200), ); void addNeedle({int count = 1}) { for (int i = 0; i < count; i++) { config.addNeedle(); } } @override Widget build(BuildContext context) { return Stack( alignment: Alignment.topCenter, children: [ Padding( padding: const EdgeInsets.only(top: 10), child: CustomPaint( painter: PiPainter(config), size: config.size, ), ), Positioned( right: 0, top: 10, child: Row( children: [ IconButton(onPressed: addNeedle, icon: const Icon(Icons.add)), IconButton(onPressed: () => addNeedle(count: 100), icon: const Icon(Icons.add_chart)), IconButton(onPressed: config.clear, icon: const Icon(Icons.refresh)), ], ), ) ], ); // Scaffold( // appBar: AppBar( // title: const Text('蒲丰投针试验'), // actions: // [ // IconButton(onPressed: addNeedle, icon: const Icon(Icons.add)), // IconButton(onPressed: () => addNeedle(count: 100), icon: const Icon(Icons.add_chart)), // IconButton(onPressed: config.clear, icon: const Icon(Icons.refresh)), // ], // ), // body: Center( // child: , // ), // ); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/fun/bufeng/config.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; class Line { final Offset p0; final Offset p1; Line({ required this.p0, required this.p1, }); bool isActive(double gap) { int gapIndex0 = p0.dy ~/ gap; int gapIndex1 = p1.dy < 0 ? -1 : p1.dy ~/ gap; return gapIndex0 != gapIndex1 || p0.dy % gap < 0.000000001 || p1.dy % gap < 0.000000001; } } class Config extends ChangeNotifier { int lineCount; Size size; List lines = []; Config({ this.lineCount = 8, this.size = const Size(200, 200), }); double get gap => size.height / lineCount; @override String toString() { int n = lines.length; int m = lines.where((line) => line.isActive(gap)).length; double parserPi = m == 0 ? 0 : 2 * n / m; return '投针个数: ${lines.length}\n命中个数: $m \n' '估算圆周率 : $parserPi'; } void addNeedle() { Line line = _createNeedle(); lines.add(line); notifyListeners(); } final Random _random = Random(); Line _createNeedle() { double x = _random.nextDouble() * size.width; double y = _random.nextDouble() * size.height; double rad = 2 * pi * _random.nextDouble(); double dx = gap * cos(rad); double dy = gap * sin(rad); Line line = Line(p0: Offset(x, y), p1: Offset(x + dx, y + dy)); return line; } void clear() { lines.clear(); notifyListeners(); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/fun/bufeng/painter.dart ================================================ import 'package:flutter/material.dart'; import 'config.dart'; class PiPainter extends CustomPainter { final Config config; PiPainter(this.config) : super(repaint: config); @override void paint(Canvas canvas, Size size) { print(size); Paint paint = Paint(); paint.style = PaintingStyle.stroke; final double span = config.gap; drawHelpText( '$config', canvas, Offset.zero, ); canvas.translate(0, size.height*0.3); canvas.save(); for (int i = 0; i <= config.lineCount; i++) { canvas.drawLine(Offset.zero, Offset(size.width, 0), paint); print(span*i); canvas.translate(0, span); } canvas.restore(); Paint needlePaint = Paint(); needlePaint.style = PaintingStyle.stroke; needlePaint.strokeWidth = 1; for (Line line in config.lines) { if (line.isActive(config.gap)) { needlePaint.color = Colors.red; } else { needlePaint.color = Colors.green; } canvas.drawLine(line.p0, line.p1, needlePaint); } } @override bool shouldRepaint(covariant PiPainter oldDelegate) { return config != oldDelegate.config; } final TextPainter textPainter = TextPainter( textAlign: TextAlign.start, textDirection: TextDirection.ltr, ); void drawHelpText( String text, Canvas canvas, Offset offset, { Color color = Colors.lightBlue, }) { textPainter.text = TextSpan( text: text, style: TextStyle(fontSize: 14, color: color), ); textPainter.layout(maxWidth: 250); textPainter.paint(canvas, offset); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/fun/dundun_view.dart ================================================ import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class DunDunView extends StatefulWidget { const DunDunView({Key? key}) : super(key: key); @override _DunDunViewState createState() => _DunDunViewState(); } class _DunDunViewState extends State { ui.Image? image; @override void initState() { super.initState(); loadImage(); } void loadImage() async { ByteData data2 = await rootBundle.load('assets/images/logo1.webp'); image = await decodeImageFromList(data2.buffer.asUint8List()); if (mounted) setState(() {}); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: CustomPaint( painter: DunDunPainter( image, ), size: const Size(200, 200), ), ), ); } } class DunDunPainter extends CustomPainter { final ui.Image? image; DunDunPainter(this.image); final Paint helpPaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.blue; final Paint pathPaint = Paint()..style = PaintingStyle.stroke; @override void paint(Canvas canvas, Size size) { canvas.translate(30, 80); helpPaint.color = Colors.red; pathPaint.style = PaintingStyle.fill; Path leftHandPath = buildLeftHandPath(); pathPaint.color = Colors.black; canvas.drawPath(leftHandPath, pathPaint); Path erPath = buildErPath(); canvas.drawPath(erPath, pathPaint); Path rightHandPath = buildRightHandPath(); pathPaint.color = Colors.black; canvas.drawPath(rightHandPath, pathPaint); pathPaint.style = PaintingStyle.fill; pathPaint.color = const Color(0xffF1F4F7); Path bodyPath = buildBodyPath(); canvas.drawPath(bodyPath, pathPaint); canvas.save(); Path eyePath = Path(); Matrix4 m = Matrix4.translationValues(46, -12, 0) .multiplied(Matrix4.rotationZ(45 / 180 * pi)); eyePath .addOval(Rect.fromCenter(center: const Offset(0, 0), width: 32, height: 49)); eyePath = eyePath.transform(m.storage); pathPaint.color = Colors.black; canvas.drawPath(eyePath, pathPaint); canvas.restore(); Path nosePath = Path(); nosePath.moveTo(79, -0); nosePath.relativeLineTo(12, -12); nosePath.relativeLineTo(-24, 0); nosePath.close(); Path clipCirclePath = Path(); clipCirclePath.addOval( Rect.fromCenter(center: const Offset(79, -10), width: 12, height: 12)); nosePath = Path.combine(PathOperation.intersect, nosePath, clipCirclePath); pathPaint.style = PaintingStyle.fill; pathPaint.color = Colors.black; canvas.drawPath(nosePath, pathPaint); Path smaliPath = Path(); smaliPath.moveTo(65, -0); smaliPath.quadraticBezierTo(78, 15, 90, 0); smaliPath.quadraticBezierTo(78, 6, 65, 0); pathPaint.color = Colors.red; canvas.drawPath(smaliPath, pathPaint); canvas.drawPath( smaliPath, pathPaint ..style = PaintingStyle.stroke ..strokeWidth = 1 ..color = Colors.black); Paint colorfulPaint = Paint()..style = PaintingStyle.stroke; List colors = const [ Color(0xFFF60C0C), Color(0xFFF3B913), Color(0xFFE7F716), Color(0xFF3DF30B), Color(0xFF0DF6EF), Color(0xFF0829FB), Color(0xFFB709F4), ]; final List pos = List.generate(colors.length, (index) => index / colors.length); colorfulPaint.shader = ui.Gradient.sweep( const Offset(60, -5), colors, pos, TileMode.clamp, 0, 2 * pi); colorfulPaint.maskFilter = const MaskFilter.blur(BlurStyle.solid, 2); Path colorfulPath = Path(); colorfulPath.addOval( Rect.fromCenter(center: const Offset(72, -5), width: 120, height: 110)); colorfulPath.addOval( Rect.fromCenter(center: const Offset(72, -5), width: 110, height: 100)); colorfulPath.addOval( Rect.fromCenter(center: const Offset(72, -5), width: 115, height: 110)); colorfulPath.addOval( Rect.fromCenter(center: const Offset(72, -5), width: 120, height: 105)); colorfulPath.addOval( Rect.fromCenter(center: const Offset(72, -5), width: 115, height: 105)); colorfulPath.addOval( Rect.fromCenter(center: const Offset(72, -5), width: 117, height: 103)); canvas.drawPath(colorfulPath, colorfulPaint); canvas.save(); Path eyePath2 = Path(); Matrix4 m2 = Matrix4.translationValues(105, -12, 0) .multiplied(Matrix4.rotationZ(-40 / 180 * pi)); eyePath2 .addOval(Rect.fromCenter(center: const Offset(0, 0), width: 29, height: 48)); eyePath2 = eyePath2.transform(m2.storage); pathPaint.style = PaintingStyle.fill; pathPaint.color = Colors.black; canvas.drawPath(eyePath2, pathPaint); canvas.restore(); Path rightEyePath = Path(); rightEyePath.addOval( Rect.fromCenter(center: const Offset(98, -14), width: 17, height: 17)); pathPaint.style = PaintingStyle.stroke; pathPaint.color = Colors.white; canvas.drawPath(rightEyePath, pathPaint..strokeWidth = 2); Path rightEyePath2 = Path(); rightEyePath2 .addOval(Rect.fromCenter(center: const Offset(98, -14), width: 7, height: 7)); pathPaint.style = PaintingStyle.fill; pathPaint.color = Colors.white.withOpacity(0.4); canvas.drawPath(rightEyePath2, pathPaint); Path rightEyePath3 = Path(); rightEyePath3 .addOval(Rect.fromCenter(center: const Offset(98, -19), width: 4, height: 4)); pathPaint.style = PaintingStyle.fill; pathPaint.color = Colors.white; canvas.drawPath(rightEyePath3, pathPaint); Path leftEyePath = Path(); leftEyePath.addOval( Rect.fromCenter(center: const Offset(50, -13), width: 18, height: 18)); pathPaint.style = PaintingStyle.stroke; pathPaint.color = Colors.white; canvas.drawPath(leftEyePath, pathPaint..strokeWidth = 2); Path leftEyePath2 = Path(); leftEyePath2 .addOval(Rect.fromCenter(center: const Offset(50, -13), width: 7, height: 7)); pathPaint.style = PaintingStyle.fill; pathPaint.color = Colors.white.withOpacity(0.4); canvas.drawPath(leftEyePath2, pathPaint); Path leftEyePath3 = Path(); leftEyePath3 .addOval(Rect.fromCenter(center: const Offset(51, -19), width: 4, height: 4)); pathPaint.style = PaintingStyle.fill; pathPaint.color = Colors.white; canvas.drawPath(leftEyePath3, pathPaint); Path leftFootPath = buildFootPath(); pathPaint.style = PaintingStyle.fill; pathPaint.color = Colors.black; canvas.drawPath(leftFootPath, pathPaint); //爱心 List arr = []; int len = 50; double a = 1; for (int i = 0; i < len; i++) { double step = (i / len) * (pi * 2); //递增的θ Offset offset = Offset( a * (11 * pow(sin(step), 3)).toDouble(), -a * (9 * cos(step) - 5 * cos(2 * step) - 2 * cos(3 * step) - cos(4 * step)), ); arr.add(offset); } Path starPath = Path(); for (int i = 0; i < len; i++) { starPath.lineTo(arr[i].dx, arr[i].dy); } pathPaint.color = Colors.red; starPath = starPath.shift(const Offset(152, -20)); canvas.drawPath(starPath, pathPaint); if (image != null) { Rect src2 = Rect.fromLTWH( 0, 0, image!.width.toDouble(), image!.height.toDouble()); Rect dst2 = const Rect.fromLTWH(50, 55, 899 / 28, 1066 / 28); canvas.drawImageRect(image!, src2, dst2, Paint()); } Path dundunOutLine = Path.combine( PathOperation.union, Path.combine( PathOperation.union, Path.combine( PathOperation.union, Path.combine(PathOperation.union, bodyPath, leftFootPath), rightHandPath), leftHandPath), erPath); Paint outLinePainter = Paint() ..style = PaintingStyle.stroke ..color = Colors.black ..strokeWidth = 3; outLinePainter.maskFilter = const MaskFilter.blur(BlurStyle.outer, 4); canvas.drawPath(dundunOutLine, outLinePainter); Path p2 = Path() ..addOval(Rect.fromCenter( center: const Offset( 72, -5, ), width: 126, height: 116)); outLinePainter.maskFilter = const MaskFilter.blur(BlurStyle.outer, 4); canvas.drawPath( p2, outLinePainter ..color = Colors.black ..strokeWidth = 2); } Path buildBodyPath() { Path path = Path(); path.quadraticBezierTo(10, -75, 75, -75); path.quadraticBezierTo(135, -70, 138, 0); path.quadraticBezierTo(130, 90, 65, 98); path.quadraticBezierTo(-5, 85, 0, 0); return path; } Path buildLeftHandPath() { Path path = Path(); path.quadraticBezierTo( -17, 14, -28, 40, ); path.quadraticBezierTo(-32, 58, -15, 62); path.quadraticBezierTo(10, 60, 0, 0); return path; } Path buildRightHandPath() { Path path = Path(); path.moveTo(135, -20); path.quadraticBezierTo(140, -48, 165, -35); path.quadraticBezierTo(180, -17, 135, 22); path.quadraticBezierTo(125, 17, 135, -20); return path; } Path buildFootPath() { Path path = Path(); path.moveTo(18, 78); path.quadraticBezierTo(18, 100, 22, 115); path.quadraticBezierTo(60, 125, 55, 98); path.quadraticBezierTo(35, 80, 18, 78); Path right = path .transform(Matrix4.diagonal3Values(-1, 1, 1).storage) .shift(const Offset(128, 0)); return Path.combine(PathOperation.union, path, right); } Path buildErPath() { Path path = Path(); path.moveTo(13, -40); path.quadraticBezierTo(8, -95, 40, -68); path.quadraticBezierTo(40, -55, 13, -40); Path right = path .transform(Matrix4.diagonal3Values(-1, 1, 1).storage) .shift(const Offset(138, -5)); return Path.combine(PathOperation.union, path, right); } void paintBodyPoints(ui.Canvas canvas) { helpPaint.strokeWidth = 4; canvas.drawPoints( ui.PointMode.points, [ const Offset(10, -68), const Offset(75, -75), const Offset(135, -70), const Offset(138, 0), const Offset(130, 90), const Offset(65, 98), // Offset(55,98), // Offset(18,78), const Offset(-5, 85), const Offset(0, 0), ], helpPaint); } void paintErPoints(ui.Canvas canvas) { helpPaint.strokeWidth = 4; canvas.drawPoints( ui.PointMode.points, [ const Offset( 13, -40, ), const Offset( 40, -68, ), const Offset(40, -55), const Offset( 8, -95, ), // Offset(18, 78), // Offset(22, 115), // Offset(55, 98), // Offset( // 40, // 80, // ), ], helpPaint); } void paintHandsHelpPoints(ui.Canvas canvas) { helpPaint.strokeWidth = 4; canvas.drawPoints( ui.PointMode.points, [ const Offset(-0, 0), const Offset(-17, 14), const Offset(-28, 40), const Offset(-32, 58), const Offset(-15, 62), const Offset( 8, 60, ), const Offset(-0, 0), ], helpPaint); } void paintRightHandsHelpPoints(ui.Canvas canvas) { helpPaint.strokeWidth = 4; canvas.drawPoints( ui.PointMode.points, [ // Offset(10,-68), // Offset(75,-75), // const Offset(140, -48), const Offset(165, -35), const Offset(180, -17), const Offset(135, 22), const Offset(125, 17), const Offset(135, -20), // Offset(55,98), // Offset(18,78), ], helpPaint); } void paintLeftFoodHelpPoints(ui.Canvas canvas) { helpPaint.strokeWidth = 4; canvas.drawPoints( ui.PointMode.points, [ const Offset( 18, 100, ), const Offset(60, 125), const Offset(18, 78), const Offset(22, 115), const Offset(55, 98), const Offset( 40, 80, ), ], helpPaint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } ================================================ FILE: modules/painting_system/draw_system/lib/src/fun/random_portrait.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/10/10 /// contact me by email 1981462002@qq.com /// 说明: class Position { final int x; final int y; Position(this.x, this.y); @override String toString() { return 'Position{x: $x, y: $y}'; } } class RandomPortrait extends StatefulWidget { const RandomPortrait({Key? key}) : super(key: key); @override _RandomPortraitState createState() => _RandomPortraitState(); } class _RandomPortraitState extends State { List positions = []; Random random = Random(); final int blockCount = 9; @override Widget build(BuildContext context) { _initPosition(); return GestureDetector( onTap: () { setState(() {}); }, child: CustomPaint( painter: PortraitPainter(positions, blockCount: blockCount))); } void _initPosition() { positions.clear(); int randomCount = 2 + random.nextInt(blockCount * blockCount ~/ 2 - 2); final int axis = blockCount ~/ 2; for (int i = 0; i < randomCount; i++) { int randomX = random.nextInt(axis + 1); int randomY = random.nextInt(blockCount); final Position position = Position(randomX, randomY); positions.add(position); } for (int i = 0; i < positions.length; i++) { if (positions[i].x < blockCount ~/ 2) { positions.add(Position(2 * axis - positions[i].x, positions[i].y)); } } } } class PortraitPainter extends CustomPainter { final Paint _paint; final int blockCount; final Color color; final List positions; final pd = 20.0; PortraitPainter(this.positions, {this.blockCount = 9, this.color = Colors.blue}) : _paint = Paint()..color = color; @override void paint(Canvas canvas, Size size) { canvas.clipRect( Rect.fromPoints(Offset.zero, Offset(size.width, size.height))); final double perW = (size.width - pd * 2) / (blockCount); final double perH = (size.height - pd * 2) / (blockCount); canvas.translate(pd, pd); for (Position element in positions) { _drawBlock(perW, perH, canvas, element); } } void _drawBlock(double dW, double dH, Canvas canvas, Position position) { canvas.drawRect( Rect.fromLTWH(position.x * dW.floor() * 1.0, position.y * dH.floor() * 1.0, dW.floor() * 1.0, dH.floor() * 1.0), _paint); } @override bool shouldRepaint(PortraitPainter oldDelegate) => true; } ================================================ FILE: modules/painting_system/draw_system/lib/src/fun/stemp/stamp_data.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; class StampData extends ChangeNotifier { final List stamps = []; void push(Stamp stamp) { stamps.add(stamp); notifyListeners(); } void removeLast() { stamps.removeLast(); notifyListeners(); } void activeLast({Color color = Colors.blue}) { stamps.last.color = color; notifyListeners(); } void clear() { stamps.clear(); notifyListeners(); } void animateAt(int index, double radius) { stamps[index].radius = radius; stamps[index].rePath(); notifyListeners(); } GameState checkWin(double length){ bool redWin = _checkWinByColor(length,Colors.red); if(redWin) return GameState.redWin; bool blueWin = _checkWinByColor(length,Colors.blue); if(blueWin) return GameState.blueWin; return GameState.doing; } bool _checkWinByColor(double length,Color color) { List red = stamps .where((element) => element.color == color) .map((e) => e.center) .toList(); List> redPoints = red .map>((e) => Point(e.dx ~/ length, e.dy ~/ length)) .toList(); return _checkWinInline(redPoints, 3); } bool _checkWinInline(List> points, int max) { if (points.length < max) return false; for (int i = 0; i < points.length; i++) { int x = points[i].x; int y = points[i].y; if (_check(x, y, points, CheckModel.horizontal,max)) { return true; } else if (_check(x, y, points, CheckModel.vertical,max)) { return true; } else if (_check(x, y, points, CheckModel.leftDiagonal,max)) { return true; } else if (_check(x, y, points, CheckModel.rightDiagonal,max)) { return true; } } return false; } bool _check(int x, int y, List points, CheckModel checkModel,int max) { int count = 1; Point checkPoint; for (int i = 1; i < max; i++) { switch (checkModel) { case CheckModel.horizontal: checkPoint = Point(x - i, y); break; case CheckModel.vertical: checkPoint = Point(x, y - i); break; case CheckModel.leftDiagonal: checkPoint = Point(x - i, y + i);break; case CheckModel.rightDiagonal: checkPoint = Point(x + i, y + i); break; } if (points.contains(checkPoint)) {count++;} else {break;} } if (count == max) return true; return false; } } enum CheckModel { horizontal, // 横向判断 vertical, // 竖向判断 leftDiagonal, // 左斜判断 rightDiagonal // 右斜判断 } enum GameState{ doing, // 进行中 redWin, // 红胜 blueWin // 蓝胜 } class Stamp { Color color; Offset center; double radius; Stamp({this.color = Colors.blue, this.center = Offset.zero, this.radius = 20}); Path? _path; Path get path { if (_path == null) { _path = Path(); double r = radius; double rad = 30 / 180 * pi; _path!.moveTo(center.dx, center.dy); _path!.relativeMoveTo(r * cos(rad), -r * sin(rad)); _path!.relativeLineTo(-2 * r * cos(rad), 0); _path!.relativeLineTo(r * cos(rad), r + r * sin(rad)); _path!.relativeLineTo(r * cos(rad), -(r + r * sin(rad))); _path!.moveTo(center.dx, center.dy); _path!.relativeMoveTo(0, -r); _path!.relativeLineTo(-r * cos(rad), r + r * sin(rad)); _path!.relativeLineTo(2 * r * cos(rad), 0); _path!.relativeLineTo(-r * cos(rad), -(r + r * sin(rad))); return _path!; } else { return _path!; } } set path(Path path) { _path = path; } void rePath() { _path = null; _path = path; } } ================================================ FILE: modules/painting_system/draw_system/lib/src/fun/stemp/stamp_paper.dart ================================================ import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'stamp_data.dart'; class StampPaper extends StatefulWidget { const StampPaper({Key? key}) : super(key: key); @override _StampPaperState createState() => _StampPaperState(); } class _StampPaperState extends State with SingleTickerProviderStateMixin { final StampData stamps = StampData(); int gridCount = 3; double radius = 0; double width = 0; GameState gameState = GameState.doing; bool get gameOver => gameState != GameState.doing; // 定义动画器 late AnimationController _controller; final Duration animDuration = const Duration(milliseconds: 200); @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: animDuration) ..addListener(_listenAnim); } void _listenAnim() { if (_controller.value == 1.0) { _controller.reverse(); } double rate = (0.9 - 1) * _controller.value + 1; stamps.animateAt(containsIndex, rate * radius); } @override Widget build(BuildContext context) { width = MediaQuery.of(context).size.shortestSide * 0.5; return GestureDetector( onTapDown: _onTapDown, onTapUp: _onTapUp, onDoubleTap: _clear, onTapCancel: _removeLast, child: CustomPaint( foregroundPainter: StampPainter(stamps: stamps, count: gridCount), painter: BackGroundPainter(count: gridCount), size: Size(width, width), ), ); } bool get contains => containsIndex != -1; void _onTapDown(TapDownDetails details) { if (gameOver) return; containsIndex = checkZone(details.localPosition); if (contains) { _controller.forward(); return; } radius = width / 2 / gridCount * 0.618; stamps.push(Stamp( radius: radius, center: details.localPosition, color: Colors.grey)); } int containsIndex = -1; int checkZone(Offset src) { for (int i = 0; i < stamps.stamps.length; i++) { Rect zone = Rect.fromCircle( center: stamps.stamps[i].center, radius: width / gridCount / 2); if (zone.contains(src)) { return i; } } return -1; } void _onTapUp(TapUpDetails details) { if (contains || gameOver) return; stamps.activeLast( color: stamps.stamps.length % 2 == 0 ? Colors.red : Colors.blue); gameState = stamps.checkWin(width / gridCount); if (gameState == GameState.redWin) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text("红棋获胜!"), backgroundColor: Colors.red, )); } if (gameState == GameState.blueWin) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("蓝棋获胜!"), backgroundColor: Colors.blue)); } } void _clear() { stamps.clear(); gameState=GameState.doing; } void _removeLast() { if (contains || gameOver) return; stamps.removeLast(); } @override void dispose() { stamps.dispose(); super.dispose(); } } class BackGroundPainter extends CustomPainter { BackGroundPainter({this.count = 3}); final int count; final Paint _pathPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1; static const List colors = [ Color(0xFFF60C0C), Color(0xFFF3B913), Color(0xFFE7F716), Color(0xFF3DF30B), Color(0xFF0DF6EF), Color(0xFF0829FB), Color(0xFFB709F4), ]; static const List pos = [ 1.0 / 7, 2.0 / 7, 3.0 / 7, 4.0 / 7, 5.0 / 7, 6.0 / 7, 1.0 ]; @override void paint(Canvas canvas, Size size) { Rect zone = Offset.zero & size; canvas.clipRect(zone); _pathPaint.shader = ui.Gradient.sweep( Offset(size.width / 2, size.height / 2), colors, pos, TileMode.mirror, pi / 2, pi); canvas.save(); for (int i = 0; i < count - 1; i++) { canvas.translate(0, size.height / count); canvas.drawLine(Offset.zero, Offset(size.width, 0), _pathPaint); } canvas.restore(); canvas.save(); for (int i = 0; i < count - 1; i++) { canvas.translate(size.width / count, 0); canvas.drawLine(Offset.zero, Offset(0, size.height), _pathPaint); } canvas.restore(); } @override bool shouldRepaint(covariant BackGroundPainter oldDelegate) { return count != oldDelegate.count; } } class StampPainter extends CustomPainter { final StampData stamps; final int count; final Paint _paint = Paint(); final Paint _pathPaint = Paint() ..color = Colors.white ..style = PaintingStyle.stroke; StampPainter({required this.stamps, this.count = 3}) : super(repaint: stamps); @override void paint(Canvas canvas, Size size) { Rect zone = Offset.zero & size; canvas.clipRect(zone); for (Stamp stamp in stamps.stamps) { double length = size.width / count; int x = stamp.center.dx ~/ (size.width / count); int y = stamp.center.dy ~/ (size.width / count); double strokeWidth = stamp.radius * 0.07; Offset center = Offset(length * x + length / 2, length * y + length / 2); stamp.center = center; canvas.drawCircle( stamp.center, stamp.radius, _paint..color = stamp.color); canvas.drawPath( stamp.path, _pathPaint ..strokeWidth = strokeWidth ..color = Colors.white); canvas.drawCircle(stamp.center, stamp.radius + strokeWidth * 1.5, _pathPaint..color = stamp.color); } } @override bool shouldRepaint(covariant StampPainter oldDelegate) { return stamps != oldDelegate.stamps || count != oldDelegate.count; } } ================================================ FILE: modules/painting_system/draw_system/lib/src/gallery_card_item.dart ================================================ import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:l10n/l10n.dart'; import 'gallery_factory.dart'; /// create by 张风捷特烈 on 2020/11/28 /// contact me by email 1981462002@qq.com /// 说明: class GalleryCardItem extends StatelessWidget { final GalleryInfo galleryInfo; final int count; const GalleryCardItem({ Key? key, required this.galleryInfo, this.count = 0, }) : super(key: key); @override Widget build(BuildContext context) { ThemeData themeData = Theme.of(context); bool isDark = themeData.brightness == Brightness.dark; return Card( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20))), child: Container( height: double.maxFinite, alignment: Alignment.topCenter, child: Column( children: [ Hero( tag: galleryInfo.name, child: Container( height: 150, decoration: BoxDecoration( image: DecorationImage( fit: BoxFit.cover, image: AssetImage(galleryInfo.image)), borderRadius: const BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), )) // color: Colors.green, ), ), Padding( padding: const EdgeInsets.only( top: 8, left: 15, right: 15, bottom: 8), child: Row( children: [ Text( galleryInfo.name, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, shadows: [ Shadow( color: Theme.of(context).primaryColor, offset: const Offset(.2, .2), blurRadius: .5) ]), ), const Spacer(), WrapColor( radius: 6, padding: const EdgeInsets.only(left: 6, right: 6, top: 2, bottom: 3), child: Text( "$count ${context.l10n.picture}", style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.white, fontSize: 12), ), ), ], ), ), Padding( padding: const EdgeInsets.only(left: 15, right: 15, top: 6), child: Text( galleryInfo.info, maxLines: 7, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 12, color: isDark?Colors.white:Colors.grey, shadows: [ Shadow( color: Theme.of(context).primaryColor, offset: const Offset(.2, .2), blurRadius: .5) ]), ), ) ], ), decoration: BoxDecoration( color: isDark?Colors.black87:Colors.white, borderRadius: BorderRadius.all( Radius.circular(20), ))), ); } } const Map galleryTypeMap = { GalleryType.base: "基础绘制", GalleryType.fun: "趣味绘制", GalleryType.particle: "粒子绘制", GalleryType.anim: "动画手势", GalleryType.art: "艺术画廊", }; class GalleryInfo { final int count; final String name; final String info; final String image; final String router; GalleryType get type { GalleryType galleryType = GalleryType.base; galleryTypeMap.forEach((key, value) { if (value == name) { galleryType = key; } }); return galleryType; } const GalleryInfo( {this.count = 0,required this.name,required this.info,required this.image,required this.router}); factory GalleryInfo.fromJson(Map map) { return GalleryInfo( count: map['count'] ?? 0, name: map["name"] ?? "", image: map["image"] ?? "assets/images/draw_bg4.webp", router: map["router"] ?? "", info: map["info"] ?? ""); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/gallery_detail_page.dart ================================================ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:toly_ui/toly_ui.dart'; import 'gallery_card_item.dart'; /// create by 张风捷特烈 on 2020/12/4 /// contact me by email 1981462002@qq.com /// 说明: class GalleryDetailPage extends StatefulWidget { final GalleryInfo galleryInfo; final List children; const GalleryDetailPage({Key? key,required this.galleryInfo, this.children = const []}) : super(key: key); @override _GalleryDetailPageState createState() => _GalleryDetailPageState(); } class _GalleryDetailPageState extends State { late PageController _ctrl; final ValueNotifier _currentIndex = ValueNotifier(0); @override void initState() { super.initState(); _ctrl = PageController(); } @override void dispose() { _ctrl.dispose(); _currentIndex.dispose(); super.dispose(); } // 按钮颜色 Color get btnColor => Theme.of(context).primaryColor.withOpacity(0.6); bool get isFirst => _currentIndex.value == 0; bool get isEnd => _currentIndex.value == widget.children.length - 1; // 顶部 bar 圆角装饰 BoxDecoration get topBoxDecoration => BoxDecoration( image: DecorationImage( fit: BoxFit.cover, image: AssetImage(widget.galleryInfo.image)), borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(10), bottomRight: Radius.circular(10), )); // 上一页按钮外层包裹装饰 BoxDecoration get prevWrapDecoration => BoxDecoration( color: Colors.white.withOpacity(0.3), borderRadius: const BorderRadius.only( topRight: Radius.circular(20), bottomRight: Radius.circular(20), )); // 下一页按钮外层包裹装饰 BoxDecoration get nextWrapDecoration => BoxDecoration( color: Colors.white.withOpacity(0.3), borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(20), topLeft: Radius.circular(20), )); @override Widget build(BuildContext context) { return Scaffold( body: AnnotatedRegion( value:const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light ), child: Column( children: [ Expanded( flex: 20, child: buildTopBar(context), ), Expanded( flex: 80, child: PageView( controller: _ctrl, children: widget.children, onPageChanged: _onPageChanged, )) ], ), ), ); } void _onPageChanged(int index) { _currentIndex.value = index; } Widget buildTopBar(BuildContext context) { return Hero( tag: widget.galleryInfo.name, child: Material( color: Colors.transparent, child: Container( alignment: Alignment.topLeft, decoration: topBoxDecoration, child: Column( children: [ buildTitle(context), Expanded( child: Row( children: [_buildPrevBtn(), const Spacer(), _buildNextBtn()], ), ) ], ), ), ), ); } Widget _buildPrevBtn() { return Container( width: 80, height: 40, alignment: Alignment.centerRight, decoration: prevWrapDecoration, padding: const EdgeInsets.only(right: 5 / 2), child: FeedbackWidget( onPressed: _switchPrevPage, child: Container( width: 35, height: 35, decoration: BoxDecoration(color: btnColor, shape: BoxShape.circle), child: const Icon(Icons.navigate_before, color: Colors.white), ), // height: 60, ), ); } Widget _buildNextBtn() { return Container( width: 80, height: 40, alignment: Alignment.centerLeft, padding: const EdgeInsets.only(left: 5 / 2), decoration: nextWrapDecoration, child: FeedbackWidget( onPressed: _switchNextPage, child: Container( width: 35, height: 35, decoration: BoxDecoration(color: btnColor, shape: BoxShape.circle), child: const Icon(Icons.navigate_next, color: Colors.white), // height: 60, ))); } // 跳转上一页 void _switchPrevPage() { if (widget.children.isNotEmpty) { int page = (_currentIndex.value - 1) % widget.children.length; _ctrl.animateToPage(page, duration: const Duration(milliseconds: 500), curve: Curves.linear); } } // 跳转下一页 void _switchNextPage() { if (widget.children.isNotEmpty) { int page = (_currentIndex.value + 1) % widget.children.length; _ctrl.animateToPage(page, duration: const Duration(milliseconds: 500), curve: Curves.linear); } } bool isDesk = kIsWeb || Platform.isMacOS || Platform.isWindows || Platform.isLinux; Widget buildTitle(BuildContext context) { return Container( padding: EdgeInsets.only(top: isDesk?26:46, bottom: 10, left: isDesk?20:0, right: 10), child: Row( children: [ // if(isDesk) const BackButton(color: Colors.white,), Text( widget.galleryInfo.name, style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white, shadows: [ Shadow( color: Theme.of(context).primaryColor, offset: const Offset(.2, .2), blurRadius: .5) ]), ), const Spacer(), ValueListenableBuilder( valueListenable: _currentIndex, builder: _buildIndicatorText, ) ], ), ); } Widget _buildIndicatorText(BuildContext context, int value, Widget? child) { String indicatorText = "${widget.children.isNotEmpty ? (value + 1) : 0} / ${widget.children.length}"; return Text( indicatorText, style: const TextStyle(shadows: [ Shadow(color: Colors.black, offset: Offset(.2, .2), blurRadius: .5) ], fontWeight: FontWeight.bold, color: Colors.white, fontSize: 20), ); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/gallery_factory.dart ================================================ import 'package:flutter/material.dart'; import 'anim/bezier3_player/bezier3_palyer.dart'; import 'anim/circle_halo.dart'; import 'anim/curve_shower/curve_anim_shower.dart'; import 'anim/draw_path.dart'; import 'anim/dundun_path.dart'; import 'anim/rotate_by_point/rotate_by_point.dart'; import 'anim/spring_widget.dart'; import 'art/circle_packing.dart'; import 'art/cubic_disarray.dart'; import 'art/hypnotic_squares.dart'; import 'art/joy_division.dart'; import 'art/piet_mondrian.dart'; import 'art/tiled_lines.dart'; import 'art/triangular_mesh.dart'; import 'art/un_deux_trois.dart'; import 'base/clock_widget.dart'; import 'base/digital/digital_shower.dart'; import 'base/draw_grid_axis.dart'; import 'base/draw_path_fun.dart'; import 'base/draw_picture.dart'; import 'base/n_side/n_side_page.dart'; import 'base/polar/polar_painter_widget.dart'; import 'base/windmill.dart'; import 'fun/bufeng/bufeng_panel.dart'; import 'fun/dundun_view.dart'; import 'fun/random_portrait.dart'; import 'fun/stemp/stamp_paper.dart'; import 'particle/random/random_particle.dart'; import 'particle/split/particle_split.dart'; import 'particle/split_img/split_image.dart'; import 'picture_frame.dart'; import 'package:l10n/l10n.dart'; /// create by 张风捷特烈 on 2020/12/5 /// contact me by email 1981462002@qq.com /// 说明: /// enum GalleryType { base, anim, particle, fun, art } class GalleryFactory { static List getGalleryByName(GalleryType type,BuildContext context) { switch (type) { case GalleryType.base: return [ FrameShower( title: "The Chaos", author: "张风捷特烈", srcUrl: "/base/draw_picture.dart", info: context.l10n.drawingOfImages, content: DrawPicture()), FrameShower( title: "数字显示管", author: "张风捷特烈", srcUrl: "/base/digital", info: context.l10n.digitalDisplayTube, content: DigitalShower()), FrameShower( title: "旋转风车", author: "张风捷特烈", srcUrl: "/base/windmill.dart", info: context.l10n.pathDrawing, content: WindmillWidget()), FrameShower( title: "平面直角坐标系", author: "张风捷特烈", srcUrl: "/base/draw_grid_axis.dart", info: context.l10n.gridCoordinateSystem, content: DrawGridAxis()), FrameShower( title: "平面极坐标系", author: "张风捷特烈", srcUrl: "/base/polar", info: context.l10n.polarCoordinateSystemOfFaces, content: PolarPainterWidget()), FrameShower( title: "曲线拟合", author: "张风捷特烈", srcUrl: "/base/draw_path_fun.dart", info: context.l10n.drawFunctionCurvesForPathPairs, content: DrawPathFun()), FrameShower( title: "圆中取形", author: "张风捷特烈", srcUrl: "/base/n_side", info: context.l10n.drawRegularPolygons, content: NSidePage()), FrameShower( title: "随机对称图", author: "张风捷特烈", srcUrl: '/fun/random_portrait.dart', info: context.l10n.randomNumberProcessing, content: RandomPortrait()), FrameShower( title: "简单时钟", author: "张风捷特烈", srcUrl: '/base/clock_widget.dart', info: context.l10n.clockDrawing, content: ClockWidget()), ]; case GalleryType.anim: return [ FrameShower( title: "手势弹簧", author: "张风捷特烈", srcUrl: '/anim/spring_widget.dart', info: context.l10n.drawSprings, content: SpringWidget()), FrameShower( title: "绕定点旋转", author: "张风捷特烈", srcUrl: '/anim/rotate_by_point', info: context.l10n.theApplicationOfAnglesInDrawing, content: RotateByPointWidget()), FrameShower( title: "流光", author: "张风捷特烈", srcUrl: '/anim/circle_halo.dart', info: context.l10n.usingShadersAndFilters, content: CircleHalo()), FrameShower( title: "曲线路径动画", author: "张风捷特烈", srcUrl: '/anim/draw_path.dart', info: context.l10n.pathDrawingFunctionCurve, content: DrawPath()), FrameShower( title: "冰墩墩线条动画", author: "张风捷特烈", srcUrl: '/anim/dundun_path.dart', info: context.l10n.thePathOfBingDwenDwen, content: DunDunPathPage()), FrameShower( title: "Bezier3 演示", author: "张风捷特烈", srcUrl: '/anim/bezier3_player', info: context.l10n.drawCubicBesselCurve, content: Bezier3Player()), FrameShower( title: "动画曲线散点图", author: "张风捷特烈", srcUrl: '/anim/curve_shower', info: context.l10n.theEffectOfAnimationCurve, content: CurveAnimShower()), ]; case GalleryType.particle: return [ FrameShower( title: "随机粒子生成器", author: "张风捷特烈", srcUrl: '/particle/random', info: context.l10n.randomParticlesAndBoundaryBouncing, content: RandomParticle()), FrameShower( title: "粒子分裂", author: "张风捷特烈", srcUrl: '/particle/split', info: context.l10n.particleCollision, content: ParticleSplit()), FrameShower( title: "图片粒子分裂", author: "张风捷特烈", srcUrl: '/particle/split_img', info: context.l10n.particle, content: SplitImage()), ]; case GalleryType.fun: return [ FrameShower( title: "Random Portrait", author: "张风捷特烈", srcUrl: '/fun/random_portrait.dart', info: context.l10n.rectangleAndRandomNumbers, content: RandomPortrait()), FrameShower( title: "冰墩墩", author: "张风捷特烈", srcUrl: '/fun/dundun_view.dart', info: context.l10n.bingDwenDwen, content: DunDunView()), FrameShower( title: "蒲丰投针试验", author: "张风捷特烈", srcUrl: '/fun/bufeng', info: context.l10n.pufengInjectionTest, content: BufengPanel()), FrameShower( title: "井字棋", author: "张风捷特烈", srcUrl: '/fun/stemp', info: context.l10n.ticTacToe, content: StampPaper()), ]; case GalleryType.art: return [ FrameShower( title: "Tiled Line", author: "generativeartistry.com", srcUrl: '/art/tiled_lines.dart', info: context.l10n.tiledLines, content: TiledLines(), ), FrameShower( title: "Joy Division", author: "generativeartistry.com", srcUrl: '/art/joy_division.dart', info: context.l10n.joyDivision, content: JoyDivision(), ), FrameShower( title: "Cubic Disarray", author: "generativeartistry.com", srcUrl: '/art/cubic_disarray.dart', info: context.l10n.cubicDisarray, content: CubicDisarray(), ), FrameShower( title: "Triangular Mesh", author: "generativeartistry.com", srcUrl: '/art/triangular_mesh.dart', info: context.l10n.triangularMesh, content: TriangularMesh(), ), FrameShower( title: "Un Deux Trois", srcUrl: '/art/un_deux_trois.dart', author: "generativeartistry.com", info: context.l10n.unDeuxTrois, content: UnDeuxTrois(), ), FrameShower( title: "Circle Packing", author: "generativeartistry.com", srcUrl: '/art/circle_packing.dart', info: context.l10n.circlePacking, content: CirclePacking(), ), FrameShower( title: "Hypnotic Squares", author: "generativeartistry.com", srcUrl: '/art/hypnotic_squares.dart', info: context.l10n.hypnoticSquares, content: HypnoticSquares(), ), FrameShower( title: "Piet Mondrian", author: "generativeartistry.com", srcUrl: '/art/piet_mondrian.dart', info: context.l10n.pietMondrian, content: PietMondrian(), ) ]; default: return []; } } } ================================================ FILE: modules/painting_system/draw_system/lib/src/gallery_unit.dart ================================================ import 'dart:convert'; import 'package:app/app.dart'; import 'package:components/project_ui/project_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:l10n/l10n.dart'; import 'desk_ui/desk_gallery_unit.dart'; import 'gallery_card_item.dart'; import 'gallery_detail_page.dart'; import 'gallery_factory.dart'; /// create by 张风捷特烈 on 2020/11/28 /// contact me by email 1981462002@qq.com /// 说明: class GalleryUnit extends StatelessWidget { const GalleryUnit({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return LayoutBuilder(builder: (_,c){ if(c.maxWidth>500){ return const DeskGalleryUnit(); } return const PhoneGalleryUnit(); }); } } class PhoneGalleryUnit extends StatefulWidget { const PhoneGalleryUnit({Key? key}) : super(key: key); @override _PhoneGalleryUnitState createState() => _PhoneGalleryUnitState(); } class _PhoneGalleryUnitState extends State { final ValueNotifier factor = ValueNotifier(0); late PageController _ctrl; final int _firstOffset = 1000; //初始偏移 int _position = 0; //页面位置 @override void initState() { super.initState(); _position = _position + _firstOffset; double value = ((_position - _firstOffset + 1) % 5) / 5; factor.value = value == 0 ? 1 : value; _ctrl = PageController( viewportFraction: 0.9, initialPage: _position, )..addListener(() { if(_ctrl.page!=null){ double value = (_ctrl.page! - _firstOffset + 1) % 5 / 5; factor.value = value == 0 ? 1 : value; } }); } @override void dispose() { _ctrl.dispose(); factor.dispose(); super.dispose(); } Color get color => Colors.blue; Color get nextColor =>Colors.orangeAccent; bool get isDark => Theme.of(context).brightness == Brightness.dark; BoxDecoration get boxDecoration => BoxDecoration( color: isDark?Colors.white.withAlpha(33):Colors.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(40), topRight: Radius.circular(40)), ); @override Widget build(BuildContext context) { return Scaffold( body: AnnotatedRegion( value:const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light ), child:ValueListenableBuilder( child: Column( children: [ _buildTitle(context), Expanded( child: Container( margin: const EdgeInsets.only(left: 8, right: 8), child: _buildContent(StrUnit.galleryDesc(context)), decoration: boxDecoration, )) ], ), valueListenable: factor, builder: (_,double value, child) => Container( color: isDark?null:Color.lerp( color, nextColor, value, ), child: child, ), ), ), ); } Widget _buildTitle(BuildContext context) { return Container( alignment: const Alignment(0, 0.3), height: MediaQuery.of(context).size.height * 0.2, child: Row( mainAxisSize: MainAxisSize.min, children: [ FlutterLogo( size: 40, ), SizedBox( width: 10, ), Text( context.l10n.paintCollection, style: TextStyle(fontSize: 26, color: Colors.white), ), ], ), ); } Widget _buildContentByState(BuildContext context, String state) { if(state.isEmpty){ return const LoadingShower(); } return _buildContent(StrUnit.galleryDesc(context)); } Widget _buildContent(String galleryInfo) { final List widgets = (json.decode(galleryInfo) as List).map((e) { GalleryInfo info = GalleryInfo.fromJson(e); List children = GalleryFactory.getGalleryByName(info.type,context); return FeedbackWidget( a: 0.95, onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (ctx) => GalleryDetailPage( galleryInfo: info, children: children, ))); }, child: GalleryCardItem( galleryInfo: info, count: children.length, ), ); }).toList(); return Container( padding: const EdgeInsets.only(bottom: 80, top: 40), child: Column( children: [ Expanded( child: PageView.builder( controller: _ctrl, itemBuilder: (_, index) { return AnimatedBuilder( child: Padding( padding: const EdgeInsets.all(6.0), child: widgets[ _fixPosition(index, _firstOffset, widgets.length)], ), animation: _ctrl, builder: (context, child) => _buildAnimItemByIndex(context, child, index), ); }, onPageChanged: (index) { _position = index; }, ), ), _buildDiver(), ], )); } Widget _buildAnimItemByIndex(BuildContext context, Widget? child, int index) { double value; if (_ctrl.position.haveDimensions&&_ctrl.page!=null) { value = _ctrl.page! - index; } else { value = (_position - index).toDouble(); } value = (1 - ((value.abs()) * .5)).clamp(0, 1).toDouble(); value = Curves.easeOut.transform(value); return Transform( transform: Matrix4.diagonal3Values(1.0, value, 1.0), alignment: Alignment.center, child: Padding( padding: const EdgeInsets.all(6.0), child: child, ), ); } Widget _buildDiver() => Container( margin: const EdgeInsets.only(bottom: 12, left: 48, right: 48, top: 10), height: 2, child: ValueListenableBuilder( valueListenable: factor, builder: (context, value, widget) { return LinearProgressIndicator( backgroundColor: Colors.black, value: factor.value, valueColor: AlwaysStoppedAnimation( Color.lerp( color, nextColor, factor.value, ), ), ); }, ), ); int _fixPosition(int realPos, int initPos, int length) { final int offset = realPos - initPos; int result = offset % length; return result < 0 ? length + result : result; } } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/out/clock_fx.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'rnd.dart'; import 'particle.dart'; const Duration easingDelayDuration = Duration(seconds: 10); /// Probabilities of Hour, Minute, Noise. // final particleDistributions = [2, 4, 100]; /// Number of "arms" to emit noise particles from center. const int noiseAngles = 2000; /// Threshold for particles to go rouge. Lower = more particles. const int rougeDistributionLmt = 85; /// Threshold for particles to go jelly. Lower = more particles. const int jellyDistributionLmt = 97; class ClockFx with ChangeNotifier { double width=0; //宽 double height=0;//高 double sizeMin=0; // 宽高最小值 Offset center=Offset.zero; //画布中心 Rect spawnArea=Rect.zero; // 粒子活动区域 List particles=[]; // 所有粒子 int numParticles=0;// 最大粒子数 DateTime time; //时间 ClockFx({ required Size size, required this.time, this.numParticles = 5000, }) { particles = List.filled(numParticles, Particle()); setSize(size); } void init() { for (int i = 0; i < numParticles; i++) { particles[i] = Particle(color:Colors.black ); resetParticle(i); } } void setTime(DateTime time) { this.time = time; } void setSize(Size size) { width = size.width; height = size.height; sizeMin = min(width, height); center = Offset(width / 2, height / 2); spawnArea = Rect.fromLTRB( center.dx - sizeMin / 100, center.dy - sizeMin / 100, center.dx + sizeMin / 100, center.dy + sizeMin / 100, ); init(); } /// Resets a particle's values. Particle resetParticle(int i) { Particle p = particles[i]; p.size = p.a = p.vx = p.vy = p.life = p.lifeLeft = 0; p.x = center.dx; p.y = center.dy; return p; } void tick(Duration duration) { updateParticles(duration); // 更新粒子 notifyListeners();// 通知监听者(画板)更新 } void updateParticles(Duration duration){ var secFrac = DateTime.now().millisecond / 1000; var vecSpeed = duration.compareTo(easingDelayDuration) > 0 ? max(.2, Curves.easeInOutSine.transform(1 - secFrac)) : 1; var vecSpeedInv = Curves.easeInSine.transform(secFrac); var maxSpawnPerTick = 10; particles.asMap().forEach((i, p) { p.x -= p.vx * vecSpeed; p.y -= p.vy * vecSpeed; p.dist = _getDistanceFromCenter(p); p.distFrac = p.dist / (sizeMin / 2); p.lifeLeft = p.life - p.distFrac; p.vx -= p.lifeLeft * p.vx * .001; p.vy -= p.lifeLeft * p.vy * .001; if (p.lifeLeft < .3) { p.size -= p.size * .0015; } if (p.distribution > rougeDistributionLmt && p.distribution < jellyDistributionLmt) { var r = Rnd.getDouble(.2, 2.5) * vecSpeedInv * p.distFrac; p.x -= p.vx * r + (p.distFrac * Rnd.getDouble(-.4, .4)); p.y -= p.vy * r + (p.distFrac * Rnd.getDouble(-.4, .4)); } if (p.distribution >= jellyDistributionLmt) { var r = Rnd.getDouble(.1, .9) * vecSpeedInv * (1 - p.lifeLeft); p.x += p.vx * r; p.y += p.vy * r; } if (p.lifeLeft <= 0 || p.size <= .5) { resetParticle(i); if (maxSpawnPerTick > 0) { _activateParticle(p); maxSpawnPerTick--; } } }); } void _activateParticle(Particle p) { p.x = Rnd.getDouble(spawnArea.left, spawnArea.right); p.y = Rnd.getDouble(spawnArea.top, spawnArea.bottom); p.isFilled = Rnd.getBool(); p.size = Rnd.getDouble(3, 8); p.distFrac = 0; p.distribution = Rnd.getInt(1, 2); double angle = Rnd.ratio * pi * 2; var am = _getMinuteRadians(); var ah = _getHourRadians() % (pi * 2); var d = pi / 18; // // Probably not the most efficient solution right here. do { angle = Rnd.ratio * pi * 2; } while (_isBetween(angle, am - d, am + d) || _isBetween(angle, ah - d, ah + d) ); p.life = Rnd.getDouble(0.75, .8); p.size = sizeMin * (Rnd.ratio > .8 ? Rnd.getDouble(.0015, .003) : Rnd.getDouble(.002, .006)); p.vx = sin(-angle); p.vy = cos(-angle); p.a = atan2(p.vy, p.vx) + pi; double v = Rnd.getDouble(.5, 1); p.vx *= v; p.vy *= v; } double _getDistanceFromCenter(Particle p) { var a = pow(center.dx - p.x, 2); var b = pow(center.dy - p.y, 2); return sqrt(a + b); } /// Gets the radians of the hour hand. double _getHourRadians() => (time.hour * pi / 6) + (time.minute * pi / (6 * 60)) + (time.second * pi / (360 * 60)); /// Gets the radians of the minute hand. double _getMinuteRadians() => (time.minute * (2 * pi) / 60) + (time.second * pi / (30 * 60)); /// Checks if a value is between two other values. bool _isBetween(double value, double min, double max) { return value >= min && value <= max; } } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/out/clock_widget.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'clock_fx.dart'; /// create by 张风捷特烈 on 2021/2/7 /// contact me by email 1981462002@qq.com /// 说明: class ClockWidget extends StatefulWidget { final double radius; const ClockWidget({Key? key, this.radius = 100}) : super(key: key); @override _ClockWidgetState createState() => _ClockWidgetState(); } class _ClockWidgetState extends State with SingleTickerProviderStateMixin { late Ticker _ticker; late ClockFx _fx; @override void initState() { super.initState(); _ticker = createTicker(_tick)..start(); _fx = ClockFx( size: Size(widget.radius * 2, widget.radius * 2), time: DateTime.now(), ); } @override void dispose() { _ticker.dispose(); _fx.dispose(); super.dispose(); } void _tick(Duration duration) { _fx.tick(duration); if (_fx.time.second != DateTime.now().second) { _fx.setTime(DateTime.now()); } } @override Widget build(BuildContext context) { return CustomPaint( size: Size(widget.radius * 2, widget.radius * 2), painter: ClockFxPainter(fx: _fx), ); } } /// Alpha value for noise particles. const double noiseAlpha = 160; class ClockFxPainter extends CustomPainter { final ClockFx fx; ClockFxPainter({required this.fx}) : super(repaint: fx); @override void paint(Canvas canvas, Size size) { for (var p in fx.particles) { double a; a = max(0.0, (p.distFrac - .13) / p.distFrac) * 255; a = min(a, min(noiseAlpha, p.lifeLeft * 3 * 255)); int alpha = a.floor(); Paint circlePaint = Paint() ..style = PaintingStyle.fill ..color = p.color.withAlpha(alpha); canvas.drawCircle(Offset(p.x, p.y), p.size, circlePaint); } } @override bool shouldRepaint(covariant ClockFxPainter oldDelegate) => oldDelegate.fx != fx; } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/out/particle.dart ================================================ import 'package:flutter/material.dart'; class Particle { double x; // x 坐标 double y; // y 坐标 double vx; // x 速度 double vy; // y 速度 double a; // 发射弧度 double dist; // 距离画布中心的长度 double distFrac;// 距离画布中心的百分比 double size;// 粒子大小 double life; // 粒子寿命 double lifeLeft; // 粒子剩余寿命 bool isFilled; // 是否填充 Color color; // 颜色 int distribution; // 分配情况 Particle({ this.x = 0, this.y = 0, this.a = 0, this.vx = 0, this.vy = 0, this.dist = 0, this.distFrac = 0, this.size = 0, this.life = 0, this.lifeLeft = 0, this.isFilled = false, this.color = Colors.blueAccent, this.distribution = 0, }); } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/out/rnd.dart ================================================ import 'dart:math'; class Rnd { static int _seed = DateTime.now().millisecondsSinceEpoch; static Random random = Random(_seed); static set seed(int val) => random = Random(_seed = val); static int get seed => _seed; /// Gets the next double. static get ratio => random.nextDouble(); /// Gets a random int between [min] and [max]. static int getInt(int min, int max) { return min + random.nextInt(max - min); } /// Gets a random double between [min] and [max]. static double getDouble(double min, double max) { return min + random.nextDouble() * (max - min); } /// Gets a random boolean with chance [chance]. static bool getBool([double chance = 0.5]) { return random.nextDouble() < chance; } /// Randomize the positions of items in a list. static List shuffle(List list) { for (int i = 0, l = list.length; i < l; i++) { int j = random.nextInt(l); if (j == i) { continue; } dynamic item = list[j]; list[j] = list[i]; list[i] = item; } return list; } /// Randomly selects an item from a list. static dynamic getItem(List list) { return list[random.nextInt(list.length)]; } } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/random/particle.dart ================================================ import 'package:flutter/material.dart'; class Particle { /// x 位移. double x; /// y 位移. double y; /// 粒子水平速度. double vx; // 粒子水平加速度 double ax; // 粒子竖直加速度 double ay; ///粒子竖直速度. double vy; /// 粒子大小. double size; /// 粒子颜色. Color color; Particle({ this.x = 0, this.y = 0, this.ax = 0, this.ay = 0, this.vx = 0, this.vy = 0, this.size = 0, this.color = Colors.black, }); } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/random/particle_manage.dart ================================================ import 'package:flutter/cupertino.dart'; import 'particle.dart'; /// create by 张风捷特烈 on 2020/11/7 /// contact me by email 1981462002@qq.com /// 说明: class ParticleManage with ChangeNotifier { List particles = []; Size size; ParticleManage({ this.size = Size.zero}); void addParticle(Particle particle) { particles.add(particle); notifyListeners(); } void tick() { particles.forEach(doUpdate); notifyListeners(); } void doUpdate(Particle p) { p.vy += p.ay; // y加速度变化 p.vx += p.ax; // x加速度变化 p.x += p.vx; p.y += p.vy; if(p.x>size.width-p.size){ p.x = size.width-p.size; p.vx = -p.vx; } if(p.x < p.size){ p.x = p.size; p.vx = -p.vx; } if(p.y > size.height-p.size){ p.y = size.height-p.size; p.vy = -p.vy; } if(p.y < p.size){ p.y = p.size; p.vy = -p.vy; } } void reset() { for (Particle p in particles) { p.x = 0; } notifyListeners(); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/random/random_particle.dart ================================================ import 'dart:async'; import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'particle.dart'; import 'particle_manage.dart'; import 'world_render.dart'; /// create by 张风捷特烈 on 2020/11/5 /// contact me by email 1981462002@qq.com /// 说明: class RandomParticle extends StatefulWidget { const RandomParticle({Key? key}) : super(key: key); @override _RandomParticleState createState() => _RandomParticleState(); } class _RandomParticleState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; ParticleManage pm = ParticleManage(); Random random = Random(); Timer? _timer; @override void initState() { super.initState(); _timer =Timer.periodic(const Duration(seconds: 1),addParticle); _controller = AnimationController( duration: const Duration(seconds: 1), vsync: this, )..addListener(pm.tick) ..repeat(); } void addParticle(Timer timer){ if(pm.particles.length>20){ timer.cancel(); } pm.addParticle(Particle( color: randomRGB(), size: 5 + 4 * random.nextDouble(), vx: 3 * random.nextDouble() * pow(-1, random.nextInt(20)), vy: 3 * random.nextDouble() * pow(-1, random.nextInt(20)), ay: 0.1, x: 150, y: 100)); } Color randomRGB({int limitR = 0, int limitG = 0, int limitB = 0,}) { var r = limitR + random.nextInt(256 - limitR); //红值 var g = limitG + random.nextInt(256 - limitG); //绿值 var b = limitB + random.nextInt(256 - limitB); //蓝值 return Color.fromARGB(255, r, g, b); //生成argb模式的颜色 } @override void dispose() { _controller.dispose(); _timer?.cancel(); super.dispose(); } theWorld() { if (_controller.isAnimating) { _controller.stop(); } else { _controller.repeat(); } } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (_,constraints){ pm.size = constraints.biggest; return GestureDetector( onTap: theWorld, child: CustomPaint( size: pm.size, painter: WorldRender(manage: pm), ), ); }, ); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/random/world_render.dart ================================================ import 'package:flutter/material.dart'; import 'particle.dart'; import 'particle_manage.dart'; /// create by 张风捷特烈 on 2020/11/7 /// contact me by email 1981462002@qq.com /// 说明: class WorldRender extends CustomPainter { final ParticleManage manage; Paint fillPaint = Paint(); Paint stokePaint = Paint() ..strokeWidth = 0.5 ..style = PaintingStyle.stroke; WorldRender({required this.manage}) : super(repaint: manage); @override void paint(Canvas canvas, Size size) { for (Particle particle in manage.particles) { drawParticle(canvas, particle); } } void drawParticle(Canvas canvas, Particle particle) { fillPaint.color = particle.color; canvas.drawCircle(Offset(particle.x, particle.y), particle.size, fillPaint); } @override bool shouldRepaint(covariant WorldRender oldDelegate) => oldDelegate.manage != manage; } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/split/particle.dart ================================================ import 'package:flutter/material.dart'; class Particle { /// x 位移. double x; /// y 位移. double y; /// 粒子水平速度. double vx; // 粒子水平加速度 double ax; // 粒子竖直加速度 double ay; ///粒子竖直速度. double vy; /// 粒子大小. double size; /// 粒子颜色. Color color; Particle({ this.x = 0, this.y = 0, this.ax = 0, this.ay = 0, this.vx = 0, this.vy = 0, this.size = 0, this.color = Colors.black, }); Particle copyWith( { double? x, double? y, double? ax, double? ay, double? vx, double? vy, double? size, Color? color } ) => Particle( x: x ?? this.x, y: y ?? this.y, ax: ax ?? this.ax, ay: ay ?? this.ay, vx: vx ?? this.vx, vy: vy ?? this.vy, size: size ?? this.size, color: color ?? this.color); } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/split/particle_manage.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'particle.dart'; /// create by 张风捷特烈 on 2020/11/7 /// contact me by email 1981462002@qq.com /// 说明: class ParticleManage with ChangeNotifier { List particles = []; Random random = Random(); Size size; ParticleManage({this.size=Size.zero}); void setParticles(List particles) { this.particles = particles; } void addParticle(Particle particle) { particles.add(particle); notifyListeners(); } void tick() { for (int i = 0; i < particles.length; i++) { doUpdate(particles[i]); } notifyListeners(); } void doUpdate(Particle p) { p.vy += p.ay; // y加速度变化 p.vx += p.ax; // x加速度变化 p.x += p.vx; p.y += p.vy; if (p.x > size.width-p.size) { p.x = size.width-p.size; var newSize = p.size / 2; if (newSize > 1) {// 如果尺寸小于 1 ,执行分裂 Particle p0 = p.copyWith(size: newSize, vx: -p.vx, vy: -p.vy, color: randomRGB()); particles.add(p0); p.size = newSize; p.vx = -p.vx; } } if (p.x < p.size) { p.x = p.size; p.vx = -p.vx; } if (p.y > size.height-p.size) { p.y = size.height-p.size; var newSize = p.size / 2; if (newSize > 1) { Particle p0 = p.copyWith(size: newSize, vx: -p.vx, vy: -p.vy, color: randomRGB()); particles.add(p0); p.size = newSize; p.vy = -p.vy; } } if (p.y < p.size) { p.y = p.size; p.vy = -p.vy; } } void reset() { for (Particle p in particles) { p.x = 0; } notifyListeners(); } Color randomRGB({ int limitR = 0, int limitG = 0, int limitB = 0, }) { var r = limitR + random.nextInt(256 - limitR); //红值 var g = limitG + random.nextInt(256 - limitG); //绿值 var b = limitB + random.nextInt(256 - limitB); //蓝值 return Color.fromARGB(255, r, g, b); //生成argb模式的颜色 } } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/split/particle_split.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'particle.dart'; import 'particle_manage.dart'; import 'world_render.dart'; /// create by 张风捷特烈 on 2020/11/5 /// contact me by email 1981462002@qq.com /// 说明: class ParticleSplit extends StatefulWidget { const ParticleSplit({Key? key}) : super(key: key); @override _ParticleSplitState createState() => _ParticleSplitState(); } class _ParticleSplitState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; ParticleManage pm = ParticleManage(); Random random = Random(); @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 1), vsync: this, )..addListener(pm.tick); initParticle(); // Future.delayed(Duration(seconds: 1)).then((value){ // // }); } @override void dispose() { _controller.dispose(); super.dispose(); } // void theWorld() { // if (_controller.isAnimating) { // _controller.stop(); // } else { // _controller.repeat(); // } // } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (_,constraints){ pm.size = constraints.biggest; return GestureDetector( // onDoubleTap: (){ // pm.addParticle(Particle( // color: Colors.blue, // size: 50, // vx: 4 * random.nextDouble() * pow(-1, random.nextInt(20)), // vy: 4 * random.nextDouble() * pow(-1, random.nextInt(20)), // ay: 0.1, // ax: 0.1, // x: 150, // y: 100)); // }, onTap: initParticle, child: CustomPaint( size: pm.size, painter: WorldRender(manage: pm), ), ); }, ); } void initParticle(){ pm.particles.clear(); pm.addParticle(Particle( color: Colors.blue, size: 50, vx: 4 * random.nextDouble() * pow(-1, random.nextInt(20)), vy: 4 * random.nextDouble() * pow(-1, random.nextInt(20)), ay: 0.1, ax: 0.1, x: 150, y: 100)); _controller.reset(); _controller.repeat(); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/split/world_render.dart ================================================ import 'package:flutter/material.dart'; import 'particle.dart'; import 'particle_manage.dart'; /// create by 张风捷特烈 on 2020/11/7 /// contact me by email 1981462002@qq.com /// 说明: class WorldRender extends CustomPainter { final ParticleManage manage; Paint fillPaint = Paint(); Paint stokePaint = Paint() ..strokeWidth = 0.5 ..style = PaintingStyle.stroke; WorldRender({ required this.manage}) : super(repaint: manage); @override void paint(Canvas canvas, Size size) { for (Particle particle in manage.particles) { drawParticle(canvas, particle); } } void drawParticle(Canvas canvas, Particle particle) { fillPaint.color = particle.color; canvas.drawCircle(Offset(particle.x, particle.y), particle.size, fillPaint); } @override bool shouldRepaint(covariant WorldRender oldDelegate) => oldDelegate.manage != manage; } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/split_img/particle.dart ================================================ import 'package:flutter/material.dart'; class Particle { /// x 位移. double x; /// y 位移. double y; /// 粒子水平速度. double vx; // 粒子水平加速度 double ax; // 粒子竖直加速度 double ay; ///粒子竖直速度. double vy; /// 粒子大小. double size; /// 粒子颜色. Color color; Particle({ this.x = 0, this.y = 0, this.ax = 0, this.ay = 0, this.vx = 0, this.vy = 0, this.size = 0, this.color = Colors.black, }); Particle copyWith( { double? x, double? y, double? ax, double? ay, double? vx, double? vy, double? size, Color? color} ) => Particle( x: x ?? this.x, y: y ?? this.y, ax: ax ?? this.ax, ay: ay ?? this.ay, vx: vx ?? this.vx, vy: vy ?? this.vy, size: size ?? this.size, color: color ?? this.color); } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/split_img/particle_manage.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'particle.dart'; /// create by 张风捷特烈 on 2020/11/7 /// contact me by email 1981462002@qq.com /// 说明: class ParticleManage with ChangeNotifier { List particles = []; final VoidCallback? onEnd; Random random = Random(); Size size; ParticleManage({this.size=Size.zero,this.onEnd}); void setParticles(List particles) { this.particles = particles; } void addParticle(Particle particle) { particles.add(particle); notifyListeners(); } void tick({bool run=true}) { if(run){ for (int i = 0; i < particles.length; i++) { doUpdate(particles[i]); } } if(particles.isNotEmpty){ notifyListeners(); } } void doUpdate(Particle p) { p.vy += p.ay; // y加速度变化 p.vx += p.ax; // x加速度变化 p.x += p.vx; p.y += p.vy; if (p.x > size.width) { particles.remove(p); } if (p.x < 0) { particles.remove(p); } if (p.y > size.height) { particles.remove(p); } if (p.y < 0) { particles.remove(p); } if(particles.isEmpty){ onEnd?.call(); } } void reset() { for (Particle p in particles) { p.x = 0; } notifyListeners(); } Color randomRGB({ int limitR = 0, int limitG = 0, int limitB = 0, }) { var r = limitR + random.nextInt(256 - limitR); //红值 var g = limitG + random.nextInt(256 - limitG); //绿值 var b = limitB + random.nextInt(256 - limitB); //蓝值 return Color.fromARGB(255, r, g, b); //生成argb模式的颜色 } } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/split_img/split_image.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'particle.dart'; import 'package:image/image.dart' as image; import 'particle_manage.dart'; import 'world_render.dart'; /// create by 张风捷特烈 on 2020/11/5 /// contact me by email 1981462002@qq.com /// 说明: class SplitImage extends StatefulWidget { const SplitImage({Key? key}) : super(key: key); @override _SplitImageState createState() => _SplitImageState(); } class _SplitImageState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late ParticleManage pm; Random random = Random(); @override void initState() { super.initState(); pm = ParticleManage( onEnd: (){ _controller.stop(); } ); initParticles(); _controller = AnimationController( duration: const Duration(seconds: 1), vsync: this, )..addListener(pm.tick); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (_,constraints){ pm.size = constraints.biggest; return Stack( children: [ GestureDetector( onTap: (){ initParticles(); _controller.reset(); _controller.repeat(); }, child: CustomPaint( size: pm.size, painter: WorldRender(manage: pm), ), ), ], ); }, ); } void initParticles() async { pm.particles.clear(); ByteData data = await rootBundle.load("assets/images/flutter.png"); List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); image.Image? imageSrc = image.decodeImage(Uint8List.fromList(bytes)); if(imageSrc==null) return; double offsetX= (pm.size.width-imageSrc.width)/2; double offsetY= (pm.size.height-imageSrc.height)/2; for (int i = 0; i < imageSrc.width; i++) { for (int j = 0; j < imageSrc.height; j++) { if (imageSrc.getPixel(i, j) == 0xff000000) { Particle particle = Particle( x: i * 1.0+ offsetX, y: j * 1.0+ offsetY, vx: 4 * random.nextDouble() * pow(-1, random.nextInt(20)), vy: 4 * random.nextDouble() * pow(-1, random.nextInt(20)), ay: 0.1, size: 0.5, color: Colors.blue); //产生粒子---每个粒子拥有随机的一些属性信息 pm.particles.add(particle); } } } pm.tick(run: false); } } ================================================ FILE: modules/painting_system/draw_system/lib/src/particle/split_img/world_render.dart ================================================ import 'package:flutter/material.dart'; import 'particle.dart'; import 'particle_manage.dart'; /// create by 张风捷特烈 on 2020/11/7 /// contact me by email 1981462002@qq.com /// 说明: class WorldRender extends CustomPainter { final ParticleManage manage; Paint fillPaint = Paint(); Paint stokePaint = Paint() ..strokeWidth = 0.5 ..style = PaintingStyle.stroke; WorldRender({required this.manage}) : super(repaint: manage); @override void paint(Canvas canvas, Size size) { for (Particle particle in manage.particles) { drawParticle(canvas, particle); } } void drawParticle(Canvas canvas, Particle particle) { fillPaint.color = particle.color; canvas.drawCircle(Offset(particle.x, particle.y), particle.size, fillPaint); } @override bool shouldRepaint(covariant WorldRender oldDelegate) => oldDelegate.manage != manage; } ================================================ FILE: modules/painting_system/draw_system/lib/src/picture_frame.dart ================================================ import 'dart:io'; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'desk_ui/desk_frame.dart'; import 'package:l10n/l10n.dart'; class PictureFrame extends StatelessWidget { final Widget? child; final double? width; final double? height; final Color? color; final Alignment? alignment; final EdgeInsetsGeometry? marge; const PictureFrame({Key? key, this.child, this.width, this.height, this.alignment, this.color = Colors.transparent, this.marge}) : super(key: key); @override Widget build(BuildContext context) { double size = MediaQuery.of(context).size.shortestSide; return Container( alignment: alignment, width: width ?? size, height: height ?? size, padding: marge ?? const EdgeInsets.all(20), child: CustomPaint( painter: FramePainter(), child: Container( margin: const EdgeInsets.all(14), decoration: BoxDecoration( color: color, border: Border.all( color: Colors.black12, width: 1, ), ), child: child, ), ), ); } } class FramePainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { Path path = Path() ..relativeLineTo(0, size.height)..relativeLineTo(size.width, 0)..relativeLineTo(0, -size.height) ..close(); Paint myPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 10; canvas.drawPath(path, myPaint); Path shadowPath = Path() ..addRect(Rect.fromPoints(Offset.zero, Offset(size.width, size.height))); // canvas.drawShadow(shadowPath, Colors.grey, 1, false); canvas.save(); canvas.translate(size.width / 2, size.height / 2); canvas.scale(-1, 1); canvas.translate(-size.width / 2, -size.height / 2); drawCorner(myPaint, canvas); canvas.restore(); canvas.save(); canvas.translate(size.width / 2, size.height / 2); canvas.scale(1, -1); canvas.translate(-size.width / 2, -size.height / 2); drawCorner(myPaint, canvas); canvas.restore(); canvas.save(); canvas.translate(size.width, size.height); canvas.scale(-1, -1); drawCorner(myPaint, canvas); canvas.restore(); drawCorner(myPaint, canvas); canvas.drawShadow(shadowPath, Colors.grey, 1, false); } void drawCorner(Paint myPaint, Canvas canvas) { myPaint ..style = PaintingStyle.fill ..color = Colors.white ..strokeCap = StrokeCap.butt; canvas.drawPoints( PointMode.polygon, [ const Offset(0, 0), const Offset(18, 0), const Offset(0, 18), const Offset(0, 0), ], myPaint); canvas.drawCircle(const Offset(8, 8), 3, myPaint..color = Colors.black); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } } class FrameShower extends StatelessWidget { final String title; final String author; final String srcUrl; final String info; final Widget content; const FrameShower({Key? key, this.title = "", this.author = "", this.srcUrl = "", this.info = "", required this.content}) : super(key: key); @override Widget build(BuildContext context) { bool isDesk = kIsWeb||Platform.isMacOS || Platform.isWindows || Platform.isLinux; if (isDesk) { return DeskFrameShower( content: content, title: title, author: author, srcUrl: srcUrl, info: info); } return Align( alignment: Alignment.topCenter, child: Column( children: [ const SizedBox(height: 15), Text( title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), PictureFrame(child: content,color: Colors.white,), Container( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ Text( "作者: $author ", style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), const Spacer(), GestureDetector( onTap: _launch, child: Text( "${context.l10n.srcPath} ", style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.blueAccent), ), ), ], ), ), const SizedBox(height: 10), Container( padding: const EdgeInsets.symmetric(horizontal: 12), alignment: Alignment.topLeft, child: Text( info, style: const TextStyle( fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold), )), ], ), ); } void _launch() async { String url = 'https://github.com/toly1994328/FlutterUnit/tree/master/lib/painter_system$srcUrl'; if (await canLaunch(url)) { await launch(url); } else { } } } ================================================ FILE: modules/painting_system/draw_system/lib/src/utils/colors.dart ================================================ import 'dart:ui'; const List colors = [ Color(0xff8e43e7), Color(0xffff4f81), Color(0xff1cc7d0), Color(0xff00aeff), Color(0xff3369e7), Color(0xffb84592), Color(0xff2dde98), Color(0xffff6c5f), Color(0xff003666), Color(0xffffc168), Color(0xff050f2c), ]; ================================================ FILE: modules/painting_system/draw_system/lib/src/utils/coordinate.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/5/1 /// contact me by email 1981462002@qq.com /// 说明: @immutable class Coordinate { final double step; final double strokeWidth; final Color axisColor; final Color gridColor; final double numScale; final bool yTop; final TextPainter _textPainter = TextPainter(textDirection: TextDirection.ltr); Coordinate( {this.step = 20, this.numScale = 20, this.yTop = false, this.strokeWidth = .5, this.axisColor = Colors.blue, this.gridColor = Colors.grey}); final Paint _gridPaint = Paint(); final Path _gridPath = Path(); void paint(Canvas canvas, Size size) { canvas.save(); canvas.translate(size.width / 2, size.height / 2); _drawGridLine(canvas, size); if (yTop) { canvas.save(); canvas.scale(1, -1); _drawAxis(canvas, size); canvas.restore(); } else { _drawAxis(canvas, size); } _drawText(canvas, size); TextSpan text = const TextSpan( text: "x", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, color: Colors.black, )); _textPainter.text = text; _textPainter.layout(); // 进行布局 _textPainter.paint(canvas, Offset(size.width / 2 - 20, -30)); _textPainter.text = const TextSpan( text: "y", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, color: Colors.black, )); _textPainter.layout(); // 进行布局 if (yTop) { _textPainter.paint(canvas, Offset(-20, -size.height / 2)); } else { _textPainter.paint(canvas, Offset(-20, size.height / 2 - 30)); } canvas.restore(); } void _drawAxis(Canvas canvas, Size size) { _gridPaint ..color = Colors.blue ..strokeWidth = 1.5; canvas.drawLine( Offset(-size.width / 2, 0), Offset(size.width / 2, 0), _gridPaint); canvas.drawLine( Offset(0, -size.height / 2), Offset(0, size.height / 2), _gridPaint); canvas.drawLine(Offset(0, size.height / 2), Offset(0 - 7.0, size.height / 2 - 10), _gridPaint); canvas.drawLine(Offset(0, size.height / 2), Offset(0 + 7.0, size.height / 2 - 10), _gridPaint); canvas.drawLine( Offset(size.width / 2, 0), Offset(size.width / 2 - 10, 7), _gridPaint); canvas.drawLine( Offset(size.width / 2, 0), Offset(size.width / 2 - 10, -7), _gridPaint); } void _drawGridLine(Canvas canvas, Size size) { _gridPaint ..style = PaintingStyle.stroke ..strokeWidth = .5 ..color = Colors.grey; for (int i = 0; i < size.width / 2 / step; i++) { _gridPath.moveTo(step * i, -size.height / 2); _gridPath.relativeLineTo(0, size.height); _gridPath.moveTo(-step * i, -size.height / 2); _gridPath.relativeLineTo(0, size.height); } for (int i = 0; i < size.height / 2 / step; i++) { _gridPath.moveTo(-size.width / 2, step * i); _gridPath.relativeLineTo(size.width, 0); _gridPath.moveTo( -size.width / 2, -step * i, ); _gridPath.relativeLineTo(size.width, 0); } canvas.drawPath(_gridPath, _gridPaint); } void _drawAxisText(Canvas canvas, String str, {Color color = Colors.black, bool? x = false}) { TextSpan text = TextSpan( text: str, style: TextStyle( fontSize: 11, color: color, )); _textPainter.text = text; _textPainter.layout(); // 进行布局 Size size = _textPainter.size; Offset offset = Offset.zero; if (x == null) { offset = Offset(-size.width * 1.5, size.width * 0.7); } else if (x) { offset = Offset(-size.width / 2, size.height / 2); } else { offset = Offset(size.height / 2, -size.height / 2 + 2); } _textPainter.paint(canvas, offset); } void _drawText(Canvas canvas, Size size) { // y > 0 轴 文字 canvas.save(); for (int i = 0; i < size.height / 2 / step; i++) { if (step < 20 && i.isOdd || i == 0) { canvas.translate(0, step); continue; } else { var num = (yTop ? (-i * step) : (i * step)) / numScale; _drawAxisText(canvas, num.toInt().toString(), color: Colors.green); } canvas.translate(0, step); } canvas.restore(); // x > 0 轴 文字 canvas.save(); for (int i = 0; i < size.width / 2 / step; i++) { if (i == 0) { _drawAxisText(canvas, "O", color: Colors.black, x: null); canvas.translate(step, 0); continue; } if (step < 20 && i.isOdd) { canvas.translate(step, 0); continue; } else { var str = (i * step) / numScale; _drawAxisText(canvas, str.toInt().toString(), color: Colors.green, x: true); } canvas.translate(step, 0); } canvas.restore(); // y < 0 轴 文字 canvas.save(); for (int i = 0; i < size.height / 2 / step; i++) { if (step < 20 && i.isOdd || i == 0) { canvas.translate(0, -step); continue; } else { var num = (yTop ? (i * step) : (-i * step)) / numScale; _drawAxisText(canvas, num.toInt().toString(), color: Colors.green); } canvas.translate(0, -step); } canvas.restore(); // x < 0 轴 文字 canvas.save(); for (int i = 0; i < size.width / 2 / step; i++) { if (step < 20 && i.isOdd || i == 0) { canvas.translate(-step, 0); continue; } else { var str = (-i * step) / numScale; _drawAxisText(canvas, str.toInt().toString(), color: Colors.green, x: true); } canvas.translate(-step, 0); } canvas.restore(); } } ================================================ FILE: modules/painting_system/draw_system/pubspec.yaml ================================================ name: draw_system description: "A new Flutter package project." version: 0.0.1 homepage: publish_to: none environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/painting_system/draw_system/test/draw_system_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:draw_system/draw_system.dart'; void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); } ================================================ FILE: modules/tools_system/pkg_player/.amazonq/rules/dart.md ================================================ Dart的spread operator应该是三个点 ================================================ FILE: modules/tools_system/pkg_player/.amazonq/rules/file_create.md ================================================ dart 脚本放在 test/scripts 下 生成的文件放在 test/science_server/moke/output 下 ================================================ FILE: modules/tools_system/pkg_player/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies build/ /test/science_server/moke /doc/* ================================================ FILE: modules/tools_system/pkg_player/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "c519ee916eaeb88923e67befb89c0f1dabfa83e6" channel: "stable" project_type: package ================================================ FILE: modules/tools_system/pkg_player/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/tools_system/pkg_player/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/tools_system/pkg_player/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/tools_system/pkg_player/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/tools_system/pkg_player/desiredFileName.txt ================================================ {} ================================================ FILE: modules/tools_system/pkg_player/devtools_options.yaml ================================================ description: This file stores settings for Dart & Flutter DevTools. documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states extensions: ================================================ FILE: modules/tools_system/pkg_player/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .build/ .buildlog/ .history .svn/ .swiftpm/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .pub-cache/ .pub/ /build/ # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release ================================================ FILE: modules/tools_system/pkg_player/example/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "d8a9f9a52e5af486f80d932e838ee93861ffd863" channel: "stable" project_type: app # Tracks metadata for the flutter migrate command migration: platforms: - platform: root create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 - platform: android create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 - platform: ios create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 - platform: linux create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 - platform: macos create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 - platform: web create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 - platform: windows create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 # User provided section # List of Local paths (relative to this file) that should be # ignored by the migrate tool. # # Files that are not part of the templates will be ignored by default. unmanaged_files: - 'lib/main.dart' - 'ios/Runner.xcodeproj/project.pbxproj' ================================================ FILE: modules/tools_system/pkg_player/example/README.md ================================================ # example A new Flutter project. ## Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. ================================================ FILE: modules/tools_system/pkg_player/example/analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at https://dart.dev/lints. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/tools_system/pkg_player/example/android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks ================================================ FILE: modules/tools_system/pkg_player/example/android/app/build.gradle ================================================ plugins { id "com.android.application" id "kotlin-android" // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id "dev.flutter.flutter-gradle-plugin" } android { namespace = "com.example.example" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.example.example" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.debug } } } flutter { source = "../.." } ================================================ FILE: modules/tools_system/pkg_player/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt ================================================ package com.example.example import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() ================================================ FILE: modules/tools_system/pkg_player/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/android/build.gradle ================================================ allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { afterEvaluate { project -> if (project.plugins.hasPlugin('com.android.library') || project.plugins.hasPlugin('com.android.application')) { println "project: ${project.name} Namespace get: ${project.android.namespace}" def packageName = project.android.namespace ?: project.android.defaultConfig.applicationId ?: project.android.sourceSets.main.manifest.srcFile.text.find(/package="([^"]*)"/) ?: project.group project.android.namespace = packageName println "Namespace set to: ${packageName} for project: ${project.name}" def manifestFile = file("${project.projectDir}/src/main/AndroidManifest.xml") if (manifestFile.exists()) { def manifestText = manifestFile.text.replaceAll(/package="[^"]*"/, "") manifestFile.text = manifestText println "Removed package attribute in ${project.name}" } } // ✅ 添加统一 Java 编译配置 if (project.plugins.hasPlugin('com.android.application') || project.plugins.hasPlugin('com.android.library')) { if (project.name == 'app') { project.android.compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 coreLibraryDesugaringEnabled true } project.dependencies.add("coreLibraryDesugaring", "com.android.tools:desugar_jdk_libs:2.0.4") } else { project.android.compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 // 不启用 desugaring,避免插件强制启用 MultiDex } } } } // ✅ 添加统一 Kotlin 编译设置(适用于所有 Kotlin 子模块) project.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { kotlinOptions { jvmTarget = "1.8" } } project.evaluationDependsOn(':app') } tasks.register("clean", Delete) { delete rootProject.buildDir } ================================================ FILE: modules/tools_system/pkg_player/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip ================================================ FILE: modules/tools_system/pkg_player/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true ================================================ FILE: modules/tools_system/pkg_player/example/android/settings.gradle ================================================ pluginManagement { def flutterSdkPath = { def properties = new Properties() file("local.properties").withInputStream { properties.load(it) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" return flutterSdkPath }() includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.3.2" apply false id "org.jetbrains.kotlin.android" version "1.9.0" apply false } include ":app" ================================================ FILE: modules/tools_system/pkg_player/example/devtools_options.yaml ================================================ description: This file stores settings for Dart & Flutter DevTools. documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states extensions: ================================================ FILE: modules/tools_system/pkg_player/example/ios/.gitignore ================================================ **/dgph *.mode1v3 *.mode2v3 *.moved-aside *.pbxuser *.perspectivev3 **/*sync/ .sconsign.dblite .tags* **/.vagrant/ **/DerivedData/ Icon? **/Pods/ **/.symlinks/ profile xcuserdata **/.generated/ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !default.mode1v3 !default.mode2v3 !default.pbxuser !default.perspectivev3 ================================================ FILE: modules/tools_system/pkg_player/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 13.0 ================================================ FILE: modules/tools_system/pkg_player/example/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: modules/tools_system/pkg_player/example/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: modules/tools_system/pkg_player/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| # You can remove unused permissions here # for more information: https://github.com/Baseflow/flutter-permission-handler/blob/main/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', ## dart: PermissionGroup.calendar 'PERMISSION_EVENTS=1', ## dart: PermissionGroup.calendarFullAccess 'PERMISSION_EVENTS_FULL_ACCESS=1', ## dart: PermissionGroup.reminders 'PERMISSION_REMINDERS=1', ## dart: PermissionGroup.contacts 'PERMISSION_CONTACTS=1', ## dart: PermissionGroup.camera 'PERMISSION_CAMERA=1', ## dart: PermissionGroup.microphone 'PERMISSION_MICROPHONE=1', ## dart: PermissionGroup.speech 'PERMISSION_SPEECH_RECOGNIZER=1', ## dart: PermissionGroup.photos 'PERMISSION_PHOTOS=1', ## The 'PERMISSION_LOCATION' macro enables the `locationWhenInUse` and `locationAlways` permission. If ## the application only requires `locationWhenInUse`, only specify the `PERMISSION_LOCATION_WHENINUSE` ## macro. ## ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] 'PERMISSION_LOCATION=1', 'PERMISSION_LOCATION_WHENINUSE=0', ## dart: PermissionGroup.notification 'PERMISSION_NOTIFICATIONS=1', ## dart: PermissionGroup.mediaLibrary 'PERMISSION_MEDIA_LIBRARY=1', ## dart: PermissionGroup.sensors 'PERMISSION_SENSORS=1', ## dart: PermissionGroup.bluetooth 'PERMISSION_BLUETOOTH=1', ## dart: PermissionGroup.appTrackingTransparency 'PERMISSION_APP_TRACKING_TRANSPARENCY=1', ## dart: PermissionGroup.criticalAlerts 'PERMISSION_CRITICAL_ALERTS=1', ## dart: PermissionGroup.assistant 'PERMISSION_ASSISTANT=1', ] end end end ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner/AppDelegate.swift ================================================ import Flutter import UIKit @main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # Launch Screen Assets You can customize the launch screen with your own desired assets by replacing the image files in this directory. 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. ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName example CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 414745FF2F92DD1B59A757A8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 867AB0040861771D848C52A6 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 86B74DE2E37DCCAF358F485D /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EA9F6A8C07D0EB6E1F42518 /* Pods_RunnerTests.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 22F0C071EC26AB098D224DAC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 2667C26F738C91A9296C7664 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4BE031F4BDDCC076922942C6 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 4EA9F6A8C07D0EB6E1F42518 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 83781D16FDC772B1F05035EC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 867AB0040861771D848C52A6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DA79ED9726DD539C034B5C35 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; DCDF0B73C6F266F9442B794B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 414745FF2F92DD1B59A757A8 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; BC2A96BFADDFB2ECFE5BD187 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 86B74DE2E37DCCAF358F485D /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( 331C807B294A618700263BE5 /* RunnerTests.swift */, ); path = RunnerTests; sourceTree = ""; }; 48AFAB8C8A750B524AB7750C /* Pods */ = { isa = PBXGroup; children = ( 83781D16FDC772B1F05035EC /* Pods-Runner.debug.xcconfig */, 4BE031F4BDDCC076922942C6 /* Pods-Runner.release.xcconfig */, DCDF0B73C6F266F9442B794B /* Pods-Runner.profile.xcconfig */, 22F0C071EC26AB098D224DAC /* Pods-RunnerTests.debug.xcconfig */, DA79ED9726DD539C034B5C35 /* Pods-RunnerTests.release.xcconfig */, 2667C26F738C91A9296C7664 /* Pods-RunnerTests.profile.xcconfig */, ); name = Pods; path = Pods; sourceTree = ""; }; 56E04736E3772E27999F81A5 /* Frameworks */ = { isa = PBXGroup; children = ( 867AB0040861771D848C52A6 /* Pods_Runner.framework */, 4EA9F6A8C07D0EB6E1F42518 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 48AFAB8C8A750B524AB7750C /* Pods */, 56E04736E3772E27999F81A5 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 331C8080294A63A400263BE5 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 1A178D63134EC55D1285588B /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, BC2A96BFADDFB2ECFE5BD187 /* Frameworks */, ); buildRules = ( ); dependencies = ( 331C8086294A63A400263BE5 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 5D9AB93AE6E2FCA2C6E9F35A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 20B22DA90D8335F0D0055FBE /* [CP] Embed Pods Frameworks */, F2ECC6564DF180A0BBEEF56D /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { CreatedOnToolsVersion = 14.0; TestTargetID = 97C146ED1CF9000F007C117D; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 331C807F294A63A400263BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 1A178D63134EC55D1285588B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 20B22DA90D8335F0D0055FBE /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 5D9AB93AE6E2FCA2C6E9F35A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; F2ECC6564DF180A0BBEEF56D /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 331C807D294A63A400263BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Profile; }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = V6P5QXUH55; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 22F0C071EC26AB098D224DAC /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; name = Debug; }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = DA79ED9726DD539C034B5C35 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; name = Release; }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 2667C26F738C91A9296C7664 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = V6P5QXUH55; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = V6P5QXUH55; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 331C8088294A63A400263BE5 /* Debug */, 331C8089294A63A400263BE5 /* Release */, 331C808A294A63A400263BE5 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: modules/tools_system/pkg_player/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: modules/tools_system/pkg_player/example/ios/RunnerTests/RunnerTests.swift ================================================ import Flutter import UIKit import XCTest class RunnerTests: XCTestCase { func testExample() { // If you add code to the Runner application, consider adding tests here. // See https://developer.apple.com/documentation/xctest for more information about using XCTest. } } ================================================ FILE: modules/tools_system/pkg_player/example/lib/app_theme.dart ================================================ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; ThemeData lightTheme() { SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.dark, systemNavigationBarColor: Colors.transparent); double px1 = 1/window.devicePixelRatio; return ThemeData( scaffoldBackgroundColor: const Color(0xffF3F4F6), useMaterial3: true, colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Android 使用 Material3 chipTheme: const ChipThemeData(padding: EdgeInsets.symmetric(horizontal: 10)), listTileTheme: const ListTileThemeData( tileColor: Colors.white, textColor: Color(0xff333333)), ///设置选中的文本颜色 textSelectionTheme: TextSelectionThemeData( selectionColor: Colors.blue.withOpacity(0.3), ), dividerTheme: DividerThemeData( color: const Color(0xffDEE0E2), space: px1, thickness: px1, ), // pageTransitionsTheme: const PageTransitionsTheme(builders: { // TargetPlatform.android: SlidePageTransitionsBuilder(), // TargetPlatform.iOS: SlidePageTransitionsBuilder(), // TargetPlatform.macOS: FadePageTransitionsBuilder(), // TargetPlatform.windows: FadePageTransitionsBuilder(), // TargetPlatform.linux: FadePageTransitionsBuilder(), // }), tabBarTheme: TabBarThemeData( dividerColor: Colors.transparent, // labelStyle: TextStyle(fontFamily: fontFamily), // unselectedLabelStyle: TextStyle(fontFamily: fontFamily), splashFactory: NoSplash.splashFactory, overlayColor: WidgetStateProperty.resolveWith( (Set states) { return states.contains(WidgetState.focused) ? null : Colors.transparent; }, ), ), bottomNavigationBarTheme: const BottomNavigationBarThemeData(backgroundColor: Colors.white), appBarTheme: AppBarTheme( systemOverlayStyle: overlayStyle, elevation: 0, centerTitle: true, backgroundColor: Colors.white, titleTextStyle: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black, ), ), ); } ================================================ FILE: modules/tools_system/pkg_player/example/lib/main.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:pkg_player/pkg_player.dart'; import 'app_theme.dart'; class UnitApiAuth extends ApiAuth { @override FutureOr> get buildHeaders => { // 'locale': 'en' 'locale': 'zh-CN' }; } void main() { runApp(const MyApp()); FxDio().register(Unit3Host()); FxDio().auth(UnitApiAuth()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: lightTheme(), home: PkgPlayerPage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked "final". final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { int _counter = 0; void _incrementCounter() { setState(() { // This call to setState tells the Flutter framework that something has // changed in this State, which causes it to rerun the build method below // so that the display can reflect the updated values. If we changed // _counter without calling setState(), then the build method would not be // called again, and so nothing would appear to happen. _counter++; }); } @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // TRY THIS: Try changing the color here to a specific color (to // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar // change color while the other colors stay the same. backgroundColor: Theme.of(context).colorScheme.inversePrimary, // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( // Column is also a layout widget. It takes a list of children and // arranges them vertically. By default, it sizes itself to fit its // children horizontally, and tries to be as tall as its parent. // // Column has various properties to control how it sizes itself and // how it positions its children. Here we use mainAxisAlignment to // center the children vertically; the main axis here is the vertical // axis because Columns are vertical (the cross axis would be // horizontal). // // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" // action in the IDE, or press "p" in the console), to see the // wireframe for each widget. mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } } ================================================ FILE: modules/tools_system/pkg_player/example/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: modules/tools_system/pkg_player/example/linux/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "example") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "com.example.example") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Load bundled libraries from the lib/ directory relative to the binary. set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Root filesystem for cross-building. if(FLUTTER_TARGET_PLATFORM_SYSROOT) set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) endif() # Define build configuration options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) # Application build; see runner/CMakeLists.txt. add_subdirectory("runner") # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endforeach(bundled_library) # Copy the native assets provided by the build.dart from all packages. set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") install(DIRECTORY "${NATIVE_ASSETS_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: modules/tools_system/pkg_player/example/linux/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: modules/tools_system/pkg_player/example/linux/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // // clang-format off #include "generated_plugin_registrant.h" void fl_register_plugins(FlPluginRegistry* registry) { } ================================================ FILE: modules/tools_system/pkg_player/example/linux/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void fl_register_plugins(FlPluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: modules/tools_system/pkg_player/example/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: modules/tools_system/pkg_player/example/linux/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) # Define the application target. To change its name, change BINARY_NAME in the # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add preprocessor definitions for the application ID. add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Add dependency libraries. Add any application-specific dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") ================================================ FILE: modules/tools_system/pkg_player/example/linux/runner/main.cc ================================================ #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: modules/tools_system/pkg_player/example/linux/runner/my_application.cc ================================================ #include "my_application.h" #include #ifdef GDK_WINDOWING_X11 #include #endif #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; char** dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); // Use a header bar when running in GNOME as this is the common style used // by applications and is the setup most users will be using (e.g. Ubuntu // desktop). // If running on X and not using GNOME then just use a traditional title bar // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 GdkScreen* screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { use_header_bar = FALSE; } } #endif if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { gtk_window_set_title(window, "example"); } gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { g_warning("Failed to register: %s", error->message); *exit_status = 1; return TRUE; } g_application_activate(application); *exit_status = 0; return TRUE; } // Implements GApplication::startup. static void my_application_startup(GApplication* application) { //MyApplication* self = MY_APPLICATION(object); // Perform any actions required at application startup. G_APPLICATION_CLASS(my_application_parent_class)->startup(application); } // Implements GApplication::shutdown. static void my_application_shutdown(GApplication* application) { //MyApplication* self = MY_APPLICATION(object); // Perform any actions required at application shutdown. G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); } // Implements GObject::dispose. static void my_application_dispose(GObject* object) { MyApplication* self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_APPLICATION_CLASS(klass)->startup = my_application_startup; G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { // Set the program name to the application ID, which helps various systems // like GTK and desktop environments map this running application to its // corresponding .desktop file. This ensures better integration by allowing // the application to be recognized beyond its binary name. g_set_prgname(APPLICATION_ID); return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, "flags", G_APPLICATION_NON_UNIQUE, nullptr)); } ================================================ FILE: modules/tools_system/pkg_player/example/linux/runner/my_application.h ================================================ #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: modules/tools_system/pkg_player/example/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/dgph **/xcuserdata/ ================================================ FILE: modules/tools_system/pkg_player/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: modules/tools_system/pkg_player/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: modules/tools_system/pkg_player/example/macos/Flutter/GeneratedPluginRegistrant.swift ================================================ // // Generated file. Do not edit. // import FlutterMacOS import Foundation import path_provider_foundation import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } ================================================ FILE: modules/tools_system/pkg_player/example/macos/Podfile ================================================ platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/AppDelegate.swift ================================================ import Cocoa import FlutterMacOS @main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { return true } } ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = com.example.example // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/MainFlutterWindow.swift ================================================ import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC10EC2044A3C60003C045; remoteInfo = Runner; }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 331C80D2294CF70F00263BE5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 331C80D6294CF71000263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( 331C80D7294CF71000263BE5 /* RunnerTests.swift */, ); path = RunnerTests; sourceTree = ""; }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 331C80D4294CF70F00263BE5 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, ); buildRules = ( ); dependencies = ( 331C80DA294CF71000263BE5 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { CreatedOnToolsVersion = 14.0; TestTargetID = 33CC10EC2044A3C60003C045; }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 331C80D3294CF70F00263BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 331C80D1294CF70F00263BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC10EC2044A3C60003C045 /* Runner */; targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; }; name = Debug; }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; }; name = Release; }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; }; name = Profile; }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 331C80DB294CF71000263BE5 /* Debug */, 331C80DC294CF71000263BE5 /* Release */, 331C80DD294CF71000263BE5 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: modules/tools_system/pkg_player/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: modules/tools_system/pkg_player/example/macos/RunnerTests/RunnerTests.swift ================================================ import Cocoa import FlutterMacOS import XCTest class RunnerTests: XCTestCase { func testExample() { // If you add code to the Runner application, consider adding tests here. // See https://developer.apple.com/documentation/xctest for more information about using XCTest. } } ================================================ FILE: modules/tools_system/pkg_player/example/pubspec.yaml ================================================ name: example description: "A new Flutter project." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. # In Android, build-name is used as versionName while build-number used as versionCode. # Read more about Android versioning at https://developer.android.com/studio/publish/versioning # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: sdk: ^3.6.0 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions # consider running `flutter pub upgrade --major-versions`. Alternatively, # dependencies can be manually updated by changing the version numbers below to # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter pkg_player: path: ../ # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^5.0.0 dependency_overrides: fx_dio: ^0.0.4+3 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images # For details regarding adding assets from package dependencies, see # https://flutter.dev/to/asset-from-package # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts from package dependencies, # see https://flutter.dev/to/font-from-package ================================================ FILE: modules/tools_system/pkg_player/example/test/widget_test.dart ================================================ // // This is a basic Flutter widget test. // // // // To perform an interaction with a widget in your test, use the WidgetTester // // utility in the flutter_test package. For example, you can send tap and scroll // // gestures. You can also use WidgetTester to find child widgets in the widget // // tree, read text, and verify that the values of widget properties are correct. // // import 'package:flutter/material.dart'; // import 'package:flutter_test/flutter_test.dart'; // // import 'package:example/main.dart'; // // void main() { // testWidgets('Counter increments smoke test', (WidgetTester tester) async { // // Build our app and trigger a frame. // await tester.pumpWidget(const MyApp()); // // // Verify that our counter starts at 0. // expect(find.text('0'), findsOneWidget); // expect(find.text('1'), findsNothing); // // // Tap the '+' icon and trigger a frame. // await tester.tap(find.byIcon(Icons.add)); // await tester.pump(); // // // Verify that our counter has incremented. // expect(find.text('0'), findsNothing); // expect(find.text('1'), findsOneWidget); // }); // } ================================================ FILE: modules/tools_system/pkg_player/example/web/index.html ================================================ example ================================================ FILE: modules/tools_system/pkg_player/example/web/manifest.json ================================================ { "name": "example", "short_name": "example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" }, { "src": "icons/Icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { "src": "icons/Icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ] } ================================================ FILE: modules/tools_system/pkg_player/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: modules/tools_system/pkg_player/example/windows/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.14) project(example LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "example") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(VERSION 3.14...3.25) # Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() # Define settings for the Profile build mode. set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build; see runner/CMakeLists.txt. add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Copy the native assets provided by the build.dart from all packages. set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") install(DIRECTORY "${NATIVE_ASSETS_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: modules/tools_system/pkg_player/example/windows/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # Set fallback configurations for older versions of the flutter tool. if (NOT DEFINED FLUTTER_TARGET_PLATFORM) set(FLUTTER_TARGET_PLATFORM "windows-x64") endif() # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" "flutter_texture_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: modules/tools_system/pkg_player/example/windows/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // // clang-format off #include "generated_plugin_registrant.h" #include void RegisterPlugins(flutter::PluginRegistry* registry) { PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); } ================================================ FILE: modules/tools_system/pkg_player/example/windows/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void RegisterPlugins(flutter::PluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: modules/tools_system/pkg_player/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST permission_handler_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) # Define the application target. To change its name, change BINARY_NAME in the # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add preprocessor definitions for the build version. target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") # Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") # Add dependency libraries and include directories. Add any application-specific # dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else #define VERSION_AS_NUMBER 1,0,0,0 #endif #if defined(FLUTTER_VERSION) #define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" VALUE "FileDescription", "example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/flutter_window.cpp ================================================ #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); // Flutter can complete the first frame before the "show window" callback is // registered. The following call ensures a frame is pending to ensure the // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/flutter_window.h ================================================ #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow hosting a Flutter view running |project|. explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/main.cpp ================================================ #include #include #include #include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.Create(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/utils.cpp ================================================ #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE *unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } unsigned int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr) -1; // remove the trailing null character int input_length = (int)wcslen(utf16_string); std::string utf8_string; if (target_length == 0 || target_length > utf8_string.max_size()) { return utf8_string; } utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, input_length, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/utils.h ================================================ #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ #include #include // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); #endif // RUNNER_UTILS_H_ ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/win32_window.cpp ================================================ #include "win32_window.h" #include #include #include "resource.h" namespace { /// Window attribute that enables dark mode window decorations. /// /// Redefined in case the developer's machine has a Windows SDK older than /// version 10.0.22000.0. /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; /// Registry key for app theme preference. /// /// A value of 0 indicates apps should use dark mode. A non-zero or missing /// value indicates apps should use light mode. constexpr const wchar_t kGetPreferredBrightnessRegKey[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); } FreeLibrary(user32_module); } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registrar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::Create(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } UpdateTheme(window); return OnCreate(); } bool Win32Window::Show() { return ShowWindow(window_handle_, SW_SHOWNORMAL); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; case WM_DWMCOLORIZATIONCOLORCHANGED: UpdateTheme(hwnd); return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } void Win32Window::UpdateTheme(HWND const window) { DWORD light_mode; DWORD light_mode_size = sizeof(light_mode); LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr, &light_mode, &light_mode_size); if (result == ERROR_SUCCESS) { BOOL enable_dark_mode = light_mode == 0; DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, &enable_dark_mode, sizeof(enable_dark_mode)); } } ================================================ FILE: modules/tools_system/pkg_player/example/windows/runner/win32_window.h ================================================ #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates a win32 window with |title| that is positioned and sized using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size this function will scale the inputted width and height as // as appropriate for the default monitor. The window is invisible until // |Show| is called. Returns true if the window was created successfully. bool Create(const std::wstring& title, const Point& origin, const Size& size); // Show the current window. Returns true if the window was successfully shown. bool Show(); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; // Update the window frame's theme to match the system theme. static void UpdateTheme(HWND const window); bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: modules/tools_system/pkg_player/fx.yaml ================================================ # L10n Checker 配置文件 l10n_check: ignore: # 忽略特定文件 - "*test.dart" - "*debug*.dart" show_checking: false # 输出到文件(支持相对路径) output_file: 'l10n_issues.json' output_format: 'json' ignore_print: true ignore_chars: - "、" ================================================ FILE: modules/tools_system/pkg_player/l10n.yaml ================================================ arb-dir: lib/src/l10n/arb template-arb-file: l10n_zh.arb output-localization-file: l10n.dart synthetic-package: false output-dir: lib/src/l10n/gen output-class: PkgL10n nullable-getter: false untranslated-messages-file: desiredFileName.txt ================================================ FILE: modules/tools_system/pkg_player/lib/pkg_player.dart ================================================ library; export 'src/view/view.dart'; export 'src/repository/repository.dart'; export 'src/bloc/bloc.dart'; export 'src/l10n/l10n.dart'; export 'package:unit_env/unit_env.dart'; ================================================ FILE: modules/tools_system/pkg_player/lib/src/bloc/bloc.dart ================================================ export 'category/category_cubit.dart'; export 'category/category_state.dart'; export 'packages/package_cubit.dart'; ================================================ FILE: modules/tools_system/pkg_player/lib/src/bloc/category/category_cubit.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../repository/api/request.dart'; import 'category_state.dart'; class CategoryCubit extends Cubit { final PackageRequest _request; CategoryCubit(this._request) : super(CategoryInitial()); Future loadCategories() async { emit(CategoryLoading()); final result = await _request.getCategories(); if (result.success) { emit(CategoryLoaded(result.data)); } else { emit(CategoryError(result.msg)); } } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/bloc/category/category_state.dart ================================================ import 'package:equatable/equatable.dart'; import '../../../pkg_player.dart'; abstract class CategoryState extends Equatable { const CategoryState(); @override List get props => []; } class CategoryInitial extends CategoryState {} class CategoryLoading extends CategoryState {} class CategoryLoaded extends CategoryState { final List categories; const CategoryLoaded(this.categories); @override List get props => [categories]; } class CategoryError extends CategoryState { final String message; const CategoryError(this.message); @override List get props => [message]; } ================================================ FILE: modules/tools_system/pkg_player/lib/src/bloc/comments/comment_replies_cubit.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../pkg_player.dart'; import 'comment_replies_state.dart'; class CommentRepliesCubit extends Cubit { final PackageRequest _request; final int commentId; List _allReplies = []; int _currentPage = 1; bool _hasMore = true; CommentRepliesCubit(this._request, this.commentId) : super(CommentRepliesInitial()); Future loadReplies({bool isRefresh = false}) async { if (isRefresh) { _currentPage = 1; _allReplies.clear(); _hasMore = true; } emit(CommentRepliesLoading()); final result = await _request.getCommentReplies(commentId, page: _currentPage); if (result.success) { if (isRefresh) { _allReplies = result.data; } else { _allReplies.addAll(result.data); } emit(CommentRepliesLoaded(_allReplies)); } else { emit(CommentRepliesError(result.msg)); } } Future loadMore() async { if (!_hasMore) return true; _currentPage++; final result = await _request.getCommentReplies(commentId, page: _currentPage); if (result.success) { final newReplies = result.data; _hasMore = newReplies.isNotEmpty; if (newReplies.isNotEmpty) { _allReplies.addAll(newReplies); emit(CommentRepliesLoaded(_allReplies)); } return !_hasMore; } return true; } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/bloc/comments/comment_replies_state.dart ================================================ import 'package:equatable/equatable.dart'; import '../../repository/model/model.dart'; abstract class CommentRepliesState extends Equatable { const CommentRepliesState(); } class CommentRepliesInitial extends CommentRepliesState { @override List get props => []; } class CommentRepliesLoading extends CommentRepliesState { @override List get props => []; } class CommentRepliesLoaded extends CommentRepliesState { final List replies; const CommentRepliesLoaded(this.replies); @override List get props => [replies]; } class CommentRepliesError extends CommentRepliesState { final String message; const CommentRepliesError(this.message); @override List get props => [message]; } ================================================ FILE: modules/tools_system/pkg_player/lib/src/bloc/comments/comments_cubit.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../pkg_player.dart'; import 'comments_state.dart'; class CommentsCubit extends Cubit { final PackageRequest _request; final int packageId; List _allComments = []; int _currentPage = 1; int _totalComments = 0; bool _hasMore = true; CommentsCubit(this._request, this.packageId) : super(CommentsInitial()); Future loadComments({bool isRefresh = false}) async { if (isRefresh) { _currentPage = 1; _allComments.clear(); _hasMore = true; } emit(CommentsLoading()); final result = await _request.getPackageComments(packageId, page: _currentPage); if (result.success) { _totalComments = result.data.total; if (isRefresh) { _allComments = result.data.data; } else { _allComments.addAll(result.data.data); } final commentsResponse = CommentsResponse( data: _allComments, total: _totalComments, ); emit(CommentsLoaded(commentsResponse)); } else { emit(CommentsError(result.msg)); } } Future loadMore() async { if (!_hasMore) return true; _currentPage++; final result = await _request.getPackageComments(packageId, page: _currentPage); if (result.success) { final newComments = result.data.data; _hasMore = newComments.isNotEmpty && _allComments.length < _totalComments; if (newComments.isNotEmpty) { _allComments.addAll(newComments); final commentsResponse = CommentsResponse( data: _allComments, total: _totalComments, ); emit(CommentsLoaded(commentsResponse)); } return !_hasMore; } return true; } Future sendComment(String content, {String? username, int? parentId}) async { final currentState = state; String guestName = username ?? UnitEnv.userName ?? '游客'; if (currentState is CommentsLoaded) { emit(CommentSending(currentState.comments)); final result = await _request.sendComment( packageId, content, guestName, parentId: parentId, ); if (result.success) { _updateCommentsInMemory(result.data, content, guestName, parentId); } else { emit(CommentsError(result.msg)); } } } void _updateCommentsInMemory( dynamic responseData, String content, String guestName, int? parentId) { final newComment = Comment( id: responseData['data'] ?? DateTime.now().millisecondsSinceEpoch, packageId: packageId, parentId: parentId, userId: responseData['user_id'], guestName: guestName, content: content, contentType: 'text', rating: null, createAt: responseData['create_at'] ?? DateTime.now().toString(), replies: [], repliesTotal: 0, ); if (parentId == null || parentId == -1) { // 新的一级评论,添加到列表开头 _allComments.insert(0, newComment); _totalComments++; } else { // 回复评论,找到父评论并添加回复 for (int i = 0; i < _allComments.length; i++) { if (_allComments[i].id == parentId) { final parentComment = _allComments[i]; final updatedReplies = [...parentComment.replies, newComment]; _allComments[i] = Comment( id: parentComment.id, packageId: parentComment.packageId, parentId: parentComment.parentId, userId: parentComment.userId, guestName: parentComment.guestName, content: parentComment.content, contentType: parentComment.contentType, rating: parentComment.rating, createAt: parentComment.createAt, replies: updatedReplies.length > 2 ? updatedReplies.sublist(0, 2) : updatedReplies, repliesTotal: parentComment.repliesTotal + 1, ); break; } } } final commentsResponse = CommentsResponse( data: _allComments, total: _totalComments, ); emit(CommentsLoaded(commentsResponse)); } void slice() { final currentState = state; if (currentState is CommentsLoaded) { List comments = currentState.comments.data; if (comments.length > 10) { emit(CommentsLoaded(CommentsResponse( data: comments.sublist(0, 10), total: currentState.comments.total, ))); } } } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/bloc/comments/comments_state.dart ================================================ import '../../repository/model/model.dart'; abstract class CommentsState {} class CommentsInitial extends CommentsState {} class CommentsLoading extends CommentsState {} class CommentsLoaded extends CommentsState { final CommentsResponse comments; CommentsLoaded(this.comments); } class CommentsError extends CommentsState { final String message; CommentsError(this.message); } class CommentSending extends CommentsState { final CommentsResponse comments; CommentSending(this.comments); } ================================================ FILE: modules/tools_system/pkg_player/lib/src/bloc/packages/package_cubit.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pkg_player/src/bloc/packages/package_state.dart'; import '../../repository/api/request.dart'; import '../../repository/model/model.dart'; class PackageCubit extends Cubit { final PackageRequest _request; final Map _categoryPackages = {}; final Set _loadingCategories = {}; PackageCubit(this._request) : super(PackageInitial()); Future loadPackagesForCategory(String categoryKey, {bool isRefresh = false, String? sortBy}) async { // 检查是否需要加载 if (!isRefresh && _categoryPackages.containsKey(categoryKey)) { return; } // 添加loading状态并立即emit if (!isRefresh) { _loadingCategories.add(categoryKey); final loadingState = PackageLoaded( _categoryPackages, loadingCategories: _loadingCategories, ); emit(loadingState); } try { final result = await _request.getCategoriesPackage( key: categoryKey, sortBy: sortBy, ); if (result.success) { _categoryPackages[categoryKey] = PackageResult( total: result.paginate?.total ?? 0, data: result.data, ); } else { _categoryPackages[categoryKey] = PackageResult.empty; } _loadingCategories.remove(categoryKey); final completedState = PackageLoaded( _categoryPackages, loadingCategories: _loadingCategories, ); emit(completedState); } catch (e) { _loadingCategories.remove(categoryKey); _categoryPackages[categoryKey] = PackageResult.empty; ; emit(PackageLoaded(_categoryPackages, loadingCategories: _loadingCategories)); } } Future loadMore(String key) async { PackageResult? lastResult = _categoryPackages[key]; List current = lastResult?.data ?? []; if (current.length == lastResult?.total) { return true; } int page = current.length ~/ 10 + 1; final result = await _request.getCategoriesPackage( key: key, page: page, pageSize: 10, ); bool noData = false; if (result.success) { int total = result.paginate?.total ?? 0; List newModels = [...current, ...result.data]; noData = total == newModels.length; _categoryPackages[key] = PackageResult(total: total, data: newModels); emit( PackageLoaded( Map.from(_categoryPackages), loadingCategories: Set.from(_loadingCategories), ), ); } else {} return noData; } void clearPackages() { _categoryPackages.clear(); _loadingCategories.clear(); emit(PackageLoaded({})); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/bloc/packages/package_state.dart ================================================ import '../../../pkg_player.dart'; abstract class PackageState { const PackageState(); bool isLoading(String key) => false; PackageResult? getResult(String key) => null; } class PackageInitial extends PackageState {} class PackageLoading extends PackageState { @override bool isLoading(String key) => true; } class PackageLoaded extends PackageState { final Map categoryPackages; final Set loadingCategories; const PackageLoaded( this.categoryPackages, { this.loadingCategories = const {}, }); @override bool isLoading(String key) => loadingCategories.contains(key); @override PackageResult? getResult(String key) { return categoryPackages[key]; } } class PackageError extends PackageState { final String message; const PackageError(this.message); } class PackageResult { final int total; final List data; PackageResult({ required this.total, required this.data, }); static PackageResult get empty => PackageResult(total: 0, data: []); } ================================================ FILE: modules/tools_system/pkg_player/lib/src/l10n/arb/l10n_en.arb ================================================ { "monthsAgo": "{diff} months ago", "yearsAgo": "{diff} years ago", "weeksAgo": "{diff} weeks ago", "daysAgo": "{diff} days ago", "hoursAgo": "{diff} hours ago", "minutesAgo": "{diff} minutes ago", "commentsOfPackage": "Comments of {packageName}", "downloadsLast30Days": "Downloads 30 days", "flutterPluginRepo": "Flutter Plugin Repository", "downloads": "Downloads", "home": "Home", "theme": "Theme", "today": "Today", "repository": "Repository:", "dependencies": "Dependencies", "saveImage": "Save Image", "saveFailed": "Save failed: {error}", "allComments": "All Comments", "others": "Others", "writeReplyHint": "Write your reply...", "writeCommentHint": "Write your comment...", "writeComment": "Write Comment", "share": "Share", "shareFeatureDeveloping": "Share feature under development...", "shareCard": "Share Card", "score": "Score", "justNow": "Just now", "loadFailed": "Load failed", "loadFailedWithMessage": "Load failed: {message}", "loadMore10": "Load more (10)", "loadingComments": "Loading comments...", "packageNameLabel": "Package Name", "packageFeatures": "Package Features", "publishTime": "Publish Time", "publisher": "Publisher", "send": "Send", "cancel": "Cancel", "like": "Like", "likeCount": "Likes Count", "likes": "Likes", "reply": "Reply", "replyToGuest": "Reply {guestName}", "replyToParent": "Reply @{parentGuestName}", "replyCommentHint": "Reply comment...", "replyDetails": "Reply Details", "copyLink": "Copy Link", "platform": "Platform", "firstCommentPrompt": "Be the first to comment", "sortMethod": "Sort Method", "recommendNotice": "After review and adoption, the package will be included in FlutterUnit plugin repository for more developers to discover and use.", "recommendSubmitted": "Recommendation submitted, thank you for your contribution!", "recommendPlugin": "Recommend Plugin", "recommendDescription": "Recommendation Description", "submitFailedWithMsg": "Submit failed: {msg}", "submitFailedNetwork": "Submit failed: Network error", "submitRecommendation": "Submit Recommendation", "plugin": "Plugin", "pluginScore": "Plugin Score", "yesterday": "Yesterday", "noReply": "No replies", "noDescription": "No description", "noData": "No data", "noComments": "No comments", "lastRelease": "Last Release", "unknownCategory": "Unknown Category", "unknownPublisher": "Unknown Publisher", "unknownUser": "Unknown User", "viewAll": "View All", "viewMoreReplies": "View {moreCount} more replies", "tagList": "Tag List", "addComment": "Add Comment", "guest": "Guest", "type": "Type", "highlightAnswer": "Highlighted Answer", "license": "License", "commentContent": "Comment Content", "describePackageFeatureHint": "Please describe package features", "describePluginForRecommend": "Please describe the main features and functionality of this package and why you recommend it...", "requestSuccess": "Request successful!", "minDescriptionLength": "Please enter at least 10 characters", "enterPackageName": "Please enter package name", "enterPackageExample": "Please enter package name, e.g., dio", "linkCopied": "Link copied to clipboard", "needGalleryPermission": "Gallery permission required to save image" } ================================================ FILE: modules/tools_system/pkg_player/lib/src/l10n/arb/l10n_zh.arb ================================================ { "monthsAgo": "{diff}个月前", "yearsAgo": "{diff}年前", "weeksAgo": "{diff}周前", "daysAgo": "{diff}天前", "hoursAgo": "{diff}小时前", "minutesAgo": "{diff}分钟前", "commentsOfPackage": "{packageName} 的评论", "downloadsLast30Days": "30 日下载量", "flutterPluginRepo": "Flutter插件库", "downloads": "下载量", "home": "主页", "theme": "主题", "today": "今天", "repository": "仓库:", "dependencies": "依赖关系", "saveImage": "保存图片", "saveFailed": "保存失败: {error}", "allComments": "全部评论", "others": "其他", "writeReplyHint": "写下你的回复...", "writeCommentHint": "写下你的评论...", "writeComment": "写评论", "share": "分享", "shareFeatureDeveloping": "分享功能开发中...", "shareCard": "分享卡片", "score": "分数", "justNow": "刚刚", "loadFailed": "加载失败", "loadFailedWithMessage": "加载失败: {message}", "loadMore10": "加载更多(10)", "loadingComments": "加载评论中...", "packageNameLabel": "包名称", "packageFeatures": "包的特点", "publishTime": "发布时间", "publisher": "发布者", "send": "发送", "cancel": "取消", "like": "喜欢", "likeCount": "喜欢人数", "likes": "喜欢数", "reply": "回复", "replyToGuest": "回复 {guestName}", "replyToParent": "回复 @{parentGuestName}", "replyCommentHint": "回复评论...", "replyDetails": "回复详情", "copyLink": "复制链接", "platform": "平台", "firstCommentPrompt": "成为第一个评论的人吧", "sortMethod": "排序方式", "recommendNotice": "推荐后,经过审核采纳,该包将收录到 FlutterUnit 插件库中,供更多开发者发现和使用。", "recommendSubmitted": "推荐已提交,感谢您的贡献!", "recommendPlugin": "推荐插件", "recommendDescription": "推荐说明", "submitFailedWithMsg": "提交失败:{msg}", "submitFailedNetwork": "提交失败:网络错误", "submitRecommendation": "提交推荐", "plugin": "插件", "pluginScore": "插件分数", "yesterday": "昨天", "noReply": "暂无回复", "noDescription": "暂无描述", "noData": "暂无数据", "noComments": "暂无评论", "lastRelease": "最后发布", "unknownCategory": "未知分类", "unknownPublisher": "未知发布者", "unknownUser": "未知用户", "viewAll": "查看所有", "viewMoreReplies": "查看更多 {moreCount} 条回复", "tagList": "标签一览", "addComment": "添加评论", "guest": "游客", "type": "类型", "highlightAnswer": "精答", "license": "许可证", "commentContent": "评论内容", "describePackageFeatureHint": "请描述包的特点", "describePluginForRecommend": "请描述这个包的主要功能和特点,为什么推荐它...", "requestSuccess": "请求成功!", "minDescriptionLength": "请至少输入10个字符的描述", "enterPackageName": "请输入包名称", "enterPackageExample": "请输入包名称,如:dio", "linkCopied": "链接已复制到剪贴板", "needGalleryPermission": "需要相册权限才能保存图片" } ================================================ FILE: modules/tools_system/pkg_player/lib/src/l10n/gen/l10n.dart ================================================ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:intl/intl.dart' as intl; import 'l10n_en.dart'; import 'l10n_zh.dart'; // ignore_for_file: type=lint /// Callers can lookup localized strings with an instance of PkgL10n /// returned by `PkgL10n.of(context)`. /// /// Applications need to include `PkgL10n.delegate()` in their app's /// `localizationDelegates` list, and the locales they support in the app's /// `supportedLocales` list. For example: /// /// ```dart /// import 'gen/l10n.dart'; /// /// return MaterialApp( /// localizationsDelegates: PkgL10n.localizationsDelegates, /// supportedLocales: PkgL10n.supportedLocales, /// home: MyApplicationHome(), /// ); /// ``` /// /// ## Update pubspec.yaml /// /// Please make sure to update your pubspec.yaml to include the following /// packages: /// /// ```yaml /// dependencies: /// # Internationalization support. /// flutter_localizations: /// sdk: flutter /// intl: any # Use the pinned version from flutter_localizations /// /// # Rest of dependencies /// ``` /// /// ## iOS Applications /// /// iOS applications define key application metadata, including supported /// locales, in an Info.plist file that is built into the application bundle. /// To configure the locales supported by your app, you’ll need to edit this /// file. /// /// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. /// Then, in the Project Navigator, open the Info.plist file under the Runner /// project’s Runner folder. /// /// Next, select the Information Property List item, select Add Item from the /// Editor menu, then select Localizations from the pop-up menu. /// /// Select and expand the newly-created Localizations item then, for each /// locale your application supports, add a new item and select the locale /// you wish to add from the pop-up menu in the Value field. This list should /// be consistent with the languages listed in the PkgL10n.supportedLocales /// property. abstract class PkgL10n { PkgL10n(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; static PkgL10n of(BuildContext context) { return Localizations.of(context, PkgL10n)!; } static const LocalizationsDelegate delegate = _PkgL10nDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. /// /// Returns a list of localizations delegates containing this delegate along with /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, /// and GlobalWidgetsLocalizations.delegate. /// /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. static const List> localizationsDelegates = >[ delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ]; /// A list of this localizations delegate's supported locales. static const List supportedLocales = [ Locale('en'), Locale('zh') ]; /// No description provided for @monthsAgo. /// /// In zh, this message translates to: /// **'{diff}个月前'** String monthsAgo(Object diff); /// No description provided for @yearsAgo. /// /// In zh, this message translates to: /// **'{diff}年前'** String yearsAgo(Object diff); /// No description provided for @weeksAgo. /// /// In zh, this message translates to: /// **'{diff}周前'** String weeksAgo(Object diff); /// No description provided for @daysAgo. /// /// In zh, this message translates to: /// **'{diff}天前'** String daysAgo(Object diff); /// No description provided for @hoursAgo. /// /// In zh, this message translates to: /// **'{diff}小时前'** String hoursAgo(Object diff); /// No description provided for @minutesAgo. /// /// In zh, this message translates to: /// **'{diff}分钟前'** String minutesAgo(Object diff); /// No description provided for @commentsOfPackage. /// /// In zh, this message translates to: /// **'{packageName} 的评论'** String commentsOfPackage(Object packageName); /// No description provided for @downloadsLast30Days. /// /// In zh, this message translates to: /// **'30 日下载量'** String get downloadsLast30Days; /// No description provided for @flutterPluginRepo. /// /// In zh, this message translates to: /// **'Flutter插件库'** String get flutterPluginRepo; /// No description provided for @downloads. /// /// In zh, this message translates to: /// **'下载量'** String get downloads; /// No description provided for @home. /// /// In zh, this message translates to: /// **'主页'** String get home; /// No description provided for @theme. /// /// In zh, this message translates to: /// **'主题'** String get theme; /// No description provided for @today. /// /// In zh, this message translates to: /// **'今天'** String get today; /// No description provided for @repository. /// /// In zh, this message translates to: /// **'仓库:'** String get repository; /// No description provided for @dependencies. /// /// In zh, this message translates to: /// **'依赖关系'** String get dependencies; /// No description provided for @saveImage. /// /// In zh, this message translates to: /// **'保存图片'** String get saveImage; /// No description provided for @saveFailed. /// /// In zh, this message translates to: /// **'保存失败: {error}'** String saveFailed(Object error); /// No description provided for @allComments. /// /// In zh, this message translates to: /// **'全部评论'** String get allComments; /// No description provided for @others. /// /// In zh, this message translates to: /// **'其他'** String get others; /// No description provided for @writeReplyHint. /// /// In zh, this message translates to: /// **'写下你的回复...'** String get writeReplyHint; /// No description provided for @writeCommentHint. /// /// In zh, this message translates to: /// **'写下你的评论...'** String get writeCommentHint; /// No description provided for @writeComment. /// /// In zh, this message translates to: /// **'写评论'** String get writeComment; /// No description provided for @share. /// /// In zh, this message translates to: /// **'分享'** String get share; /// No description provided for @shareFeatureDeveloping. /// /// In zh, this message translates to: /// **'分享功能开发中...'** String get shareFeatureDeveloping; /// No description provided for @shareCard. /// /// In zh, this message translates to: /// **'分享卡片'** String get shareCard; /// No description provided for @score. /// /// In zh, this message translates to: /// **'分数'** String get score; /// No description provided for @justNow. /// /// In zh, this message translates to: /// **'刚刚'** String get justNow; /// No description provided for @loadFailed. /// /// In zh, this message translates to: /// **'加载失败'** String get loadFailed; /// No description provided for @loadFailedWithMessage. /// /// In zh, this message translates to: /// **'加载失败: {message}'** String loadFailedWithMessage(Object message); /// No description provided for @loadMore10. /// /// In zh, this message translates to: /// **'加载更多(10)'** String get loadMore10; /// No description provided for @loadingComments. /// /// In zh, this message translates to: /// **'加载评论中...'** String get loadingComments; /// No description provided for @packageNameLabel. /// /// In zh, this message translates to: /// **'包名称'** String get packageNameLabel; /// No description provided for @packageFeatures. /// /// In zh, this message translates to: /// **'包的特点'** String get packageFeatures; /// No description provided for @publishTime. /// /// In zh, this message translates to: /// **'发布时间'** String get publishTime; /// No description provided for @publisher. /// /// In zh, this message translates to: /// **'发布者'** String get publisher; /// No description provided for @send. /// /// In zh, this message translates to: /// **'发送'** String get send; /// No description provided for @cancel. /// /// In zh, this message translates to: /// **'取消'** String get cancel; /// No description provided for @like. /// /// In zh, this message translates to: /// **'喜欢'** String get like; /// No description provided for @likeCount. /// /// In zh, this message translates to: /// **'喜欢人数'** String get likeCount; /// No description provided for @likes. /// /// In zh, this message translates to: /// **'喜欢数'** String get likes; /// No description provided for @reply. /// /// In zh, this message translates to: /// **'回复'** String get reply; /// No description provided for @replyToGuest. /// /// In zh, this message translates to: /// **'回复 {guestName}'** String replyToGuest(Object guestName); /// No description provided for @replyToParent. /// /// In zh, this message translates to: /// **'回复 @{parentGuestName}'** String replyToParent(Object parentGuestName); /// No description provided for @replyCommentHint. /// /// In zh, this message translates to: /// **'回复评论...'** String get replyCommentHint; /// No description provided for @replyDetails. /// /// In zh, this message translates to: /// **'回复详情'** String get replyDetails; /// No description provided for @copyLink. /// /// In zh, this message translates to: /// **'复制链接'** String get copyLink; /// No description provided for @platform. /// /// In zh, this message translates to: /// **'平台'** String get platform; /// No description provided for @firstCommentPrompt. /// /// In zh, this message translates to: /// **'成为第一个评论的人吧'** String get firstCommentPrompt; /// No description provided for @sortMethod. /// /// In zh, this message translates to: /// **'排序方式'** String get sortMethod; /// No description provided for @recommendNotice. /// /// In zh, this message translates to: /// **'推荐后,经过审核采纳,该包将收录到 FlutterUnit 插件库中,供更多开发者发现和使用。'** String get recommendNotice; /// No description provided for @recommendSubmitted. /// /// In zh, this message translates to: /// **'推荐已提交,感谢您的贡献!'** String get recommendSubmitted; /// No description provided for @recommendPlugin. /// /// In zh, this message translates to: /// **'推荐插件'** String get recommendPlugin; /// No description provided for @recommendDescription. /// /// In zh, this message translates to: /// **'推荐说明'** String get recommendDescription; /// No description provided for @submitFailedWithMsg. /// /// In zh, this message translates to: /// **'提交失败:{msg}'** String submitFailedWithMsg(Object msg); /// No description provided for @submitFailedNetwork. /// /// In zh, this message translates to: /// **'提交失败:网络错误'** String get submitFailedNetwork; /// No description provided for @submitRecommendation. /// /// In zh, this message translates to: /// **'提交推荐'** String get submitRecommendation; /// No description provided for @plugin. /// /// In zh, this message translates to: /// **'插件'** String get plugin; /// No description provided for @pluginScore. /// /// In zh, this message translates to: /// **'插件分数'** String get pluginScore; /// No description provided for @yesterday. /// /// In zh, this message translates to: /// **'昨天'** String get yesterday; /// No description provided for @noReply. /// /// In zh, this message translates to: /// **'暂无回复'** String get noReply; /// No description provided for @noDescription. /// /// In zh, this message translates to: /// **'暂无描述'** String get noDescription; /// No description provided for @noData. /// /// In zh, this message translates to: /// **'暂无数据'** String get noData; /// No description provided for @noComments. /// /// In zh, this message translates to: /// **'暂无评论'** String get noComments; /// No description provided for @lastRelease. /// /// In zh, this message translates to: /// **'最后发布'** String get lastRelease; /// No description provided for @unknownCategory. /// /// In zh, this message translates to: /// **'未知分类'** String get unknownCategory; /// No description provided for @unknownPublisher. /// /// In zh, this message translates to: /// **'未知发布者'** String get unknownPublisher; /// No description provided for @unknownUser. /// /// In zh, this message translates to: /// **'未知用户'** String get unknownUser; /// No description provided for @viewAll. /// /// In zh, this message translates to: /// **'查看所有'** String get viewAll; /// No description provided for @viewMoreReplies. /// /// In zh, this message translates to: /// **'查看更多 {moreCount} 条回复'** String viewMoreReplies(Object moreCount); /// No description provided for @tagList. /// /// In zh, this message translates to: /// **'标签一览'** String get tagList; /// No description provided for @addComment. /// /// In zh, this message translates to: /// **'添加评论'** String get addComment; /// No description provided for @guest. /// /// In zh, this message translates to: /// **'游客'** String get guest; /// No description provided for @type. /// /// In zh, this message translates to: /// **'类型'** String get type; /// No description provided for @highlightAnswer. /// /// In zh, this message translates to: /// **'精答'** String get highlightAnswer; /// No description provided for @license. /// /// In zh, this message translates to: /// **'许可证'** String get license; /// No description provided for @commentContent. /// /// In zh, this message translates to: /// **'评论内容'** String get commentContent; /// No description provided for @describePackageFeatureHint. /// /// In zh, this message translates to: /// **'请描述包的特点'** String get describePackageFeatureHint; /// No description provided for @describePluginForRecommend. /// /// In zh, this message translates to: /// **'请描述这个包的主要功能和特点,为什么推荐它...'** String get describePluginForRecommend; /// No description provided for @requestSuccess. /// /// In zh, this message translates to: /// **'请求成功!'** String get requestSuccess; /// No description provided for @minDescriptionLength. /// /// In zh, this message translates to: /// **'请至少输入10个字符的描述'** String get minDescriptionLength; /// No description provided for @enterPackageName. /// /// In zh, this message translates to: /// **'请输入包名称'** String get enterPackageName; /// No description provided for @enterPackageExample. /// /// In zh, this message translates to: /// **'请输入包名称,如:dio'** String get enterPackageExample; /// No description provided for @linkCopied. /// /// In zh, this message translates to: /// **'链接已复制到剪贴板'** String get linkCopied; /// No description provided for @needGalleryPermission. /// /// In zh, this message translates to: /// **'需要相册权限才能保存图片'** String get needGalleryPermission; } class _PkgL10nDelegate extends LocalizationsDelegate { const _PkgL10nDelegate(); @override Future load(Locale locale) { return SynchronousFuture(lookupPkgL10n(locale)); } @override bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode); @override bool shouldReload(_PkgL10nDelegate old) => false; } PkgL10n lookupPkgL10n(Locale locale) { // Lookup logic when only language code is specified. switch (locale.languageCode) { case 'en': return PkgL10nEn(); case 'zh': return PkgL10nZh(); } throw FlutterError( 'PkgL10n.delegate failed to load unsupported locale "$locale". This is likely ' 'an issue with the localizations generation tool. Please file an issue ' 'on GitHub with a reproducible sample app and the gen-l10n configuration ' 'that was used.' ); } ================================================ FILE: modules/tools_system/pkg_player/lib/src/l10n/gen/l10n_en.dart ================================================ import 'l10n.dart'; // ignore_for_file: type=lint /// The translations for English (`en`). class PkgL10nEn extends PkgL10n { PkgL10nEn([String locale = 'en']) : super(locale); @override String monthsAgo(Object diff) { return '$diff months ago'; } @override String yearsAgo(Object diff) { return '$diff years ago'; } @override String weeksAgo(Object diff) { return '$diff weeks ago'; } @override String daysAgo(Object diff) { return '$diff days ago'; } @override String hoursAgo(Object diff) { return '$diff hours ago'; } @override String minutesAgo(Object diff) { return '$diff minutes ago'; } @override String commentsOfPackage(Object packageName) { return 'Comments of $packageName'; } @override String get downloadsLast30Days => 'Downloads in last 30 days'; @override String get flutterPluginRepo => 'Flutter Plugin Repository'; @override String get downloads => 'Downloads'; @override String get home => 'Home'; @override String get theme => 'Theme'; @override String get today => 'Today'; @override String get repository => 'Repository:'; @override String get dependencies => 'Dependencies'; @override String get saveImage => 'Save Image'; @override String saveFailed(Object error) { return 'Save failed: $error'; } @override String get allComments => 'All Comments'; @override String get others => 'Others'; @override String get writeReplyHint => 'Write your reply...'; @override String get writeCommentHint => 'Write your comment...'; @override String get writeComment => 'Write Comment'; @override String get share => 'Share'; @override String get shareFeatureDeveloping => 'Share feature under development...'; @override String get shareCard => 'Share Card'; @override String get score => 'Score'; @override String get justNow => 'Just now'; @override String get loadFailed => 'Load failed'; @override String loadFailedWithMessage(Object message) { return 'Load failed: $message'; } @override String get loadMore10 => 'Load more (10)'; @override String get loadingComments => 'Loading comments...'; @override String get packageNameLabel => 'Package Name'; @override String get packageFeatures => 'Package Features'; @override String get publishTime => 'Publish Time'; @override String get publisher => 'Publisher'; @override String get send => 'Send'; @override String get cancel => 'Cancel'; @override String get like => 'Like'; @override String get likeCount => 'Likes Count'; @override String get likes => 'Likes'; @override String get reply => 'Reply'; @override String replyToGuest(Object guestName) { return 'Reply $guestName'; } @override String replyToParent(Object parentGuestName) { return 'Reply @$parentGuestName'; } @override String get replyCommentHint => 'Reply comment...'; @override String get replyDetails => 'Reply Details'; @override String get copyLink => 'Copy Link'; @override String get platform => 'Platform'; @override String get firstCommentPrompt => 'Be the first to comment'; @override String get sortMethod => 'Sort Method'; @override String get recommendNotice => 'After review and adoption, the package will be included in FlutterUnit plugin repository for more developers to discover and use.'; @override String get recommendSubmitted => 'Recommendation submitted, thank you for your contribution!'; @override String get recommendPlugin => 'Recommend Plugin'; @override String get recommendDescription => 'Recommendation Description'; @override String submitFailedWithMsg(Object msg) { return 'Submit failed: $msg'; } @override String get submitFailedNetwork => 'Submit failed: Network error'; @override String get submitRecommendation => 'Submit Recommendation'; @override String get plugin => 'Plugin'; @override String get pluginScore => 'Plugin Score'; @override String get yesterday => 'Yesterday'; @override String get noReply => 'No replies'; @override String get noDescription => 'No description'; @override String get noData => 'No data'; @override String get noComments => 'No comments'; @override String get lastRelease => 'Last Release'; @override String get unknownCategory => 'Unknown Category'; @override String get unknownPublisher => 'Unknown Publisher'; @override String get unknownUser => 'Unknown User'; @override String get viewAll => 'View All'; @override String viewMoreReplies(Object moreCount) { return 'View $moreCount more replies'; } @override String get tagList => 'Tag List'; @override String get addComment => 'Add Comment'; @override String get guest => 'Guest'; @override String get type => 'Type'; @override String get highlightAnswer => 'Highlighted Answer'; @override String get license => 'License'; @override String get commentContent => 'Comment Content'; @override String get describePackageFeatureHint => 'Please describe package features'; @override String get describePluginForRecommend => 'Please describe the main features and functionality of this package and why you recommend it...'; @override String get requestSuccess => 'Request successful!'; @override String get minDescriptionLength => 'Please enter at least 10 characters'; @override String get enterPackageName => 'Please enter package name'; @override String get enterPackageExample => 'Please enter package name, e.g., dio'; @override String get linkCopied => 'Link copied to clipboard'; @override String get needGalleryPermission => 'Gallery permission required to save image'; } ================================================ FILE: modules/tools_system/pkg_player/lib/src/l10n/gen/l10n_zh.dart ================================================ import 'l10n.dart'; // ignore_for_file: type=lint /// The translations for Chinese (`zh`). class PkgL10nZh extends PkgL10n { PkgL10nZh([String locale = 'zh']) : super(locale); @override String monthsAgo(Object diff) { return '$diff个月前'; } @override String yearsAgo(Object diff) { return '$diff年前'; } @override String weeksAgo(Object diff) { return '$diff周前'; } @override String daysAgo(Object diff) { return '$diff天前'; } @override String hoursAgo(Object diff) { return '$diff小时前'; } @override String minutesAgo(Object diff) { return '$diff分钟前'; } @override String commentsOfPackage(Object packageName) { return '$packageName 的评论'; } @override String get downloadsLast30Days => '30 日下载量'; @override String get flutterPluginRepo => 'Flutter插件库'; @override String get downloads => '下载量'; @override String get home => '主页'; @override String get theme => '主题'; @override String get today => '今天'; @override String get repository => '仓库:'; @override String get dependencies => '依赖关系'; @override String get saveImage => '保存图片'; @override String saveFailed(Object error) { return '保存失败: $error'; } @override String get allComments => '全部评论'; @override String get others => '其他'; @override String get writeReplyHint => '写下你的回复...'; @override String get writeCommentHint => '写下你的评论...'; @override String get writeComment => '写评论'; @override String get share => '分享'; @override String get shareFeatureDeveloping => '分享功能开发中...'; @override String get shareCard => '分享卡片'; @override String get score => '分数'; @override String get justNow => '刚刚'; @override String get loadFailed => '加载失败'; @override String loadFailedWithMessage(Object message) { return '加载失败: $message'; } @override String get loadMore10 => '加载更多(10)'; @override String get loadingComments => '加载评论中...'; @override String get packageNameLabel => '包名称'; @override String get packageFeatures => '包的特点'; @override String get publishTime => '发布时间'; @override String get publisher => '发布者'; @override String get send => '发送'; @override String get cancel => '取消'; @override String get like => '喜欢'; @override String get likeCount => '喜欢人数'; @override String get likes => '喜欢数'; @override String get reply => '回复'; @override String replyToGuest(Object guestName) { return '回复 $guestName'; } @override String replyToParent(Object parentGuestName) { return '回复 @$parentGuestName'; } @override String get replyCommentHint => '回复评论...'; @override String get replyDetails => '回复详情'; @override String get copyLink => '复制链接'; @override String get platform => '平台'; @override String get firstCommentPrompt => '成为第一个评论的人吧'; @override String get sortMethod => '排序方式'; @override String get recommendNotice => '推荐后,经过审核采纳,该包将收录到 FlutterUnit 插件库中,供更多开发者发现和使用。'; @override String get recommendSubmitted => '推荐已提交,感谢您的贡献!'; @override String get recommendPlugin => '推荐插件'; @override String get recommendDescription => '推荐说明'; @override String submitFailedWithMsg(Object msg) { return '提交失败:$msg'; } @override String get submitFailedNetwork => '提交失败:网络错误'; @override String get submitRecommendation => '提交推荐'; @override String get plugin => '插件'; @override String get pluginScore => '插件分数'; @override String get yesterday => '昨天'; @override String get noReply => '暂无回复'; @override String get noDescription => '暂无描述'; @override String get noData => '暂无数据'; @override String get noComments => '暂无评论'; @override String get lastRelease => '最后发布'; @override String get unknownCategory => '未知分类'; @override String get unknownPublisher => '未知发布者'; @override String get unknownUser => '未知用户'; @override String get viewAll => '查看所有'; @override String viewMoreReplies(Object moreCount) { return '查看更多 $moreCount 条回复'; } @override String get tagList => '标签一览'; @override String get addComment => '添加评论'; @override String get guest => '游客'; @override String get type => '类型'; @override String get highlightAnswer => '精答'; @override String get license => '许可证'; @override String get commentContent => '评论内容'; @override String get describePackageFeatureHint => '请描述包的特点'; @override String get describePluginForRecommend => '请描述这个包的主要功能和特点,为什么推荐它...'; @override String get requestSuccess => '请求成功!'; @override String get minDescriptionLength => '请至少输入10个字符的描述'; @override String get enterPackageName => '请输入包名称'; @override String get enterPackageExample => '请输入包名称,如:dio'; @override String get linkCopied => '链接已复制到剪贴板'; @override String get needGalleryPermission => '需要相册权限才能保存图片'; } ================================================ FILE: modules/tools_system/pkg_player/lib/src/l10n/l10n.dart ================================================ import 'package:flutter/cupertino.dart'; import 'gen/l10n.dart'; export 'gen/l10n.dart' show PkgL10n; extension PkGL10nExt on BuildContext { PkgL10n get pkgL10n { PkgL10n? app = Localizations.of(this, PkgL10n); return app!; } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/api/request.dart ================================================ import 'package:unit_env/unit_env.dart'; import '../model/model.dart'; class PackageRequest with ScienceHostMixin { Future> getAllPackages() async { return host.get(PkgUrl.listPackages.path, convertor: (e) => e); } Future> insertPackage(Map data) async { return host.post('/packages/import', data: data, convertor: (e) => e); } Future> deletePackages(String name) async { return host.delete('/packages/$name', convertor: (e) => e); } Future> addCategories({ required String key, required String name, required String? desc, }) async { return addCategoriesRaw({ 'key': key, 'name': name, 'description': desc ?? '', }); } Future>> getCategoriesPackage({ required String key, int page = 1, int pageSize = 10, String? sortBy, }) async { return host.get('/categories/$key/export', queryParameters: { 'sort_by': sortBy ?? 'downloads', 'page': page, 'page_size': pageSize, }, convertor: (data) { List list = data['data'] as List; return list.map((json) => PluginModel.fromJson(json)).toList(); }); } Future> addCategoriesRaw(Map data) async { return host.post('/categories', data: data, convertor: (e) => e); } Future> addToCategories( String categoryKey, List packageNames, ) async { return host.post('/packages/add_to_category', data: { "category_key": categoryKey, "package_names": packageNames, }, convertor: (e) => e); } Future>> getCategories() async { return host.get>('/categories', queryParameters: { 'page': 1, 'page_size': 100, }, convertor: (data) { List list = data['data'] as List; dynamic value = list.map((json) => Category.fromJson(json)).toList(); return value; }); } Future> getPackageComments( int packageId, { int page = 1, int pageSize = 10, }) async { return host.get('/packages/$packageId/comments', queryParameters: { 'page': page, 'page_size': pageSize, }, convertor: (data) => CommentsResponse.fromJson(data)); } Future> sendComment( int packageId, String content, String guestName, {int? parentId}) async { Map data = { 'content': content, 'guest_name': guestName, }; if (parentId != null && parentId != -1) { data['parent_id'] = parentId; } return host.post('/packages/$packageId/comments', data: data, convertor: (e) => e); } Future>> getCommentReplies( int commentId, { int page = 1, int pageSize = 15, }) async { return host.get('/comments/$commentId/replies', queryParameters: { 'page': page, 'page_size': pageSize, }, convertor: (data) { List list = data['data'] as List; return list.map((json) => Comment.fromJson(json)).toList(); }); } Future> submitFeedback({ required String feedbackType, required String title, required String content, }) async { return host.post('/feedback', data: { 'feedback_type': feedbackType, 'title': title, 'content': content, }, convertor: (e) => e); } Future insertPackages(String category, List jsonData) async { List packageNames = []; for (dynamic data in jsonData) { print("insertPackages:${data['name']}"); await insertPackage(data); packageNames.add(data['name']); } ApiRet ret = await addToCategories(category, packageNames); if (ret.success) { print(ret.data); } } } enum PkgUrl { listPackages("/packages"), ; final String path; final Method? method; const PkgUrl(this.path, [this.method = Method.get]); } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/api/url.dart ================================================ import 'package:fx_dio/fx_dio.dart'; ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/database/dao/category_dao.dart ================================================ import 'package:pkg_player/pkg_player.dart'; import 'package:sqflite/sqflite.dart'; class CategoryDao { Future insert(String key, String name, String description) async { final db = await PkgDatabaseHelper().database; return await db.insert('t_pkg_categories', { 'key': key, 'name': name, 'description': description, }); } Future>> getAll() async { final db = await PkgDatabaseHelper().database; return await db.query('t_pkg_categories'); } Future?> getByKey(String key) async { final db = await PkgDatabaseHelper().database; final result = await db.query('t_pkg_categories', where: 'key = ?', whereArgs: [key]); return result.isNotEmpty ? result.first : null; } Future update(int id, String key, String name, String description) async { final db = await PkgDatabaseHelper().database; return await db.update('t_pkg_categories', { 'key': key, 'name': name, 'description': description, }, where: 'id = ?', whereArgs: [id]); } Future delete(int id) async { final db = await PkgDatabaseHelper().database; return await db.delete('t_pkg_categories', where: 'id = ?', whereArgs: [id]); } Future deleteAll() async { final db = await PkgDatabaseHelper().database; await db.delete('t_pkg_categories'); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/database/dao/dao.dart ================================================ // TODO Implement this library. ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/database/dao/package_dao.dart ================================================ import 'package:pkg_player/pkg_player.dart'; import 'package:sqflite/sqflite.dart'; class PackageDao { Future insert(Map package) async { final db = await PkgDatabaseHelper().database; return await db.insert('t_pkg_packages', package); } Future>> getAll() async { final db = await PkgDatabaseHelper().database; return await db.query('t_pkg_packages'); } Future?> getByName(String name) async { final db = await PkgDatabaseHelper().database; final result = await db.query('t_pkg_packages', where: 'name = ?', whereArgs: [name]); return result.isNotEmpty ? result.first : null; } Future>> getByCategory(int categoryId) async { final db = await PkgDatabaseHelper().database; return await db.rawQuery(''' SELECT p.* FROM t_pkg_packages p JOIN t_pkg_package_categories pc ON p.id = pc.package_id WHERE pc.category_id = ? ''', [categoryId]); } Future update(int id, Map package) async { final db = await PkgDatabaseHelper().database; return await db .update('t_pkg_packages', package, where: 'id = ?', whereArgs: [id]); } Future delete(int id) async { final db = await PkgDatabaseHelper().database; return await db.delete('t_pkg_packages', where: 'id = ?', whereArgs: [id]); } Future deleteAll() async { final db = await PkgDatabaseHelper().database; await db.delete('t_pkg_packages'); } Future insertTags(int packageId, List tags) async { final db = await PkgDatabaseHelper().database; final batch = db.batch(); for (String tag in tags) { // 先获取或创建标签ID final result = await db.query('t_pkg_tags', where: 'name = ?', whereArgs: [tag]); int tagId; if (result.isNotEmpty) { tagId = result.first['id'] as int; } else { tagId = await db.insert('t_pkg_tags', {'name': tag}); } batch.insert( 't_pkg_package_tags', {'package_id': packageId, 'tag_id': tagId}); } await batch.commit(); } Future insertTopics(int packageId, List topics) async { final db = await PkgDatabaseHelper().database; final batch = db.batch(); for (String topic in topics) { // 先获取或创建主题ID final result = await db.query('t_pkg_topics', where: 'name = ?', whereArgs: [topic]); int topicId; if (result.isNotEmpty) { topicId = result.first['id'] as int; } else { topicId = await db.insert('t_pkg_topics', {'name': topic}); } batch.insert('t_pkg_package_topics', {'package_id': packageId, 'topic_id': topicId}); } await batch.commit(); } Future insertDependencies( int packageId, Map dependencies) async { final db = await PkgDatabaseHelper().database; final batch = db.batch(); dependencies.forEach((name, version) { batch.insert('t_pkg_package_dependencies', { 'package_id': packageId, 'dependency_name': name, 'version_constraint': version?.toString() ?? '', }); }); await batch.commit(); } Future> getTags(int packageId) async { final db = await PkgDatabaseHelper().database; final result = await db.rawQuery(''' SELECT t.name FROM t_pkg_tags t JOIN t_pkg_package_tags pt ON t.id = pt.tag_id WHERE pt.package_id = ? ''', [packageId]); return result.map((row) => row['name'] as String).toList(); } Future> getTopics(int packageId) async { final db = await PkgDatabaseHelper().database; final result = await db.rawQuery(''' SELECT t.name FROM t_pkg_topics t JOIN t_pkg_package_topics pt ON t.id = pt.topic_id WHERE pt.package_id = ? ''', [packageId]); return result.map((row) => row['name'] as String).toList(); } Future> getDependencies(int packageId) async { final db = await PkgDatabaseHelper().database; final result = await db.query('t_pkg_package_dependencies', where: 'package_id = ?', whereArgs: [packageId]); return Map.fromEntries(result.map((row) => MapEntry( row['dependency_name'] as String, row['version_constraint'] as String))); } Future insertPackageCategory(int packageId, int categoryId) async { final db = await PkgDatabaseHelper().database; await db.insert('t_pkg_package_categories', { 'package_id': packageId, 'category_id': categoryId, }); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/database/dao/package_service.dart ================================================ import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:pkg_player/pkg_player.dart'; import 'package_dao.dart'; import 'category_dao.dart'; import 'tag_dao.dart'; import 'topic_dao.dart'; class PackageService { final PackageDao _packageDao = PackageDao(); final CategoryDao _categoryDao = CategoryDao(); final TagDao _tagDao = TagDao(); final TopicDao _topicDao = TopicDao(); Future savePluginModel(PluginModel plugin, String categoryKey) async { // 获取或创建分类 Map? category = await _categoryDao.getByKey(categoryKey); if (category == null) { // 从category.json获取分类信息并创建 final categoryInfo = await _getCategoryInfo(categoryKey); final categoryId = await _categoryDao.insert( categoryKey, categoryInfo['name']!, categoryInfo['desc']!); category = {'id': categoryId}; } // 插入包信息 final packageId = await _packageDao.insert({ 'name': plugin.name, 'last_version': plugin.lastVersion, 'last_publish': plugin.lastPublish, 'likes': plugin.statistics.likes, 'points': plugin.statistics.points, 'downloads': plugin.statistics.downloads, 'description': plugin.desc, 'publisher': plugin.publisher, 'repository': plugin.repository ?? '', 'homepage': plugin.repository ?? '', }); // 插入包分类关联 await _packageDao.insertPackageCategory(packageId, category['id']); // 插入标签 if (plugin.tags.isNotEmpty) { await _packageDao.insertTags(packageId, plugin.tags); } // 插入主题 if (plugin.topics.isNotEmpty) { await _packageDao.insertTopics(packageId, plugin.topics); } // 插入依赖 if (plugin.dependencies != null && plugin.dependencies!.isNotEmpty) { await _packageDao.insertDependencies(packageId, plugin.dependencies!); } } Future savePluginModels( List plugins, String categoryKey) async { for (PluginModel plugin in plugins) { try { await savePluginModel(plugin, categoryKey); } catch (e, s) { print('Failed to save plugin ${plugin.name}: $e,$s'); } } } Future> _getCategoryInfo(String categoryKey) async { try { String jsonString = await rootBundle.loadString('assets/test/category.json'); Map categories = json.decode(jsonString); if (categories.containsKey(categoryKey)) { return { 'name': categories[categoryKey]['name'], 'desc': categories[categoryKey]['desc'], }; } } catch (e) { print('Failed to load category info: $e'); } // 默认值 return { 'name': categoryKey, 'desc': '未知分类', }; } Future clearAllData() async { await _packageDao.deleteAll(); await _categoryDao.deleteAll(); await _tagDao.deleteAll(); await _topicDao.deleteAll(); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/database/dao/tag_dao.dart ================================================ import 'package:pkg_player/pkg_player.dart'; import 'package:sqflite/sqflite.dart'; class TagDao { Future insertOrGet(String tagName) async { final db = await PkgDatabaseHelper().database; // 先查询是否存在 final result = await db.query('t_pkg_tags', where: 'name = ?', whereArgs: [tagName]); if (result.isNotEmpty) { return result.first['id'] as int; } // 不存在则插入 return await db.insert('t_pkg_tags', {'name': tagName}); } Future>> getAll() async { final db = await PkgDatabaseHelper().database; return await db.query('t_pkg_tags'); } Future deleteAll() async { final db = await PkgDatabaseHelper().database; await db.delete('t_pkg_tags'); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/database/dao/topic_dao.dart ================================================ import 'package:pkg_player/pkg_player.dart'; import 'package:sqflite/sqflite.dart'; class TopicDao { Future insertOrGet(String topicName) async { final db = await PkgDatabaseHelper().database; // 先查询是否存在 final result = await db .query('t_pkg_topics', where: 'name = ?', whereArgs: [topicName]); if (result.isNotEmpty) { return result.first['id'] as int; } // 不存在则插入 return await db.insert('t_pkg_topics', {'name': topicName}); } Future>> getAll() async { final db = await PkgDatabaseHelper().database; return await db.query('t_pkg_topics'); } Future deleteAll() async { final db = await PkgDatabaseHelper().database; await db.delete('t_pkg_topics'); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/database/database.dart ================================================ export 'database_helper.dart'; export 'dao/dao.dart'; ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/database/database_helper.dart ================================================ import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; class PkgDatabaseHelper { static final PkgDatabaseHelper _instance = PkgDatabaseHelper._internal(); static Database? _database; PkgDatabaseHelper._internal(); factory PkgDatabaseHelper() => _instance; Future get database async { _database ??= await _initDatabase(); return _database!; } String? _dbPath; void setDbPath(String path) { _dbPath = path; } Future _initDatabase() async { assert(_dbPath != null); return await openDatabase( _dbPath!, version: 1, onCreate: _onCreate, ); } Future _onCreate(Database db, int version) async { await db.execute(''' CREATE TABLE t_pkg_categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT UNIQUE NOT NULL, name TEXT NOT NULL, description TEXT NOT NULL ) '''); await db.execute(''' CREATE TABLE t_pkg_tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL ) '''); await db.execute(''' CREATE TABLE t_pkg_topics ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL ) '''); await db.execute(''' CREATE TABLE t_pkg_packages ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, last_version TEXT, last_publish TEXT, likes INTEGER DEFAULT 0, points INTEGER DEFAULT 0, downloads INTEGER DEFAULT 0, description TEXT, publisher TEXT, repository TEXT, homepage TEXT ) '''); await db.execute(''' CREATE TABLE t_pkg_package_categories ( package_id INTEGER NOT NULL, category_id INTEGER NOT NULL, PRIMARY KEY (package_id, category_id) ) '''); await db.execute(''' CREATE TABLE t_pkg_package_tags ( package_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY (package_id, tag_id) ) '''); await db.execute(''' CREATE TABLE t_pkg_package_topics ( package_id INTEGER NOT NULL, topic_id INTEGER NOT NULL, PRIMARY KEY (package_id, topic_id) ) '''); await db.execute(''' CREATE TABLE t_pkg_package_dependencies ( package_id INTEGER NOT NULL, dependency_name TEXT NOT NULL, version_constraint TEXT, PRIMARY KEY (package_id, dependency_name) ) '''); await db.execute( 'CREATE INDEX idx_t_pkg_packages_name ON t_pkg_packages(name)'); await db.execute('CREATE INDEX idx_t_pkg_tags_name ON t_pkg_tags(name)'); await db .execute('CREATE INDEX idx_t_pkg_topics_name ON t_pkg_topics(name)'); await db.execute( 'CREATE INDEX idx_t_pkg_package_categories_package ON t_pkg_package_categories(package_id)'); await db.execute( 'CREATE INDEX idx_t_pkg_package_categories_category ON t_pkg_package_categories(category_id)'); } Future> getTables() async { final db = await database; final result = await db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'"); return result.map((row) => row['name'] as String).toList(); } Future>> getTableData(String tableName) async { final db = await database; return await db.query(tableName); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/model/category_model.dart ================================================ class Category { final int id; final String key; final String name; final String description; Category({ required this.id, required this.key, required this.name, required this.description, }); factory Category.fromJson(Map json) { return Category( id: json['id'], key: json['key'], name: json['name'], description: json['description'] ?? '', ); } String label(bool isZh) { if (isZh) return name; return key.replaceAll('_', ' '); } Map toJson() { return { 'id': id, 'key': key, 'name': name, 'description': description, }; } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/model/comment_model.dart ================================================ class Comment { final int id; final int packageId; final int? parentId; final int? userId; final String guestName; final String content; final String contentType; final int? rating; final String createAt; final List replies; final int repliesTotal; Comment({ required this.id, required this.packageId, this.parentId, required this.userId, required this.guestName, required this.content, required this.contentType, this.rating, required this.createAt, required this.replies, required this.repliesTotal, }); factory Comment.fromJson(Map json) { return Comment( id: json['id'], packageId: json['package_id'], parentId: json['parent_id'], userId: json['user_id'], guestName: json['guest_name'], content: json['content'], contentType: json['content_type'], rating: json['rating'], createAt: json['create_at'], replies: (json['replies'] as List?) ?.map((e) => Comment.fromJson(e)) .toList() ?? [], repliesTotal: json['replies_total'] ?? 0, ); } Map toJson() { return { 'id': id, 'package_id': packageId, 'parent_id': parentId, 'user_id': userId, 'guest_name': guestName, 'content': content, 'content_type': contentType, 'rating': rating, 'create_at': createAt, 'replies': replies.map((e) => e.toJson()).toList(), 'replies_total': repliesTotal, }; } } class CommentsResponse { final List data; final int total; CommentsResponse({ required this.data, required this.total, }); factory CommentsResponse.fromJson(Map json) { return CommentsResponse( data: (json['data'] as List) .map((e) => Comment.fromJson(e)) .toList(), total: json['total'], ); } Map toJson() { return { 'data': data.map((e) => e.toJson()).toList(), 'total': total, }; } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/model/model.dart ================================================ export 'plugin_model.dart'; export 'category_model.dart'; export 'comment_model.dart'; export 'sort_type.dart'; ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/model/plugin_model.dart ================================================ class PluginModel { final int? id; final String name; final String lastVersion; final String lastPublish; final Statistics statistics; final List tags; final Map? dependencies; List get platforms => tags.where((e) => e.startsWith('platform:')).toList(); List get sdks => tags.where((e) => e.startsWith('sdk:')).toList(); List get license => tags.where((e) => e.startsWith('license:')).toList(); final String? desc; final String? publisher; final String? repository; final String? homepage; final List topics; PluginModel({ this.id, required this.name, required this.lastVersion, required this.lastPublish, required this.statistics, required this.tags, required this.desc, required this.publisher, required this.repository, required this.homepage, required this.topics, required this.dependencies, }); factory PluginModel.fromJson(Map json) { return PluginModel( id: json['id'], name: json['name'] ?? '', lastVersion: json['last_version'] ?? '', lastPublish: json['last_publish'] ?? '', statistics: Statistics.fromJson(json['statistics'] ?? {}), tags: List.from(json['tags'] ?? []), desc: json['desc'] ?? '', publisher: json['publisher'] ?? '', repository: json['repository'], homepage: json['homepage'], dependencies: json['dependencies'], topics: List.from(json['topics'] ?? []), ); } bool get isFavorite => tags.contains('is:flutter-favorite'); Map toJson() { return { 'id': id, 'name': name, 'last_version': lastVersion, 'last_publish': lastPublish, 'statistics': statistics.toJson(), 'tags': tags, 'desc': desc, 'dependencies': dependencies, 'publisher': publisher, 'homepage': homepage, 'repository': repository, 'topics': topics, }; } } class Statistics { final int likes; final int points; final int downloads; Statistics({ required this.likes, required this.points, required this.downloads, }); factory Statistics.fromJson(Map json) { return Statistics( likes: json['likes'] ?? '', points: json['points'] ?? '', downloads: json['downloads'] ?? '', ); } Map toJson() { return { 'likes': likes, 'points': points, 'downloads': downloads, }; } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/model/sort_type.dart ================================================ import 'package:flutter/material.dart'; import 'package:pkg_player/src/l10n/l10n.dart'; enum SortType { downloads('downloads'), likes('likes'), publishTime('publish_time'); const SortType(this.value); final String value; String getLabel(BuildContext context) { switch (this) { case SortType.downloads: return context.pkgL10n.downloads; case SortType.likes: return context.pkgL10n.likes; case SortType.publishTime: return context.pkgL10n.publishTime; } } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/repository/repository.dart ================================================ export 'database/database.dart'; export 'model/model.dart'; export 'api/request.dart'; ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/comments/comment_replies_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pkg_player/pkg_player.dart'; import 'package:tolyui_refresh/tolyui_refresh.dart'; import '../../bloc/comments/comment_replies_cubit.dart'; import '../../bloc/comments/comment_replies_state.dart'; import '../home/plugin_item.dart'; class CommentRepliesPage extends StatefulWidget { final Comment parentComment; const CommentRepliesPage({ Key? key, required this.parentComment, }) : super(key: key); @override _CommentRepliesPageState createState() => _CommentRepliesPageState(); } class _CommentRepliesPageState extends State { final RefreshController _refreshController = RefreshController(); late PkgL10n l10n; @override void didChangeDependencies() { super.didChangeDependencies(); l10n = context.pkgL10n; } @override Widget build(BuildContext context) { Color? tileColor = Theme.of(context).listTileTheme.tileColor; return Scaffold( appBar: AppBar( title: Text(l10n.replyDetails), ), body: Column( children: [ _buildParentComment(tileColor), Expanded( child: BlocBuilder( builder: (context, state) { if (state is CommentRepliesLoading) { return Center(child: CircularProgressIndicator()); } if (state is CommentRepliesError) { return Center( child: Text(l10n.loadFailedWithMessage(state.message))); } if (state is CommentRepliesLoaded) { if (state.replies.isEmpty) { return TolyRefresh( controller: _refreshController, onRefresh: _onRefresh, child: SingleChildScrollView( physics: AlwaysScrollableScrollPhysics(), child: Container( height: MediaQuery.of(context).size.height - 300, child: Center(child: Text(l10n.noReply)), ), ), ); } return TolyRefresh( controller: _refreshController, enablePullUp: true, enablePullDown: false, onRefresh: _onRefresh, onLoading: _onLoading, child: ListView.separated( padding: EdgeInsets.all(16), itemCount: state.replies.length, separatorBuilder: (context, index) => SizedBox(height: 12), itemBuilder: (context, index) { final reply = state.replies[index]; return _buildReplyItem(reply, tileColor); }, ), ); } return Center(child: Text(l10n.noComments)); }, ), ), ], ), // floatingActionButton: FloatingActionButton( // onPressed: () => _showAddReplyDialog(context), // child: Icon(Icons.reply), // ), ); } Widget _buildParentComment(Color? color) { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: color, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildAvatar(widget.parentComment.guestName), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( widget.parentComment.guestName, style: TextStyle( fontWeight: FontWeight.w600, fontSize: 14, color: Colors.grey[800], ), ), SizedBox(width: 8), Text( formatDate(widget.parentComment.createAt, l10n), style: TextStyle( color: Colors.grey[500], fontSize: 12, ), ), ], ), SizedBox(height: 6), Text( widget.parentComment.content, style: TextStyle( fontSize: 14, height: 1.4, color: Colors.grey[700], ), ), ], ), ), ], ), ); } Widget _buildReplyItem(Comment reply, Color? color) { return Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(8), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: _getAvatarColor(reply.guestName).withOpacity(0.2), borderRadius: BorderRadius.circular(16), ), child: Center( child: Text( reply.guestName.isNotEmpty ? reply.guestName[0].toUpperCase() : 'U', style: TextStyle( color: _getAvatarColor(reply.guestName), fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( reply.guestName, style: TextStyle( fontWeight: FontWeight.w500, fontSize: 13, color: Colors.grey[700], ), ), SizedBox(width: 8), Text( formatDate(reply.createAt, l10n), style: TextStyle( color: Colors.grey[500], fontSize: 11, ), ), ], ), SizedBox(height: 4), Text( reply.content, style: TextStyle( fontSize: 13, height: 1.3, color: Colors.grey[600], ), ), ], ), ), ], ), ); } Widget _buildAvatar(String name) { return Container( width: 36, height: 36, decoration: BoxDecoration( gradient: LinearGradient( colors: [ _getAvatarColor(name).withOpacity(0.8), _getAvatarColor(name), ], ), borderRadius: BorderRadius.circular(18), ), child: Center( child: Text( name.isNotEmpty ? name[0].toUpperCase() : 'U', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ); } Color _getAvatarColor(String name) { final colors = [ Colors.blue, Colors.green, Colors.orange, Colors.purple, Colors.teal, Colors.pink, Colors.indigo, ]; return colors[name.hashCode % colors.length]; } void _onRefresh() async { await context.read().loadReplies(isRefresh: true); _refreshController.refreshCompleted(); } void _onLoading() async { final noMore = await context.read().loadMore(); if (noMore) { _refreshController.loadNoData(); } else { _refreshController.loadComplete(); } } void _showAddReplyDialog(BuildContext context) { final controller = TextEditingController(); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( '回复 @${widget.parentComment.guestName}', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, ), ), Spacer(), IconButton( onPressed: () => Navigator.pop(context), icon: Icon(Icons.close, size: 20), padding: EdgeInsets.zero, constraints: BoxConstraints(), ), ], ), SizedBox(height: 16), Container( decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: TextField( controller: controller, autofocus: true, decoration: InputDecoration( hintText: '写下你的回复...', border: InputBorder.none, contentPadding: EdgeInsets.all(16), hintStyle: TextStyle(color: Colors.grey[500]), ), maxLines: 4, minLines: 3, ), ), SizedBox(height: 20), Row( children: [ Expanded( child: OutlinedButton( onPressed: () => Navigator.pop(context), style: OutlinedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('取消'), ), ), SizedBox(width: 12), Expanded( child: ElevatedButton( onPressed: () { if (controller.text.trim().isNotEmpty) { // TODO: 实现发送回复 Navigator.pop(context); } }, style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('发送'), ), ), ], ), ], ), ), ), ); } @override void dispose() { _refreshController.dispose(); super.dispose(); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/comments/comments_detail_page.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pkg_player/pkg_player.dart'; import 'package:tolyui_refresh/tolyui_refresh.dart'; import '../../bloc/comments/comment_replies_cubit.dart'; import '../../bloc/comments/comments_cubit.dart'; import '../../bloc/comments/comments_state.dart'; import '../../l10n/l10n.dart'; import '../home/plugin_item.dart'; import 'comment_replies_page.dart'; typedef OnCommentAction = void Function(String content, [int? commentId]); class CommentsDetailPage extends StatefulWidget { final int packageId; final String packageName; final OnCommentAction onCommentAction; const CommentsDetailPage({ Key? key, required this.packageId, required this.packageName, required this.onCommentAction, }) : super(key: key); @override _CommentsDetailPageState createState() => _CommentsDetailPageState(); } class _CommentsDetailPageState extends State { final RefreshController _refreshController = RefreshController(); @override Widget build(BuildContext context) { Color? tileColor = Theme.of(context).listTileTheme.tileColor; PkgL10n l10n = context.pkgL10n; return Scaffold( appBar: AppBar( title: Text(l10n.commentsOfPackage(widget.packageName)), ), body: BlocBuilder( builder: (context, state) { if (state is CommentsLoading) { return Center(child: CircularProgressIndicator()); } if (state is CommentsError) { return Center( child: Text(l10n.loadFailedWithMessage(state.message))); } if (state is CommentsLoaded || state is CommentSending) { final comments = state is CommentsLoaded ? state.comments : (state as CommentSending).comments; if (comments.data.isEmpty) { return TolyRefresh( controller: _refreshController, onRefresh: _onRefresh, child: SingleChildScrollView( physics: AlwaysScrollableScrollPhysics(), child: Container( height: MediaQuery.of(context).size.height - 200, child: Center(child: Text(l10n.noComments)), ), ), ); } return TolyRefresh( controller: _refreshController, enablePullUp: true, enablePullDown: false, onRefresh: _onRefresh, onLoading: _onLoading, child: ListView.separated( padding: EdgeInsets.all(12), itemCount: comments.data.length, separatorBuilder: (context, index) => SizedBox(height: 8), itemBuilder: (context, index) { final comment = comments.data[index]; return _buildCommentCard(comment, tileColor, l10n); }, ), ); } return Center(child: Text(l10n.noComments)); }, ), floatingActionButton: FloatingActionButton( onPressed: () => _showAddCommentDialog(context), child: Icon(Icons.add), ), ); } void _onRefresh() async { await context.read().loadComments(isRefresh: true); _refreshController.refreshCompleted(); } void _onLoading() async { final noMore = await context.read().loadMore(); if (noMore) { _refreshController.loadNoData(); } else { _refreshController.loadComplete(); } } @override void dispose() { _refreshController.dispose(); super.dispose(); } Widget _buildCommentCard(Comment comment, Color? color, PkgL10n l10n) { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildAvatar(comment.guestName), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( comment.guestName, style: TextStyle( fontWeight: FontWeight.w600, fontSize: 14, color: Colors.grey[800], ), ), SizedBox(width: 8), Text( formatDate(comment.createAt, l10n), style: TextStyle( color: Colors.grey[500], fontSize: 12, ), ), Spacer(), GestureDetector( onTap: () => _showReplyDialog(context, comment), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.reply, size: 16, color: Colors.grey[600]), SizedBox(width: 4), Text( l10n.reply, style: TextStyle( color: Colors.grey[600], fontSize: 12, ), ), ], ), ), ], ), SizedBox(height: 6), Text( comment.content, style: TextStyle( fontSize: 14, height: 1.4, color: Colors.grey[700], ), ), ], ), ), ], ), if (comment.replies.isNotEmpty) ...[ SizedBox(height: 12), _buildReplies(comment, color, l10n), ], if (comment.repliesTotal > 2) ...[ SizedBox(height: 8), GestureDetector( onTap: () => _onViewMoreReplies(context, comment), child: Text( l10n.viewMoreReplies(comment.repliesTotal - 2), style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 12, ), ), ), ], ], ), ); } Widget _buildAvatar(String name) { return Container( width: 36, height: 36, decoration: BoxDecoration( gradient: LinearGradient( colors: [ _getAvatarColor(name).withOpacity(0.8), _getAvatarColor(name), ], ), borderRadius: BorderRadius.circular(18), ), child: Center( child: Text( name.isNotEmpty ? name[0].toUpperCase() : 'U', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ); } Widget _buildReplies(Comment comment, Color? color, PkgL10n l10n) { return Container( margin: EdgeInsets.only(left: 48), padding: EdgeInsets.all(12), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.withValues(alpha: 0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...comment.replies .map((reply) => _buildReplyItem(reply, l10n)) .toList(), ], ), ); } Widget _buildReplyItem(Comment reply, PkgL10n l10n) { return Padding( padding: EdgeInsets.only(bottom: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 24, height: 24, decoration: BoxDecoration( color: _getAvatarColor(reply.guestName).withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( reply.guestName.isNotEmpty ? reply.guestName[0].toUpperCase() : 'U', style: TextStyle( color: _getAvatarColor(reply.guestName), fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ), SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( reply.guestName, style: TextStyle( fontWeight: FontWeight.w500, fontSize: 12, color: Colors.grey[700], ), ), SizedBox(width: 6), Text( formatDate(reply.createAt, l10n), style: TextStyle( color: Colors.grey[500], fontSize: 10, ), ), ], ), SizedBox(height: 2), Text( reply.content, style: TextStyle( fontSize: 12, height: 1.3, color: Colors.grey[600], ), ), ], ), ), ], ), ); } Color _getAvatarColor(String name) { final colors = [ Colors.blue, Colors.green, Colors.orange, Colors.purple, Colors.teal, Colors.pink, Colors.indigo, ]; return colors[name.hashCode % colors.length]; } void _showAddCommentDialog(BuildContext context) { final controller = TextEditingController(); Color? tileColor = Theme.of(context).listTileTheme.tileColor; showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (ctx) => Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(ctx).viewInsets.bottom, ), child: Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( color: tileColor, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( context.pkgL10n.addComment, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, ), ), Spacer(), IconButton( onPressed: () => Navigator.pop(context), icon: Icon(Icons.close, size: 20), padding: EdgeInsets.zero, constraints: BoxConstraints(), ), ], ), SizedBox(height: 16), Container( decoration: BoxDecoration( color: tileColor, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: TextField( controller: controller, autofocus: true, decoration: InputDecoration( hintText: context.pkgL10n.writeCommentHint, border: InputBorder.none, contentPadding: EdgeInsets.all(16), hintStyle: TextStyle(color: Colors.grey[500]), ), maxLines: 4, minLines: 3, ), ), SizedBox(height: 20), Row( children: [ Expanded( child: OutlinedButton( onPressed: () => Navigator.pop(ctx), style: OutlinedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('取消'), ), ), SizedBox(width: 12), Expanded( child: ElevatedButton( onPressed: () { if (controller.text.trim().isNotEmpty) { context .read() .sendComment(controller.text.trim()); Navigator.pop(ctx); } }, style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('发送'), ), ), ], ), ], ), ), ), ); } void _showReplyDialog(BuildContext context, Comment comment) { final controller = TextEditingController(); Color? tileColor = Theme.of(context).listTileTheme.tileColor; showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (ctx) => Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(ctx).viewInsets.bottom, ), child: Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( color: tileColor, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( context.pkgL10n.replyToGuest(comment.guestName), style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, ), ), Spacer(), IconButton( onPressed: () => Navigator.pop(context), icon: Icon(Icons.close, size: 20), padding: EdgeInsets.zero, constraints: BoxConstraints(), ), ], ), SizedBox(height: 16), Container( decoration: BoxDecoration( color: tileColor, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: TextField( controller: controller, autofocus: true, decoration: InputDecoration( hintText: context.pkgL10n.writeReplyHint, border: InputBorder.none, contentPadding: EdgeInsets.all(16), hintStyle: TextStyle(color: Colors.grey[500]), ), maxLines: 4, minLines: 3, ), ), SizedBox(height: 20), Row( children: [ Expanded( child: OutlinedButton( onPressed: () => Navigator.pop(ctx), style: OutlinedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('取消'), ), ), SizedBox(width: 12), Expanded( child: ElevatedButton( onPressed: () { if (controller.text.trim().isNotEmpty) { context.read().sendComment( controller.text.trim(), parentId: comment.id); Navigator.pop(ctx); } }, style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('发送'), ), ), ], ), ], ), ), ), ); } void _onViewMoreReplies(BuildContext context, Comment comment) { Navigator.push( context, MaterialPageRoute( builder: (context) => BlocProvider( create: (context) => CommentRepliesCubit(PackageRequest(), comment.id)..loadReplies(), child: CommentRepliesPage( parentComment: comment, ), ), ), ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/comments/comments_empty.dart ================================================ import 'package:flutter/material.dart'; class CommentsEmptyView extends StatelessWidget { const CommentsEmptyView({super.key}); @override Widget build(BuildContext context) { Color? tileColor = Theme.of(context).listTileTheme.tileColor; return Container( height: 140, color: tileColor, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.chat_bubble_outline, color: Colors.grey[400], size: 32), SizedBox(height: 8), Text('暂无评论', style: TextStyle(color: Colors.grey[600])), Text('成为第一个评论的人吧', style: TextStyle(color: Colors.grey[500], fontSize: 12)), ], ), ), ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/comments/comments_error.dart ================================================ import 'package:flutter/material.dart'; class CommentsErrorView extends StatelessWidget { final String message; const CommentsErrorView({super.key, required this.message}); @override Widget build(BuildContext context) { return Container( height: 120, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, color: Colors.red[400], size: 32), SizedBox(height: 8), Text('加载失败', style: TextStyle(color: Colors.grey[600])), Text(message, style: TextStyle(color: Colors.grey[500], fontSize: 12)), ], ), ), ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/comments/comments_loading.dart ================================================ import 'package:flutter/material.dart'; class CommentsLoadingView extends StatelessWidget { const CommentsLoadingView({super.key}); @override Widget build(BuildContext context) { Color? tileColor = Theme.of(context).listTileTheme.tileColor; return Container( color: tileColor, height: 120, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2), ), SizedBox(height: 8), Text('加载评论中...', style: TextStyle(color: Colors.grey[600], fontSize: 12)), ], ), ), ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/comments/comments_top_bar.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pkg_player/pkg_player.dart'; import '../../bloc/comments/comments_cubit.dart'; import '../../bloc/comments/comments_state.dart'; class CommentsTopBar extends StatelessWidget { final VoidCallback onTapComment; const CommentsTopBar({ super.key, required this.onTapComment, }); @override Widget build(BuildContext context) { int total = context.select((CommentsCubit bloc) { CommentsState state = bloc.state; if (state is CommentsLoaded) { return state.comments.total; } return -1; }); Color? tileColor = Theme.of(context).listTileTheme.tileColor; return Container( color: tileColor, padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12), child: Row( children: [ Text(context.pkgL10n.allComments, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), SizedBox(width: 8), if (total > 0) Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: Theme.of(context).primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( '$total', style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.w500, ), ), ), Spacer(), GestureDetector( onTap: onTapComment, child: Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(16), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.edit, color: Colors.white, size: 14), SizedBox(width: 4), Text( context.pkgL10n.writeComment, style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500, ), ), ], ), ), ), ], ), ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/comments/comments_with_data.dart ================================================ import 'package:flutter/material.dart'; import 'package:pkg_player/pkg_player.dart'; import '../home/plugin_item.dart'; typedef OnReplay = void Function(int commentId); class SliverCommentsWithData extends StatelessWidget { final List comments; final VoidCallback onViewAll; final int total; final OnReplay onReplay; final ValueChanged onViewMoreDetail; const SliverCommentsWithData({ super.key, required this.comments, required this.onReplay, required this.total, required this.onViewAll, required this.onViewMoreDetail, }); @override Widget build(BuildContext context) { Color color = Theme.of(context).primaryColor; bool hasMore = total > 10; int length = comments.length; Color? tileColor = Theme.of(context).listTileTheme.tileColor; PkgL10n l10n = context.pkgL10n; return DecoratedSliver( decoration: BoxDecoration(color: tileColor), sliver: SliverPadding( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), sliver: SliverList.separated( separatorBuilder: (_, __) => SizedBox(height: 10), itemCount: hasMore ? length + 1 : length, itemBuilder: (_, index) { if (hasMore && index == length) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: ElevatedButton.icon( style: ElevatedButton.styleFrom( foregroundColor: Colors.white, backgroundColor: color), onPressed: onViewAll, icon: Icon( Icons.view_kanban, color: Colors.white, ), label: Text( l10n.viewAll, style: TextStyle( fontWeight: FontWeight.bold, ), )), ); } return CommentItemView( l10n: l10n, comment: comments[index], theme: color, onReplay: onReplay, onViewMoreDetail: onViewMoreDetail, ); }), ), ); } } class CommentItemView extends StatelessWidget { final Comment comment; final Color theme; final OnReplay onReplay; final PkgL10n l10n; final ValueChanged onViewMoreDetail; const CommentItemView({ super.key, required this.comment, required this.theme, required this.l10n, required this.onReplay, required this.onViewMoreDetail, }); @override Widget build(BuildContext context) { Color? tileColor = Theme.of(context).listTileTheme.tileColor; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildAvatar(comment.guestName), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( comment.guestName, style: TextStyle( fontWeight: FontWeight.w500, fontSize: 14, color: Colors.grey[600], ), ), ), if (comment.rating == 100) Container( padding: EdgeInsets.symmetric( horizontal: 12, vertical: 4), decoration: BoxDecoration( color: Colors.blue.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(4)), child: Text( l10n.highlightAnswer, style: TextStyle(fontSize: 12, color: Colors.white), )) ], ), SizedBox(height: 6), Text( comment.content, style: TextStyle( fontSize: 14, height: 1.4, color: Colors.grey[700], ), ), SizedBox(height: 6), Row( children: [ Text( formatDate(comment.createAt, l10n), style: TextStyle( color: Colors.grey[500], fontSize: 12, ), ), Spacer(), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => onReplay(comment.id), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( l10n.reply, style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 12, ), ), ), ), ], ), ], ), ), ], ), if (comment.replies.isNotEmpty) ...[ SizedBox(height: 12), _buildReplies(comment, tileColor), ], ], ); } Widget _buildAvatar(String name) { return Container( width: 32, height: 32, decoration: BoxDecoration( gradient: LinearGradient( colors: [ _getAvatarColor(name).withOpacity(0.8), _getAvatarColor(name), ], ), borderRadius: BorderRadius.circular(18), ), child: Center( child: Text( name.isNotEmpty ? name[0].toUpperCase() : 'U', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ); } Color _getAvatarColor(String name) { final colors = [ Colors.blue, Colors.green, Colors.orange, Colors.purple, Colors.teal, Colors.pink, Colors.indigo, ]; return colors[name.hashCode % colors.length]; } Widget _buildReplies(Comment comment, Color? tileColor) { final displayReplies = comment.replies.take(2).toList(); final hasMore = comment.repliesTotal > 2; return Container( margin: EdgeInsets.only(left: 36), padding: EdgeInsets.all(12), decoration: BoxDecoration( color: tileColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.withValues(alpha: 0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...displayReplies.asMap().entries.map((entry) { final index = entry.key; final reply = entry.value; return Column( children: [ _buildReplyItem(reply), if (index < displayReplies.length - 1) SizedBox(height: 8), ], ); }).toList(), if (hasMore) ...[ SizedBox(height: 8), GestureDetector( onTap: () { onViewMoreDetail(comment.id); }, child: Text( l10n.viewMoreReplies(comment.repliesTotal - 2), style: TextStyle( color: theme, fontSize: 12, ), ), ), ], ], ), ); } Widget _buildReplyItem(Comment reply) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 24, height: 24, decoration: BoxDecoration( color: _getAvatarColor(reply.guestName).withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( reply.guestName.isNotEmpty ? reply.guestName[0].toUpperCase() : 'U', style: TextStyle( color: _getAvatarColor(reply.guestName), fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ), SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( reply.guestName, style: TextStyle( fontWeight: FontWeight.w500, fontSize: 12, color: Colors.grey[700], ), ), SizedBox(width: 6), Text( formatDate(reply.createAt, l10n), style: TextStyle( color: Colors.grey[500], fontSize: 10, ), ), ], ), SizedBox(height: 2), Text( reply.content, style: TextStyle( fontSize: 12, height: 1.3, color: Colors.grey[600], ), ), ], ), ), ], ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/components/card/plugin_card.dart ================================================ import 'package:flutter/material.dart'; import 'package:pkg_player/pkg_player.dart'; import 'dart:math' as math; class PluginCard extends StatefulWidget { final PluginModel plugin; const PluginCard({Key? key, required this.plugin}) : super(key: key); @override _PluginCardState createState() => _PluginCardState(); } class _PluginCardState extends State with TickerProviderStateMixin { late AnimationController _controller; late AnimationController _glowController; late Animation _rotationAnimation; late Animation _glowAnimation; @override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 20), vsync: this, )..repeat(); _glowController = AnimationController( duration: Duration(seconds: 3), vsync: this, )..repeat(reverse: true); _rotationAnimation = Tween(begin: 0, end: 2 * math.pi).animate(_controller); _glowAnimation = Tween(begin: 0.3, end: 1.0).animate( CurvedAnimation(parent: _glowController, curve: Curves.easeInOut), ); } @override void dispose() { _controller.dispose(); _glowController.dispose(); super.dispose(); } List _getGradientColors() { final colorSets = [ [Color(0xFF667eea), Color(0xFF764ba2)], // 紫蓝渐变 [Color(0xFFf093fb), Color(0xFFf5576c)], // 粉红渐变 [Color(0xFF4facfe), Color(0xFF00f2fe)], // 蓝青渐变 [Color(0xFF43e97b), Color(0xFF38f9d7)], // 绿青渐变 [Color(0xFFfa709a), Color(0xFFfee140)], // 粉黄渐变 [Color(0xFFa8edea), Color(0xFFfed6e3)], // 青粉渐变 [Color(0xFFff9a9e), Color(0xFFfecfef)], // 珊瑚渐变 [Color(0xFF667eea), Color(0xFF764ba2)], // 深紫渐变 ]; final hash = widget.plugin.name.hashCode.abs(); return colorSets[hash % colorSets.length]; } String _formatNumber(int number) { if (number >= 1000000) { return '${(number / 1000000).toStringAsFixed(1)}M'; } else if (number >= 1000) { return '${(number / 1000).toStringAsFixed(1)}K'; } return number.toString(); } @override Widget build(BuildContext context) { final gradientColors = _getGradientColors(); return AnimatedBuilder( animation: Listenable.merge([_controller, _glowController]), builder: (context, child) { return Container( width: 350, height: 500, child: Stack( children: [ // 外层发光效果 AnimatedBuilder( animation: _glowAnimation, builder: (context, child) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: gradientColors[0].withOpacity(0.4 * _glowAnimation.value), blurRadius: 30 * _glowAnimation.value, spreadRadius: 5 * _glowAnimation.value, ), BoxShadow( color: gradientColors[1].withOpacity(0.3 * _glowAnimation.value), blurRadius: 50 * _glowAnimation.value, spreadRadius: 2 * _glowAnimation.value, ), ], ), ); }, ), // 主卡片 Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: gradientColors, stops: [0.0, 1.0], ), ), ), // 旋转的装饰环 Positioned.fill( child: AnimatedBuilder( animation: _rotationAnimation, builder: (context, child) { return Transform.rotate( angle: _rotationAnimation.value, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), border: Border.all( color: Colors.white.withOpacity(0.1), width: 2, ), ), child: Stack( children: [ // 旋转光点 Positioned( top: 20, right: 20, child: Container( width: 8, height: 8, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withOpacity(0.8), boxShadow: [ BoxShadow( color: Colors.white.withOpacity(0.5), blurRadius: 10, spreadRadius: 2, ), ], ), ), ), ], ), ), ); }, ), ), // 几何装饰 Positioned( top: -30, right: -30, child: Transform.rotate( angle: _rotationAnimation.value * 0.5, child: Container( width: 120, height: 120, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ Colors.white.withOpacity(0.15), Colors.white.withOpacity(0.05), Colors.transparent, ], ), ), ), ), ), // 底部装饰 Positioned( bottom: -40, left: -40, child: Transform.rotate( angle: -_rotationAnimation.value * 0.3, child: Container( width: 100, height: 100, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: LinearGradient( colors: [ Colors.white.withOpacity(0.1), Colors.transparent, ], ), ), ), ), ), // 闪烁粒子效果 ...List.generate(5, (index) { final angle = (index * 2 * math.pi / 5) + _rotationAnimation.value * 2; final radius = 80 + math.sin(_rotationAnimation.value * 3 + index) * 20; return Positioned( left: 175 + math.cos(angle) * radius, top: 250 + math.sin(angle) * radius, child: Container( width: 3, height: 3, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withOpacity(0.6 + 0.4 * math.sin(_rotationAnimation.value * 4 + index)), boxShadow: [ BoxShadow( color: Colors.white.withOpacity(0.3), blurRadius: 5, ), ], ), ), ); }), // 内容 Padding( padding: EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 头部 Row( children: [ Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.25), borderRadius: BorderRadius.circular(16), border: Border.all( color: Colors.white.withOpacity(0.3), width: 1, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: Offset(0, 5), ), ], ), child: ShaderMask( shaderCallback: (bounds) => LinearGradient( colors: [Colors.white, Colors.white70], ).createShader(bounds), child: Icon( Icons.extension, color: Colors.white, size: 32, ), ), ), Spacer(), Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.white.withOpacity(0.3), Colors.white.withOpacity(0.1), ], ), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withOpacity(0.4), width: 1, ), ), child: Text( 'v${widget.plugin.lastVersion}', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12, shadows: [ Shadow( color: Colors.black.withOpacity(0.3), offset: Offset(1, 1), blurRadius: 2, ), ], ), ), ), ], ), SizedBox(height: 24), // 包名 ShaderMask( shaderCallback: (bounds) => LinearGradient( colors: [Colors.white, Colors.white70], begin: Alignment.topLeft, end: Alignment.bottomRight, ).createShader(bounds), child: Text( widget.plugin.name, style: TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.w900, height: 1.2, letterSpacing: -0.5, shadows: [ Shadow( color: Colors.black.withOpacity(0.3), offset: Offset(2, 2), blurRadius: 4, ), ], ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), SizedBox(height: 16), // 描述 Text( widget.plugin.desc ?? '暂无描述', style: TextStyle( color: Colors.white.withOpacity(0.95), fontSize: 14, height: 1.5, fontWeight: FontWeight.w400, shadows: [ Shadow( color: Colors.black.withOpacity(0.2), offset: Offset(1, 1), blurRadius: 2, ), ], ), maxLines: 3, overflow: TextOverflow.ellipsis, ), Spacer(), // 统计数据 Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.white.withOpacity(0.25), Colors.white.withOpacity(0.1), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withOpacity(0.3), width: 1.5, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 15, offset: Offset(0, 8), ), ], ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildStatItem(Icons.favorite, _formatNumber(widget.plugin.statistics.likes)), _buildStatItem(Icons.star, _formatNumber(widget.plugin.statistics.points)), _buildStatItem(Icons.download, _formatNumber(widget.plugin.statistics.downloads)), ], ), ), SizedBox(height: 16), // 标签 if (widget.plugin.tags.isNotEmpty) ...[ Wrap( spacing: 8, runSpacing: 8, children: widget.plugin.tags.take(3).map((tag) => Container( padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.white.withOpacity(0.3), Colors.white.withOpacity(0.15), ], ), borderRadius: BorderRadius.circular(15), border: Border.all( color: Colors.white.withOpacity(0.4), width: 1, ), ), child: Text( tag, style: TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.w600, shadows: [ Shadow( color: Colors.black.withOpacity(0.2), offset: Offset(1, 1), blurRadius: 1, ), ], ), ), )).toList(), ), SizedBox(height: 16), ], // 发布者信息 Row( children: [ Icon(Icons.person, color: Colors.white.withOpacity(0.9), size: 16), SizedBox(width: 8), Expanded( child: Text( widget.plugin.publisher ?? '未知发布者', style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 12, fontWeight: FontWeight.w500, shadows: [ Shadow( color: Colors.black.withOpacity(0.2), offset: Offset(1, 1), blurRadius: 1, ), ], ), overflow: TextOverflow.ellipsis, ), ), ], ), ], ), ), // Flutter logo 水印 Positioned( bottom: 16, right: 16, child: Transform.rotate( angle: math.sin(_rotationAnimation.value * 2) * 0.1, child: Opacity( opacity: 0.15 + 0.05 * math.sin(_rotationAnimation.value * 3), child: FlutterLogo( size: 40, style: FlutterLogoStyle.stacked, textColor: Colors.white, ), ), ), ), ], ), ); }, ); } Widget _buildStatItem(IconData icon, String value) { return Column( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: Colors.white, size: 20), SizedBox(height: 4), Text( value, style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14, ), ), ], ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/components/card/plugin_card_page.dart ================================================ import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; // import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:pkg_player/pkg_player.dart'; import 'plugin_card.dart'; class PluginCardPage extends StatelessWidget { final PluginModel plugin; final GlobalKey _cardKey = GlobalKey(); PluginCardPage({Key? key, required this.plugin}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[100], appBar: AppBar( title: Text('分享卡片'), backgroundColor: Colors.transparent, elevation: 0, actions: [ IconButton( icon: Icon(Icons.share), onPressed: () => _shareCard(context), ), ], ), body: Center( child: SingleChildScrollView( padding: EdgeInsets.all(20), child: Column( children: [ // 卡片预览 RepaintBoundary( key: _cardKey, child: Hero( tag: 'plugin_card_${plugin.name}', child: ClipRRect( borderRadius: BorderRadius.circular(24), child: PluginCard(plugin: plugin)), ), ), SizedBox(height: 32), // 操作按钮 Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildActionButton( context, icon: Icons.download, label: '保存图片', onTap: () => _saveCard(context), ), _buildActionButton( context, icon: Icons.share, label: '分享', onTap: () => _shareCard(context), ), _buildActionButton( context, icon: Icons.copy, label: '复制链接', onTap: () => _copyLink(context), ), ], ), ], ), ), ), ); } Widget _buildActionButton( BuildContext context, { required IconData icon, required String label, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Container( padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 8, offset: Offset(0, 2), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: Theme.of(context).primaryColor), SizedBox(height: 4), Text( label, style: TextStyle( fontSize: 12, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ], ), ), ); } Future _saveCard(BuildContext context) async { try { // 请求相册权限 final status = await Permission.photos.request(); if (!status.isGranted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('需要相册权限才能保存图片'), backgroundColor: Colors.red, ), ); return; } // 获取卡片的RenderRepaintBoundary RenderRepaintBoundary boundary = _cardKey.currentContext!.findRenderObject() as RenderRepaintBoundary; // 转换为图片 ui.Image image = await boundary.toImage(pixelRatio: 3.0); ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); Uint8List pngBytes = byteData!.buffer.asUint8List(); // 保存到相册 // final result = await ImageGallerySaver.saveImage( // pngBytes, // name: 'plugin_${plugin.name}_${DateTime.now().millisecondsSinceEpoch}', // quality: 100, // ); // if (result['isSuccess']) { // ScaffoldMessenger.of(context).showSnackBar( // SnackBar( // content: Text('图片已保存到相册'), // backgroundColor: Colors.green, // ), // ); // } else { // throw Exception('保存失败'); // } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('保存失败: $e'), backgroundColor: Colors.red, ), ); } } void _shareCard(BuildContext context) { // TODO: 实现分享功能 ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('分享功能开发中...'), backgroundColor: Colors.blue, ), ); } void _copyLink(BuildContext context) { final link = 'https://pub.dev/packages/${plugin.name}'; Clipboard.setData(ClipboardData(text: link)); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('链接已复制到剪贴板'), backgroundColor: Colors.green, ), ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/components/dialog/sort_picker.dart ================================================ import 'package:flutter/material.dart'; import 'package:pkg_player/pkg_player.dart'; Future showSortPicker(BuildContext context, SortType current) { Color? tileColor = Theme.of(context).listTileTheme.tileColor; return showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (ctx) => Container( decoration: BoxDecoration( color: tileColor, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), padding: EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(2), ), ), SizedBox(height: 20), Text( context.pkgL10n.sortMethod, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, ), ), SizedBox(height: 20), ...SortType.values.map((sortType) { final isSelected = current == sortType; return Container( margin: EdgeInsets.symmetric(vertical: 4), decoration: BoxDecoration( color: isSelected ? Theme.of(context).primaryColor.withOpacity(0.1) : null, borderRadius: BorderRadius.circular(12), ), child: ListTile( title: Text( sortType.getLabel(context), style: TextStyle( fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, color: isSelected ? Theme.of(context).primaryColor : null, ), ), trailing: isSelected ? Icon(Icons.check_circle, color: Theme.of(context).primaryColor) : null, onTap: () { Navigator.pop(ctx, sortType); }, ), ); }).toList(), SizedBox(height: 20), ], ), ), ); } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/home/empty_list.dart ================================================ import 'package:flutter/material.dart'; class EmptyListView extends StatelessWidget { final RefreshCallback onRefresh; const EmptyListView({super.key, required this.onRefresh}); @override Widget build(BuildContext context) { return RefreshIndicator( onRefresh: onRefresh, child: SingleChildScrollView( physics: AlwaysScrollableScrollPhysics(), child: Container( height: MediaQuery.of(context).size.height - 200, child: Center(child: Text('暂无数据')), ), ), ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/home/pkg_list_with_data.dart ================================================ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:tolyui_refresh/tolyui_refresh.dart'; import '../../../pkg_player.dart'; import 'plugin_item.dart'; bool kIsDeskTop = kIsWeb || Platform.isMacOS || Platform.isWindows || Platform.isLinux; class PkgListWithData extends StatelessWidget { final List packages; final RefreshController controller; final VoidCallback? onRefresh; final VoidCallback? onLoading; final bool hasMore; const PkgListWithData({ super.key, required this.packages, required this.controller, this.onRefresh, this.hasMore = true, this.onLoading, }); @override Widget build(BuildContext context) { Color? tiledColor = Theme.of(context).listTileTheme.tileColor; PkgL10n l10n = context.pkgL10n; return TolyRefresh( onRefresh: onRefresh, enablePullUp: hasMore, onLoading: onLoading, controller: controller, footer: kIsDeskTop ? SliverToBoxAdapter( child: GestureDetector( onTap: onLoading, child: Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12), child: Text( '加载更多(10)', style: TextStyle(fontSize: 14, color: Colors.blue), ), )), ), ) : null, child: ListView.separated( separatorBuilder: (_, __) => Divider(), itemCount: packages.length, itemBuilder: (context, index) { return PluginItem( l10n: l10n, plugin: packages[index], tiledColor: tiledColor, ); }, ), ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/home/pkg_player_home_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import 'package:pkg_player/pkg_player.dart'; import 'package:pkg_player/src/view/components/dialog/sort_picker.dart'; import 'package:tolyui_refresh/tolyui_refresh.dart' hide RefreshIndicator; import '../../bloc/bloc.dart'; import '../../bloc/packages/package_state.dart'; import '../../repository/api/request.dart'; import '../../repository/model/sort_type.dart'; import 'empty_list.dart'; import 'pkg_list_with_data.dart'; import 'recommendation_page.dart'; class PkgPlayerPage extends StatelessWidget { const PkgPlayerPage({super.key}); @override Widget build(BuildContext context) { final request = PackageRequest(); return MultiBlocProvider( providers: [ BlocProvider( create: (context) => CategoryCubit(request)..loadCategories()), BlocProvider(create: (context) => PackageCubit(request)), ], child: _PkgPlayerView(), ); } } class _PkgPlayerView extends StatefulWidget { @override _PkgPlayerViewState createState() => _PkgPlayerViewState(); } class _PkgPlayerViewState extends State<_PkgPlayerView> with TickerProviderStateMixin { TabController? _tabController; List _categories = []; final Map controllerMap = {}; SortType _currentSortType = SortType.downloads; @override Widget build(BuildContext context) { PkgL10n l10n = context.pkgL10n; return BlocConsumer( listener: listenCategory, builder: (context, state) { if (state is CategoryLoaded) { final categories = state.categories; return Scaffold( appBar: buildAppBar(context, categories, l10n), body: categories.isNotEmpty ? buildContent(categories) : EmptyListView(onRefresh: () => _onRefresh(context, '')), ); } if (state is CategoryLoading) { return Scaffold( appBar: AppBar(title: Text(l10n.flutterPluginRepo)), body: Center(child: CircularProgressIndicator()), ); } if (state is CategoryError) { return Scaffold( appBar: AppBar(title: Text(l10n.flutterPluginRepo)), body: Center(child: Text(l10n.loadFailedWithMessage(state.message))), ); } return Scaffold( appBar: AppBar(title: Text(l10n.flutterPluginRepo)), body: Center(child: Text(l10n.noData)), ); }, ); } void listenCategory(BuildContext context, state) { if (state is CategoryLoaded) { _categories = state.categories; _initTabController(state.categories); if (state.categories.isNotEmpty) { context .read() .loadPackagesForCategory(state.categories[0].key); } } } BlocBuilder buildContent( List categories) { return BlocBuilder( builder: (context, state) { if (state is PackageInitial) { return Center(child: CircularProgressIndicator()); } return TabBarView( controller: _tabController, children: categories.map((category) { final isLoading = state.isLoading(category.key); PackageResult? packages = state.getResult(category.key); if (isLoading || packages == null) { return Center(child: CircularProgressIndicator()); } if (packages.data.isEmpty) { return Center(child: Text('暂无数据')); } if (packages.data.isNotEmpty) { return PkgListWithData( onLoading: () => _onLoading( context, category.key, ), packages: packages.data, hasMore: packages.data.length != packages.total, onRefresh: () => _refresh(context, category.key), controller: controllerMap[category.key]!, ); } return RefreshIndicator( onRefresh: () => _onRefresh(context, category.key), child: SingleChildScrollView( physics: AlwaysScrollableScrollPhysics(), child: SizedBox( height: MediaQuery.of(context).size.height - 200, child: Center(child: Text('暂无数据')), ), )); }).toList(), ); }, ); } AppBar buildAppBar( BuildContext context, List categories, PkgL10n l10n, ) { bool isZh = Localizations.localeOf(context).languageCode == 'zh'; return AppBar( title: Text(l10n.flutterPluginRepo), leading: IconButton( icon: Icon(Icons.upload), onPressed: () => _navigateToRecommendation(context), ), actions: [ IconButton( icon: Icon(Icons.filter_list), onPressed: () => _showSortDialog(context), ), ], bottom: categories.isNotEmpty ? TabBar( controller: _tabController, isScrollable: true, tabAlignment: TabAlignment.start, tabs: categories .map((category) => Tab(text: category.label(isZh))) .toList(), ) : null, ); } void _initTabController(List tabs) { if (!mounted) return; int length = tabs.length; if (_tabController?.length != length) { _tabController?.dispose(); _tabController = TabController(length: length, vsync: this); _tabController?.addListener(_onTabChanged); } if (controllerMap.isEmpty) { for (Category category in tabs) { controllerMap[category.key] = RefreshController(); } } } void _onTabChanged() { if (_tabController != null && _categories.isNotEmpty) { final currentIndex = _tabController!.index; if (currentIndex < _categories.length) { final categoryKey = _categories[currentIndex].key; context.read().loadPackagesForCategory(categoryKey); } } } Future _onRefresh(BuildContext context, String categoryKey) async { if (categoryKey.isEmpty) { context.read().loadCategories(); } else { context.read().clearPackages(); context.read().loadPackagesForCategory(categoryKey); } } void _navigateToRecommendation(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => RecommendationPage(), ), ); } @override void dispose() { _tabController?.removeListener(_onTabChanged); _tabController?.dispose(); super.dispose(); } void _refresh(BuildContext context, String key) async { await context .read() .loadPackagesForCategory(key, isRefresh: true); controllerMap[key]?.refreshCompleted(); } void _onLoading(BuildContext context, String key) async { bool noMore = await context.read().loadMore( key, ); if (noMore) { controllerMap[key]?.loadNoData(); } else { controllerMap[key]?.loadComplete(); } } void _showSortDialog(BuildContext context) async { SortType? sort = await showSortPicker(context, _currentSortType); if (sort != null) { _currentSortType = sort; _applySortFilter(); } } void _applySortFilter() { if (_categories.isNotEmpty && _tabController != null) { final currentIndex = _tabController!.index; if (currentIndex < _categories.length) { final categoryKey = _categories[currentIndex].key; context.read().loadPackagesForCategory( categoryKey, isRefresh: true, sortBy: _currentSortType.value, ); } } } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/home/plugin_item.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pkg_player/pkg_player.dart'; import '../../bloc/comments/comments_cubit.dart'; import 'pkg_list_with_data.dart'; class PluginItem extends StatelessWidget { final PluginModel plugin; final PkgL10n l10n; final Color? tiledColor; const PluginItem({ Key? key, required this.plugin, this.tiledColor, required this.l10n, }) : super(key: key); String _formatNumber(int number) { if (number >= 1000000) { return '${(number / 1000000).toStringAsFixed(1)}M'; } else if (number >= 1000) { return '${(number / 1000).toStringAsFixed(1)}K'; } return number.toString(); } @override Widget build(BuildContext context) { TextStyle style = TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, fontSize: 12); Color subColor = Color(0xff6d7278); TextStyle subStyle = TextStyle( fontSize: 10, color: subColor, letterSpacing: 0, ); return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => BlocProvider( create: (context) => CommentsCubit(PackageRequest(), plugin.id!)..loadComments(), child: PluginDetailPage(plugin: plugin)), ), ); }, child: Container( padding: EdgeInsets.symmetric( horizontal: kIsDeskTop ? 46 : 12, vertical: 10), decoration: BoxDecoration( color: tiledColor, // border: Border(bottom: BorderSide(color: Colors.grey[300]!)), ), child: Stack( children: [ if (plugin.isFavorite) Positioned( top: 40, right: 0, width: 50, height: 50, child: Image.asset( width: 50, height: 50, 'assets/images/flutter_favorite.webp', package: 'pkg_player', ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Column( spacing: 2, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( plugin.name, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600), ), Row( children: [ Text.rich(TextSpan(children: [ TextSpan( text: 'v ', style: TextStyle(fontSize: 10)), TextSpan( text: plugin.lastVersion, style: TextStyle( color: Colors.blue[700], fontSize: 12)), TextSpan( text: ' (${formatDate(plugin.lastPublish, l10n)})', style: TextStyle( letterSpacing: 0, fontSize: 12, color: subColor)), ])), SizedBox(width: 4), Icon(Icons.verified_outlined, size: 14, color: Colors.grey[500]), SizedBox(width: 4), Text( plugin.publisher ?? '', style: TextStyle( color: Colors.blue, letterSpacing: 0, fontSize: 12), ), ], ), ], ), ), Column( children: [ Text('${_formatNumber(plugin.statistics.likes)}', style: style), Text( l10n.like, style: subStyle, ), ], ), SizedBox(width: 8), Column( children: [ Text('${_formatNumber(plugin.statistics.points)}', style: style), Text( l10n.score, style: subStyle, ), ], ), SizedBox(width: 8), Column( children: [ Text('${_formatNumber(plugin.statistics.downloads)}', style: style), Text( l10n.downloads, style: subStyle, ), ], ), ], ), SizedBox(height: 6), if (plugin.tags.isNotEmpty) ...[ PackageTagsView( type: 'Platform', platforms: plugin.platforms .map((e) => e.replaceAll('platform:', '')) .toList(), ), SizedBox(height: 4), Wrap( spacing: 4, children: [ PackageTagsView( type: 'sdk', platforms: plugin.sdks .map((e) => e.replaceAll('sdk:', '')) .toList(), ), PackageTagsView( type: 'license', platforms: plugin.license .map((e) => e.replaceAll('license:', '')) .toList(), ), ], ) ], SizedBox(height: 6), Text( plugin.desc ?? '', style: TextStyle(color: Colors.grey[600], fontSize: 14), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ], ), ), ); } } class PackageTagsView extends StatelessWidget { final List platforms; final String type; const PackageTagsView({ super.key, required this.platforms, required this.type, }); @override Widget build(BuildContext context) { Color themeColor = Theme.of(context).primaryColor; return Container( color: themeColor.withValues(alpha: 0.1), padding: EdgeInsets.only(right: 4), child: Wrap( spacing: 6, children: [ Wrap( children: [ Container( decoration: BoxDecoration( // color: themeColor.withValues(alpha: 0.1), border: Border( right: BorderSide( color: themeColor.withValues(alpha: 0.5)))), padding: EdgeInsets.symmetric(horizontal: 6, vertical: 4), child: Text( type.toUpperCase(), style: TextStyle(fontSize: 10, height: 1), ), ), ], ), ...platforms.map( (e) => Container( padding: EdgeInsets.only(top: 4, bottom: 4), child: Text( e.toLowerCase(), style: TextStyle(fontSize: 10, height: 1, color: themeColor), ), ), ) ], ), ); } } String formatDate(String dateStr, PkgL10n l10n) { try { final date = DateTime.parse(dateStr); final now = DateTime.now(); final diff = now.difference(date).inDays; if (diff == 0) return l10n.today; if (diff == 1) return l10n.yesterday; if (diff < 30) return l10n.daysAgo(diff); if (diff < 365) return l10n.monthsAgo((diff / 30).floor()); return l10n.yearsAgo((diff / 365).floor()); } catch (e) { return dateStr; } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/home/recommendation_page.dart ================================================ import 'package:flutter/material.dart'; import '../../repository/api/request.dart'; import '../../l10n/l10n.dart'; class RecommendationPage extends StatefulWidget { @override _RecommendationPageState createState() => _RecommendationPageState(); } class _RecommendationPageState extends State { final _formKey = GlobalKey(); final _packageNameController = TextEditingController(); final _featuresController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(context.pkgL10n.recommendPlugin), ), body: SingleChildScrollView( padding: EdgeInsets.all(16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.blue.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.info, color: Colors.blue, size: 20), SizedBox(width: 8), Text( context.pkgL10n.recommendDescription, style: TextStyle( fontWeight: FontWeight.bold, color: Colors.blue.shade700, ), ), ], ), SizedBox(height: 8), Text( context.pkgL10n.recommendNotice, style: TextStyle( color: Colors.blue.shade700, height: 1.4, ), ), ], ), ), SizedBox(height: 24), Text( context.pkgL10n.packageNameLabel, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), SizedBox(height: 8), TextFormField( controller: _packageNameController, decoration: InputDecoration( hintText: context.pkgL10n.enterPackageExample, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), prefixIcon: Icon(Icons.extension), ), validator: (value) { if (value == null || value.trim().isEmpty) { return context.pkgL10n.enterPackageName; } return null; }, ), SizedBox(height: 20), Text( context.pkgL10n.packageFeatures, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), SizedBox(height: 8), TextFormField( controller: _featuresController, maxLines: 5, decoration: InputDecoration( hintText: context.pkgL10n.describePluginForRecommend, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), alignLabelWithHint: true, ), validator: (value) { if (value == null || value.trim().isEmpty) { return context.pkgL10n.describePackageFeatureHint; } if (value.trim().length < 10) { return context.pkgL10n.minDescriptionLength; } return null; }, ), SizedBox(height: 32), SizedBox( width: double.infinity, height: 48, child: ElevatedButton( onPressed: _submitRecommendation, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).primaryColor, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( context.pkgL10n.submitRecommendation, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ], ), ), ), ); } void _submitRecommendation() async { if (_formKey.currentState!.validate()) { try { final request = PackageRequest(); final result = await request.submitFeedback( feedbackType: 'package', title: _packageNameController.text.trim(), content: _featuresController.text.trim(), ); if (result.success) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(context.pkgL10n.recommendSubmitted), backgroundColor: Colors.green, ), ); _packageNameController.clear(); _featuresController.clear(); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(context.pkgL10n.submitFailedWithMsg(result.msg)), backgroundColor: Colors.red, ), ); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(context.pkgL10n.submitFailedNetwork), backgroundColor: Colors.red, ), ); } } } @override void dispose() { _packageNameController.dispose(); _featuresController.dispose(); super.dispose(); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/package_detail/comments_section.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../pkg_player.dart'; import '../../bloc/comments/comments_cubit.dart'; import '../../bloc/comments/comments_state.dart'; import '../comments/comments_empty.dart'; import '../comments/comments_error.dart'; import '../comments/comments_loading.dart'; import '../comments/comments_with_data.dart'; import '../comments/comments_detail_page.dart'; class CommentsSection extends StatelessWidget { final int? packageId; final String? packageName; final OnReplay onReplay; final ValueChanged onViewMoreDetail; const CommentsSection({ super.key, required this.packageId, this.packageName, required this.onReplay, required this.onViewMoreDetail, }); @override Widget build(BuildContext context) { CommentsCubit cubit = context.watch(); if (packageId == null) { return SliverToBoxAdapter( child: SizedBox.shrink(), ); } CommentsState state = cubit.state; if (state is CommentsLoading) { return SliverToBoxAdapter( child: CommentsLoadingView(), ); } if (state is CommentsLoaded) { if (state.comments.data.isEmpty) { return SliverToBoxAdapter( child: CommentsEmptyView(), ); } return SliverCommentsWithData( total: state.comments.total, comments: state.comments.data, onViewAll: () => _onViewAll(context), onReplay: onReplay, onViewMoreDetail: onViewMoreDetail, ); } if (state is CommentsError) { return SliverToBoxAdapter( child: CommentsErrorView(message: state.message), ); } return SliverToBoxAdapter( child: SizedBox.shrink(), ); } void _onViewAll(BuildContext context) async { if (packageId == null) return; CommentsCubit cubit = context.read(); await Navigator.push( context, MaterialPageRoute( builder: (_) => BlocProvider.value( value: context.read(), child: CommentsDetailPage( onCommentAction: (String content, [int? commentId]) { cubit.sendComment(content, parentId: commentId); }, packageId: packageId!, packageName: packageName ?? '插件', ), ), )); cubit.slice(); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/package_detail/detail.dart ================================================ // TODO Implement this library. ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/package_detail/detail_flexible_bar.dart ================================================ import 'dart:math' as math; import 'package:flutter/material.dart'; class DetailFlexibleBar extends StatefulWidget { final String name; final String desc; final String version; const DetailFlexibleBar( {super.key, required this.name, required this.version, required this.desc}); @override State createState() => _DetailFlexibleBarState(); } class _DetailFlexibleBarState extends State with TickerProviderStateMixin { late Animation _rotationAnimation; late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 20), vsync: this, )..repeat(); _rotationAnimation = Tween(begin: 0, end: 2 * math.pi).animate(_controller); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return FlexibleSpaceBar( centerTitle: true, titlePadding: EdgeInsets.only(bottom: 8), title: ShaderMask( shaderCallback: (bounds) => LinearGradient( colors: [Colors.white, Colors.white70], ).createShader(bounds), child: Column( mainAxisSize: MainAxisSize.min, spacing: 4, children: [ Text( widget.name, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w900, color: Colors.white, shadows: [ Shadow( color: Colors.black.withOpacity(0.3), offset: Offset(1, 1), blurRadius: 3, ), ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: Text( widget.desc, style: TextStyle(fontSize: 8, color: Colors.white), ), ) ], ), ), background: AnimatedBuilder( animation: _rotationAnimation, builder: (context, child) { return Stack( children: [ // 渐变背景 Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: getDetailGradientColors(widget.name), ), ), ), // 旋转装饰环 Positioned.fill( child: Transform.rotate( angle: _rotationAnimation.value, child: Container( decoration: BoxDecoration( border: Border.all( color: Colors.white.withOpacity(0.1), width: 1, ), ), child: Stack( children: [ Positioned( top: 15, right: 15, child: Container( width: 4, height: 4, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withOpacity(0.8), boxShadow: [ BoxShadow( color: Colors.white.withOpacity(0.5), blurRadius: 6, spreadRadius: 1, ), ], ), ), ), ], ), ), ), ), // 动态装饰元素 Positioned( top: -50, right: -50, child: Transform.rotate( angle: _rotationAnimation.value * 0.5, child: Container( width: 100, height: 100, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ Colors.white.withOpacity(0.15), Colors.white.withOpacity(0.05), Colors.transparent, ], ), ), ), ), ), Positioned( bottom: -30, left: -30, child: Transform.rotate( angle: -_rotationAnimation.value * 0.3, child: Container( width: 60, height: 60, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), gradient: LinearGradient( colors: [ Colors.white.withOpacity(0.1), Colors.transparent, ], ), ), ), ), ), // 闪烁粒子效果 ...List.generate(3, (index) { final angle = (index * 2 * math.pi / 3) + _rotationAnimation.value * 2; final radius = 60 + math.sin(_rotationAnimation.value * 3 + index) * 15; return Positioned( left: MediaQuery.of(context).size.width / 2 + math.cos(angle) * radius, top: 90 + math.sin(angle) * radius, child: Container( width: 2, height: 2, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withOpacity(0.6 + 0.4 * math.sin(_rotationAnimation.value * 4 + index)), boxShadow: [ BoxShadow( color: Colors.white.withOpacity(0.3), blurRadius: 3, ), ], ), ), ); }), // 中心内容 Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Stack( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 26, vertical: 8), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.white.withOpacity(0.3), width: 2, ), ), child: Text( 'v${widget.version}', style: TextStyle( color: Colors.white, fontSize: 14, height: 1, fontWeight: FontWeight.bold, shadows: [ Shadow( color: Colors.black.withOpacity(0.3), offset: Offset(1, 1), blurRadius: 1, ), ], ), ), ), ], ), SizedBox(height: 16), ], ), ), ], ); }, ), ); } } List getDetailGradientColors(String name) { final colorSets = [ [Color(0xFF667eea), Color(0xFF764ba2)], // 紫蓝渐变 [Color(0xFF667eea), Color(0xFF764ba2)], // 深紫渐变 ]; final hash = name.hashCode.abs(); return colorSets[hash % colorSets.length]; } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/package_detail/plugin_dependencies_section.dart ================================================ import 'package:flutter/material.dart'; import 'package:pkg_player/pkg_player.dart'; class PluginDependenciesSection extends StatelessWidget { final Map? dependencies; const PluginDependenciesSection({Key? key, required this.dependencies}) : super(key: key); @override Widget build(BuildContext context) { if (dependencies == null || dependencies!.isEmpty) { return SizedBox.shrink(); } Color? tileColor = Theme.of(context).listTileTheme.tileColor; return Column( children: [ Container( width: double.infinity, padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12), decoration: BoxDecoration( color: tileColor, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(context.pkgL10n.dependencies, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), SizedBox(height: 12), Wrap( spacing: 6, runSpacing: 6, children: dependencies!.entries.map((entry) { return Container( padding: EdgeInsets.symmetric(horizontal: 6, vertical: 4), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.blue.shade50, Colors.blue.shade100], ), borderRadius: BorderRadius.circular(6), border: Border.all( color: Colors.blue.withOpacity(0.2), width: 0.5, ), ), child: Text( "${entry.key}: ${entry.value.toString()}", style: TextStyle( fontSize: 12, height: 1, letterSpacing: 0, fontWeight: FontWeight.w500, color: Colors.blue[700], ), ), ); }).toList(), ), ], ), ), ], ); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/package_detail/plugin_detail_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pkg_player/pkg_player.dart'; import '../../bloc/comments/comment_replies_cubit.dart'; import '../../bloc/comments/comments_cubit.dart'; import '../../bloc/comments/comments_state.dart'; import '../comments/comments_top_bar.dart'; import '../comments/comment_replies_page.dart'; import 'detail_flexible_bar.dart'; import 'comments_section.dart'; import 'plugin_info_section.dart'; import 'plugin_dependencies_section.dart'; import 'plugin_tags.dart'; class PluginDetailPage extends StatefulWidget { final PluginModel plugin; const PluginDetailPage({Key? key, required this.plugin}) : super(key: key); @override _PluginDetailPageState createState() => _PluginDetailPageState(); } class _PluginDetailPageState extends State { @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( slivers: [ SliverAppBar( expandedHeight: 200, pinned: true, centerTitle: true, backgroundColor: getDetailGradientColors(widget.plugin.name)[0], elevation: 0, iconTheme: IconThemeData(color: Colors.white), actions: [ // Container( // margin: EdgeInsets.only(right: 16, top: 8), // padding: EdgeInsets.symmetric(horizontal: 6, vertical: 6), // decoration: BoxDecoration( // color: Colors.white.withOpacity(0.2), // borderRadius: BorderRadius.circular(12), // border: Border.all(color: Colors.white.withOpacity(0.3)), // ), // child: GestureDetector( // child: Icon( // Icons.share, // color: Colors.white, // size: 20, // ), // onTap: () { // Navigator.push( // context, // PageRouteBuilder( // pageBuilder: (context, animation, secondaryAnimation) => // PluginCardPage(plugin: widget.plugin), // transitionsBuilder: // (context, animation, secondaryAnimation, child) { // return SlideTransition( // position: Tween( // begin: Offset(0.0, 1.0), // end: Offset.zero, // ).animate(CurvedAnimation( // parent: animation, // curve: Curves.easeOutCubic, // )), // child: child, // ); // }, // transitionDuration: Duration(milliseconds: 400), // ), // ); // }, // ), // ), ], flexibleSpace: DetailFlexibleBar( desc: widget.plugin.desc ?? '', name: widget.plugin.name, version: widget.plugin.lastVersion, ), ), SliverToBoxAdapter( child: PluginInfoSection(plugin: widget.plugin), ), if (widget.plugin.tags.isNotEmpty) SliverToBoxAdapter( child: PluginTags( tags: widget.plugin.tags, ), ), if (widget.plugin.tags.isNotEmpty) SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.only(top: 10.0, bottom: 10), child: PluginDependenciesSection( dependencies: widget.plugin.dependencies), ), ), SliverToBoxAdapter( child: CommentsTopBar( onTapComment: () => _showReplyInput(-1), ), ), CommentsSection( packageId: widget.plugin.id, packageName: widget.plugin.name, onReplay: _showReplyInput, onViewMoreDetail: _onViewMoreDetail, ) ], ), ); } final TextEditingController _commentController = TextEditingController(); void _sendComment({int? parentId}) { if (_commentController.text.trim().isEmpty) return; String content = _commentController.text.trim(); context.read().sendComment(content, parentId: parentId); Navigator.pop(context); } PkgL10n get l10n => context.pkgL10n; void _showReplyInput(int parentId) { String hintText = parentId == -1 ? l10n.writeCommentHint : l10n.replyCommentHint; Color? tileColor = Theme.of(context).listTileTheme.tileColor; showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: tileColor, borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), child: Row( children: [ Expanded( child: TextField( controller: _commentController, autofocus: true, decoration: InputDecoration( hintText: hintText, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), filled: true, fillColor: Colors.grey.withValues(alpha: 0.2), contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), maxLines: null, ), ), SizedBox(width: 12), GestureDetector( onTap: () => _sendComment(parentId: parentId), child: Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.send, color: Colors.white, size: 20, ), ), ), ], ), ), ), ).whenComplete(() { _commentController.clear(); }); } void _onViewMoreDetail(int commentId) { // 需要先获取评论对象,这里简化处理 final currentState = context.read().state; if (currentState is CommentsLoaded) { final comment = currentState.comments.data.firstWhere( (c) => c.id == commentId, orElse: () => Comment( id: commentId, packageId: widget.plugin.id!, userId: 0, guestName: '未知用户', content: '评论内容', contentType: 'text', createAt: DateTime.now().toString(), replies: [], repliesTotal: 0, ), ); Navigator.push( context, MaterialPageRoute( builder: (context) => BlocProvider( create: (context) => CommentRepliesCubit(PackageRequest(), comment.id) ..loadReplies(), child: CommentRepliesPage( parentComment: comment, ), ), ), ); } } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/package_detail/plugin_info_section.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:pkg_player/pkg_player.dart'; import '../home/plugin_item.dart'; class PluginInfoSection extends StatelessWidget { final PluginModel plugin; const PluginInfoSection({Key? key, required this.plugin}) : super(key: key); @override Widget build(BuildContext context) { Color? tileColor = Theme.of(context).listTileTheme.tileColor; PkgL10n l10n = context.pkgL10n; return Container( width: double.infinity, padding: EdgeInsets.only(left: 12, right: 12, top: 16, bottom: 12), decoration: BoxDecoration( color: tileColor, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (plugin.publisher?.isNotEmpty == true) _buildInfoRow(Icons.person, l10n.publisher, plugin.publisher!), if (plugin.homepage?.isNotEmpty == true) _buildInfoRow(Icons.home, l10n.home, plugin.homepage!), if (plugin.repository?.isNotEmpty == true) _buildRepositoryRow(plugin.repository!, l10n), _buildInfoRow(Icons.schedule, l10n.lastRelease, formatDate(plugin.lastPublish, l10n)), SizedBox(height: 4), Divider(), SizedBox(height: 8), Row( children: [ Expanded( child: _buildStatItem( l10n.likeCount, plugin.statistics.likes, Icons.favorite, Colors.red, ), ), Expanded( child: _buildStatItem( l10n.pluginScore, plugin.statistics.points, Icons.bar_chart, Colors.orange, ), ), Expanded( child: _buildStatItem( l10n.downloadsLast30Days, plugin.statistics.downloads, Icons.download, Colors.green, ), ), ], ), ], ), ); } Widget _buildInfoRow(IconData icon, String label, String value) { return Padding( padding: EdgeInsets.only(bottom: 8), child: Row( children: [ Icon(icon, size: 20, color: Colors.grey[600]), SizedBox(width: 12), Text('$label:', style: TextStyle( fontWeight: FontWeight.w500, color: Colors.grey[700])), SizedBox(width: 8), Expanded( child: Text(value, style: TextStyle(color: Colors.grey[800]))), ], ), ); } Widget _buildRepositoryRow(String repository, PkgL10n l10n) { final displayName = _extractRepoName(repository); return Padding( padding: EdgeInsets.only(bottom: 8), child: Row( children: [ Icon(Icons.code, size: 20, color: Colors.grey[600]), SizedBox(width: 12), Text(l10n.repository, style: TextStyle( fontWeight: FontWeight.w500, color: Colors.grey[700])), SizedBox(width: 8), Text(displayName, style: TextStyle(color: Colors.blue)), SizedBox(width: 8), GestureDetector( onTap: () => _copyToClipboard(repository), child: Icon( Icons.copy, size: 16, color: Colors.grey[600], ), ), ], ), ); } String _extractRepoName(String repository) { try { final uri = Uri.parse(repository); final pathSegments = uri.pathSegments.where((s) => s.isNotEmpty).toList(); if (pathSegments.length >= 2) { return '${pathSegments[pathSegments.length - 2]}/${pathSegments.last}'; } return pathSegments.isNotEmpty ? pathSegments.last : repository; } catch (e) { return repository; } } void _copyToClipboard(String text) { Clipboard.setData(ClipboardData(text: text)); } Widget _buildStatItem(String label, int value, IconData icon, Color color) { return Column( spacing: 4, children: [ Row( spacing: 2, mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: color, size: 18), Text( _formatNumber(value), style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: color), ), ], ), Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 12)), ], ); } String _formatNumber(int number) { if (number >= 1000000) { return '${(number / 1000000).toStringAsFixed(1)}M'; } else if (number >= 1000) { return '${(number / 1000).toStringAsFixed(1)}K'; } return number.toString(); } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/package_detail/plugin_tags.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:pkg_player/pkg_player.dart'; class PluginTags extends StatelessWidget { final List tags; const PluginTags({super.key, required this.tags}); @override Widget build(BuildContext context) { final categorizedTags = _categorizeTags(tags); Color? tileColor = Theme.of(context).listTileTheme.tileColor; PkgL10n l10n = context.pkgL10n; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 12), Container( width: double.infinity, padding: EdgeInsets.all(12), decoration: BoxDecoration( color: tileColor, ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.tagList, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), SizedBox( height: 12, ), ...categorizedTags.entries.map((entry) { return Padding( padding: EdgeInsets.only( bottom: entry.key == categorizedTags.keys.last ? 0 : 10), child: Wrap(spacing: 4, runSpacing: 4, children: [ Text( _getCategoryDisplayName(entry.key, l10n) + ": ", style: TextStyle( fontSize: 13, letterSpacing: 0, fontWeight: FontWeight.w600, color: Colors.grey[700], ), ), ...entry.value .map((tag) => Container( padding: EdgeInsets.symmetric( horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _getCategoryColor(entry.key).withOpacity(0.1), borderRadius: BorderRadius.circular(6), border: Border.all( color: _getCategoryColor(entry.key) .withOpacity(0.3), ), ), child: Text( _removeTagPrefix(tag), style: TextStyle( height: 1, letterSpacing: 0, color: _getCategoryColor(entry.key), fontSize: 12, ), ), )) .toList(), ]), ); }).toList(), ]), ), ], ); } Map> _categorizeTags(List tags) { final Map> categorized = {}; for (String tag in tags) { String category = ''; if (tag.startsWith('platform:')) { category = 'platform'; } else if (tag.startsWith('sdk:')) { category = 'sdk'; } else if (tag.startsWith('is:')) { category = 'is'; continue; } else if (tag.startsWith('topic:')) { category = 'topic'; } else if (tag.startsWith('license:')) { category = 'license'; } categorized.putIfAbsent(category, () => []); categorized[category]!.add(tag); } // Return ordered map final orderedCategories = [ 'sdk', 'platform', 'is', 'license', 'topic', 'other' ]; final Map> orderedMap = {}; for (String category in orderedCategories) { if (categorized.containsKey(category)) { orderedMap[category] = categorized[category]!; } } return orderedMap; } String _getCategoryDisplayName(String category, PkgL10n l10n) { switch (category) { case 'platform': return l10n.platform; case 'sdk': return 'SDK'; case 'is': return l10n.type; case 'topic': return l10n.theme; case 'license': return l10n.license; case 'other': return l10n.others; default: return category; } } Color _getCategoryColor(String category) { switch (category) { case 'platform': return Colors.blue; case 'sdk': return Colors.green; case 'is': return Colors.orange; case 'topic': return Colors.purple; case 'license': return Colors.teal; case 'other': return Colors.grey; default: return Colors.blue; } } String _removeTagPrefix(String tag) { if (tag.contains(':')) { return tag.split(':').last; } return tag; } } ================================================ FILE: modules/tools_system/pkg_player/lib/src/view/view.dart ================================================ export 'home/pkg_player_home_page.dart'; export 'package_detail/plugin_detail_page.dart'; export 'components/card/plugin_card.dart'; export 'components/card/plugin_card_page.dart'; export 'package_detail/detail.dart'; ================================================ FILE: modules/tools_system/pkg_player/pubspec.yaml ================================================ name: pkg_player description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ^3.6.1 flutter: ">=1.17.0" dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter sqflite: 2.4.0 flutter_bloc: ^8.1.6 equatable: ^2.0.5 tolyui_refresh: ^0.0.1+1 path_provider: ^2.1.1 permission_handler: ^11.0.1 # image_gallery_saver: ^2.0.3 unit_env: path: ../../basic_system/unit_env dev_dependencies: flutter_test: sdk: flutter http: ^1.1.0 flutter_lints: ^5.0.0 dependency_overrides: fx_dio: ^0.0.4+3 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: assets: - assets/images/ # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/to/asset-from-package # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/to/font-from-package ================================================ FILE: modules/tools_system/pkg_player/test/pkg_player_test.dart ================================================ // import 'package:flutter_test/flutter_test.dart'; // // import 'package:pkg_player/pkg_player.dart'; // // void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); // } ================================================ FILE: modules/tools_system/pkg_player/test/science_server/moke/category.json ================================================ [ { "key": "state_management", "name": "状态管理", "priority": 999, "description": "管理界面显示与业务逻辑之间的数据同步。通过 Bloc、Riverpod、Provider 等模型确保数据流清晰、可预测,提高可维护性,是中大型 Flutter 项目的基础架构核心部分。" }, { "key": "ui", "name": "UI 组件", "priority": 998, "description": "包含界面组件、布局系统、手势交互与视觉表现,是应用前端体验的核心。负责构建整体界面结构、列表、表单、弹窗、滚动效果等,决定用户对应用的第一印象与交互舒适度。" }, { "key": "network", "name": "网络通信", "priority": 997, "description": "负责 API 请求、实时通信和服务器交互,包括 HTTP、WebSocket 等协议。决定数据交互效率、稳定性与安全性,是大多数应用必不可少的底层功能。" }, { "key": "data_storage", "name": "数据存储", "priority": 996, "description": "用于本地保存用户数据、数据库信息、缓存和配置。包括 SQLite、Hive、Isar 等方案,支持离线模式与性能优化,是提升应用稳定性的重要能力。" }, { "key": "platform_access", "name": "平台能力", "priority": 995, "description": "提供对系统或硬件功能的访问,如相机、定位、传感器、权限、电池信息等。让应用具备更接近原生的设备能力,增强功能扩展范围。" }, { "key": "media", "name": "媒体处理", "priority": 994, "description": "涵盖图片、音频、视频的采集、播放、编辑与压缩能力。适用于内容类应用、社交平台、多媒体播放器等场景,是丰富应用功能的重要模块。" }, { "key": "framework", "name": "应用基础", "priority": 993, "description": "应用程序的基础功能,比如架构设计、引擎、认证、路由、支付等应用级通用功能。" }, { "key": "data_handler", "name": "数据处理", "priority": 992, "description": "数据的加解密、转换、文件操作等逻辑处理。" } ] ================================================ FILE: modules/tools_system/pkg_player/test/science_server/moke/moke.dart ================================================ dynamic data = { "name": "provider", "last_version": "6.1.5+1", "last_publish": "2025-08-19T12:50:56.438080Z", "statistics": {"likes": 10881, "points": 150, "downloads": 2018839}, "tags": [ "is:flutter-favorite", "publisher:dash-overflow.net", "sdk:flutter", "platform:android", "platform:ios", "platform:windows", "platform:linux", "platform:macos", "platform:web", "is:null-safe", "is:wasm-ready", "is:dart3-compatible", "license:mit", "license:fsf-libre", "license:osi-approved" ], "desc": "A wrapper around InheritedWidget to make them easier to use and more reusable.", "dependencies": { "collection": "^1.15.0", "flutter": {"sdk": "flutter"}, "nested": "^1.0.0" }, "publisher": "dash-overflow.net", "homepage": null, "repository": "https://github.com/rrousselGit/provider", "topics": [] }; ================================================ FILE: modules/tools_system/pkg_player/test/science_server/package.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as p; import 'package:pkg_player/pkg_player.dart'; import 'package:unit_env/unit_env.dart'; import 'moke/moke.dart'; void main() async { PackageRequest request = PackageRequest(); String mockDir = p.join( Directory.current.path, 'test', 'science_server', 'moke', ); setUpAll(() async { FxDio().register(Unit3Host()); }); Future getAllPackages() async { ApiRet ret = await request.getAllPackages(); if (ret.success) { print(ret.data); } } Future getInsertPackages() async { ApiRet ret = await request.insertPackage(data); if (ret.success) { print(ret.data); } } Future deletePackages() async { ApiRet ret = await request.deletePackages('provider'); if (ret.success) { print(ret.data); } } Future addCategories() async { File file = File(p.join(mockDir, 'category.json')); String data = await file.readAsString(); List jsonData = jsonDecode(data); for (dynamic e in jsonData) { ApiRet ret = await request.addCategoriesRaw(e); if (ret.success) { print(ret.data); } } } Future getCategories() async { File file = File(p.join(mockDir, 'api_category.json')); ApiRet ret = await request.getCategories(); if (ret.success) { String formattedJson = const JsonEncoder.withIndent(' ').convert(ret.data); await file.writeAsString(formattedJson); print('Categories saved to ${file.path}'); } } Future getCategoriesPackage() async { File file = File(p.join(mockDir, 'api_category_packages.json')); ApiRet ret = await request.getCategoriesPackage(key: 'state_management'); if (ret.success) { String formattedJson = const JsonEncoder.withIndent(' ').convert(ret.data); await file.writeAsString(formattedJson); print('Categories saved to ${file.path}'); } } group('System API Tests', () { test('/packages', getAllPackages); test('/insert', getInsertPackages); test('/delete', deletePackages); test('addCategories', addCategories); test('getCategories', getCategories); test('getCategoriesPackages', getCategoriesPackage); }); } ================================================ FILE: modules/tools_system/pkg_player/test/science_server/sync/model/package_detail.dart ================================================ class PackageDetail { final String name; final String? description; final String? homepage; final String? repository; final String? documentation; final List tags; final String latestVersion; final DateTime updatedAt; final List authors; final String? license; final Map dependencies; final Map devDependencies; final Environment environment; final Publisher? publisher; PackageDetail({ required this.name, this.description, this.homepage, this.repository, this.documentation, required this.tags, required this.latestVersion, required this.updatedAt, required this.authors, this.license, required this.dependencies, required this.devDependencies, required this.environment, this.publisher, }); factory PackageDetail.fromJson(Map json) { final latest = json['latest']; final pubspec = latest['pubspec']; return PackageDetail( name: json['name'], description: json['description'], homepage: json['homepage'], repository: json['repository'], documentation: json['documentation'], tags: List.from(json['tags'] ?? []), latestVersion: latest['version'], updatedAt: DateTime.parse(json['advisoriesUpdated']), authors: List.from(pubspec['authors'] ?? []), license: pubspec['license'], dependencies: Map.from(pubspec['dependencies'] ?? {}), devDependencies: Map.from(pubspec['dev_dependencies'] ?? {}), environment: Environment.fromJson(pubspec['environment'] ?? {}), publisher: json['publisher'] != null ? Publisher.fromJson(json['publisher']) : null, ); } } class Environment { final String? sdk; final String? flutter; Environment({this.sdk, this.flutter}); factory Environment.fromJson(Map json) { return Environment( sdk: json['sdk'], flutter: json['flutter'], ); } } class Publisher { final String publisherId; final String description; Publisher({ required this.publisherId, required this.description, }); factory Publisher.fromJson(Map json) { return Publisher( publisherId: json['publisherId'], description: json['description'], ); } } class PackageMetrics { final int downloadCount; final int downloadCount30Days; final int likeCount; final double popularityScore; final int pubPoints; PackageMetrics({ required this.downloadCount, required this.downloadCount30Days, required this.likeCount, required this.popularityScore, required this.pubPoints, }); factory PackageMetrics.fromJson(Map json) { return PackageMetrics( downloadCount: json['downloadCount'] ?? 0, downloadCount30Days: json['downloadCount30Days'] ?? 0, likeCount: json['likeCount'] ?? 0, popularityScore: (json['popularityScore'] ?? 0.0).toDouble(), pubPoints: json['pubPoints'] ?? 0, ); } } ================================================ FILE: modules/tools_system/pkg_player/test/science_server/sync/model/pub_package.dart ================================================ class PubPackage { final String name; final String? description; final String? homepage; final String? repository; final List tags; final String latestVersion; final DateTime updatedAt; PubPackage({ required this.name, this.description, this.homepage, this.repository, required this.tags, required this.latestVersion, required this.updatedAt, }); factory PubPackage.fromJson(Map json) { return PubPackage( name: json['name'], description: json['description'], homepage: json['homepage'], repository: json['repository'], tags: List.from(json['tags'] ?? []), latestVersion: json['latest']['version'], updatedAt: DateTime.parse(json['updated']), ); } } class PackageVersion { final String version; final DateTime published; final Map dependencies; PackageVersion({ required this.version, required this.published, required this.dependencies, }); factory PackageVersion.fromJson(Map json) { return PackageVersion( version: json['version'], published: DateTime.parse(json['published']), dependencies: Map.from(json['pubspec']['dependencies'] ?? {}), ); } } class PackageScore { final int? grantedPoints; final int? maxPoints; final double? likeCount; final double? popularityScore; PackageScore({ this.grantedPoints, this.maxPoints, this.likeCount, this.popularityScore, }); factory PackageScore.fromJson(Map json) { return PackageScore( grantedPoints: json['grantedPoints'], maxPoints: json['maxPoints'], likeCount: json['likeCount']?.toDouble(), popularityScore: json['popularityScore']?.toDouble(), ); } } class SearchResult { final List packages; final String? next; SearchResult({ required this.packages, this.next, }); factory SearchResult.fromJson(Map json) { return SearchResult( packages: (json['packages'] as List) .map((p) => PubPackage.fromJson(p)) .toList(), next: json['next'], ); } } ================================================ FILE: modules/tools_system/pkg_player/test/science_server/sync/package_repository.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as p; import 'package:pkg_player/pkg_player.dart'; import 'pub_repository.dart'; abstract class PackageRepository { Future syncPackage(String name); Future> syncPackages(List packageNames, {int batchSize = 5}); Future createPackageFiles( String fileDir,List jsonData); } class PubPackageRepository extends PackageRepository { PubRepository _repo = PubRepository(); @override Future syncPackage(String name) async { List results = await Future.wait([ _repo.getPackageDetail(name), _repo.getPackageMetrics(name), ]); dynamic detail = results[0]; dynamic ret1 = results[1]; dynamic latest = detail['latest']; dynamic pubspec = latest['pubspec']; List tags = ret1['score']['tags']; String? publisher; for (String value in tags) { if (value.startsWith('publisher:')) { publisher = value.replaceAll('publisher:', ''); break; } } return PluginModel.fromJson({ "name": detail['name'], "last_version": latest['version'], "last_publish": latest['published'], "statistics": { "likes": ret1['score']['likeCount'], "points": ret1['score']['grantedPoints'], "downloads": ret1['score']['downloadCount30Days'], }, "tags": ret1['score']['tags'], "desc": pubspec['description'], "publisher": publisher, "repository": pubspec['repository'], "homepage": pubspec['homepage'], "dependencies": pubspec['dependencies'], "topics": pubspec['topics'] }); } @override Future> syncPackages(List packageNames, {int batchSize = 5}) async { List results = []; for (int i = 0; i < packageNames.length; i += batchSize) { int end = (i + batchSize < packageNames.length) ? i + batchSize : packageNames.length; List batch = packageNames.sublist(i, end); List> futures = batch.map((name) async { try { return await syncPackage(name); } catch (e) { print('Failed to sync package $name: $e'); return null; } }).toList(); List batchResults = await Future.wait(futures); results.addAll( batchResults.where((model) => model != null).cast()); } return results; } //{ // "category": "ui", // "packages": [ // "macos_ui", // "fluent_ui", // "tolyui", // ] // } @override Future createPackageFiles( String fileDir, List jsonData) async { for (dynamic e in jsonData) { File outFile = File(p.join(fileDir, e['category']+".json")); List pkgs = []; for (dynamic pkg in e['packages']) { pkgs.add(await syncPackage(pkg)); print("SYNC SUCCESS $pkg"); } String formattedJson = const JsonEncoder.withIndent(' ').convert(pkgs); await outFile.writeAsString(formattedJson); } return; } } ================================================ FILE: modules/tools_system/pkg_player/test/science_server/sync/pub_repository.dart ================================================ import 'dart:convert'; import 'package:http/http.dart' as http; import 'model/pub_package.dart'; class PubRepository { static const String _baseUrl = 'https://pub.dev/api'; final http.Client _client; PubRepository({http.Client? client}) : _client = client ?? http.Client(); /// 搜索包 Future searchPackages({ required String query, int page = 1, String? sort, }) async { final uri = Uri.parse('$_baseUrl/search').replace(queryParameters: { 'q': query, 'page': page.toString(), if (sort != null) 'sort': sort, }); final response = await _client.get(uri); if (response.statusCode == 200) { return SearchResult.fromJson(json.decode(response.body)); } throw Exception('Failed to search packages: ${response.statusCode}'); } /// 获取包详情 Future getPackage(String packageName) async { final response = await _client.get(Uri.parse('$_baseUrl/packages/$packageName')); if (response.statusCode == 200) { return PubPackage.fromJson(json.decode(response.body)); } throw Exception('Failed to get package: ${response.statusCode}'); } /// 获取包详细信息(包含依赖等) Future getPackageDetail(String packageName) async { final response = await _client.get(Uri.parse('$_baseUrl/packages/$packageName')); if (response.statusCode == 200) { return json.decode(response.body); } throw Exception('Failed to get package detail: ${response.statusCode}'); } /// 获取包的所有版本 Future> getPackageVersions(String packageName) async { final response = await _client .get(Uri.parse('$_baseUrl/packages/$packageName/versions')); if (response.statusCode == 200) { final data = json.decode(response.body); return (data['versions'] as List) .map((v) => PackageVersion.fromJson(v)) .toList(); } throw Exception('Failed to get package versions: ${response.statusCode}'); } /// 获取特定版本信息 Future getPackageVersion( String packageName, String version) async { final response = await _client .get(Uri.parse('$_baseUrl/packages/$packageName/versions/$version')); if (response.statusCode == 200) { return PackageVersion.fromJson(json.decode(response.body)); } throw Exception('Failed to get package version: ${response.statusCode}'); } /// 获取包评分 Future getPackageScore(String packageName) async { final response = await _client.get(Uri.parse('$_baseUrl/packages/$packageName/score')); if (response.statusCode == 200) { return PackageScore.fromJson(json.decode(response.body)); } throw Exception('Failed to get package score: ${response.statusCode}'); } /// 获取包的指标数据 Future getPackageMetrics(String packageName) async { final response = await _client.get(Uri.parse('$_baseUrl/packages/$packageName/metrics')); if (response.statusCode == 200) { return json.decode(response.body); } throw Exception('Failed to get package metrics: ${response.statusCode}'); } /// 获取包的发布者信息 Future> getPackagePublisher(String packageName) async { final response = await _client .get(Uri.parse('$_baseUrl/packages/$packageName/publisher')); if (response.statusCode == 200) { return json.decode(response.body); } throw Exception('Failed to get package publisher: ${response.statusCode}'); } /// 获取热门包列表 Future> getPopularPackages({int limit = 10}) async { final result = await searchPackages( query: '', sort: 'popularity', ); return result.packages.take(limit).toList(); } /// 获取最新包列表 Future> getLatestPackages({int limit = 10}) async { final result = await searchPackages( query: '', sort: 'updated', ); return result.packages.take(limit).toList(); } /// 按标签搜索 Future searchByTag(String tag, {int page = 1}) async { return searchPackages(query: 'tags:$tag', page: page); } /// 按依赖搜索 Future searchByDependency(String dependency, {int page = 1}) async { return searchPackages(query: 'dependency:$dependency', page: page); } void dispose() { _client.close(); } } ================================================ FILE: modules/tools_system/pkg_player/test/science_server/sync.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as p; import 'package:pkg_player/pkg_player.dart'; import 'sync/package_repository.dart'; void main() { String mockDir = p.join( Directory.current.path, 'test', 'science_server', 'moke', ); PackageRequest request = PackageRequest(); group('sync package', () { late PackageRepository repository; setUp(() { repository = PubPackageRepository(); FxDio().register(Unit3Host()); }); /// 插入所有的分类 Future insertAllCategories() async { File file = File(p.join(mockDir, 'category.json')); String data = await file.readAsString(); List jsonData = jsonDecode(data); for (dynamic e in jsonData) { ApiRet ret = await request.addCategoriesRaw(e); if (ret.success) { print(ret.data); } } } /// 插入所有的分类-包映射 Future handleCategoryPackage() async { File file = File(p.join(mockDir, 'cate_pkgs.json')); Directory outputDir = Directory(p.join(mockDir, 'output', 'category_packages')); if (!outputDir.existsSync()) { outputDir.createSync(recursive: true); } String data = await file.readAsString(); List jsonData = jsonDecode(data); await repository.createPackageFiles(outputDir.path, jsonData); } /// 插入把分类-包映射 通过接口插入远程数据库 Future syncToServer() async { Directory outputDir = Directory(p.join(mockDir, 'output', 'category_packages')); if (!outputDir.existsSync()) { return; } List entities = outputDir.listSync(); for (FileSystemEntity file in entities) { if (file is File) { String data = await file.readAsString(); List jsonData = jsonDecode(data); String category = p.basenameWithoutExtension(file.path); await request.insertPackages(category, jsonData); print('同步成功:$category'); } } } test('insertAllCategories', insertAllCategories); test('handleCategoryPackage', handleCategoryPackage); test('syncToServer', syncToServer); }); } ================================================ FILE: modules/tools_system/pkg_player/test/science_server/system.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:unit_env/unit_env.dart'; void main() async { late Host host; setUpAll(() async { host = Unit3Host(); FxDio().register(host); // initHttp(app); // request = HouseDetailRequest(); }); group('System API Tests', () { test('/v', () async { await host.get('/v', convertor: (e) { print(e); }); }); }); } ================================================ FILE: modules/tools_system/pkg_player/test/science_server/test_comments.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as p; import 'package:pkg_player/pkg_player.dart'; import 'package:unit_env/unit_env.dart'; void main() async { PackageRequest request = PackageRequest(); String mockDir = p.join( Directory.current.path, 'test', 'science_server', 'moke', 'output', ); setUpAll(() async { FxDio().register(Unit3Host()); }); Future getPackageComments() async { ApiRet ret = await request.getPackageComments(83); if (ret.success) { File file = File(p.join(mockDir, 'api_comments.json')); await file.parent.create(recursive: true); String formattedJson = const JsonEncoder.withIndent(' ').convert({ 'status': true, 'msg': '请求成功!', ...ret.data.toJson(), }); await file.writeAsString(formattedJson); print('Comments saved to ${file.path}'); print('Total comments: ${ret.data.total}'); } else { print('Failed to get comments: ${ret.msg}'); } } group('Comments API Tests', () { test('/packages/83/comments', getPackageComments); }); } ================================================ FILE: modules/tools_system/pkg_player/test/scripts/analyze_packages.dart ================================================ import 'dart:io'; import 'dart:convert'; import 'package:path/path.dart' as path; void main() async { final directoryPath = path.join('test', 'science_server', 'moke', 'output', 'category_packages'); final directory = Directory(directoryPath); final Map packageMapping = {}; await for (final file in directory.list()) { if (file is File && path.extension(file.path) == '.json') { final content = await file.readAsString(); final List packages = json.decode(content); for (final package in packages) { final name = package['name'] as String; final desc = package['desc'] as String? ?? ''; packageMapping[name] = desc; } } } final outputPath = path.join('test', 'science_server', 'moke', 'output', 'package_desc_mapping.json'); final outputFile = File(outputPath); await outputFile.writeAsString(json.encode(packageMapping)); print('已分析 ${packageMapping.length} 个包,结果保存到 $outputPath'); } ================================================ FILE: modules/tools_system/pkg_player/test/scripts/find_untranslated.dart ================================================ import 'dart:io'; import 'dart:convert'; import 'package:path/path.dart' as path; void main() async { // 读取已翻译的包 final descZhPath = path.join('test', 'science_server', 'moke', 'output', 'desc_zh.json'); final descZhFile = File(descZhPath); final descZhContent = await descZhFile.readAsString(); final Map translatedPackages = json.decode(descZhContent); // 读取所有包的映射 final mappingPath = path.join('test', 'science_server', 'moke', 'output', 'package_desc_mapping.json'); final mappingFile = File(mappingPath); final mappingContent = await mappingFile.readAsString(); final Map allPackages = json.decode(mappingContent); // 找出未翻译的包 final Map untranslatedPackages = {}; allPackages.forEach((name, desc) { if (!translatedPackages.containsKey(name)) { untranslatedPackages[name] = desc.toString(); } }); final outputPath = path.join('test', 'science_server', 'moke', 'output', 'translation_todo.json'); final outputFile = File(outputPath); await outputFile.writeAsString(json.encode(untranslatedPackages)); print('发现 ${untranslatedPackages.length} 个未翻译的包,保存到 $outputPath'); } ================================================ FILE: modules/tools_system/pkg_player/test/scripts/get_ipv4.dart ================================================ import 'dart:io'; void main() async { try { final interfaces = await NetworkInterface.list(); for (final interface in interfaces) { for (final addr in interface.addresses) { if (addr.type == InternetAddressType.IPv4 && !addr.isLoopback) { print('设备IPv4地址: ${addr.address}'); print('网络接口: ${interface.name}'); return; } } } print('未找到IPv4地址'); } catch (e) { print('获取IP地址失败: $e'); } } ================================================ FILE: modules/tools_system/pkg_player/test/scripts/insert_chinese_desc.dart ================================================ import 'dart:io'; import 'dart:convert'; void main() async { // 读取中文描述映射 final descZhFile = File('test/science_server/moke/output/desc_zh.json'); final descZhContent = await descZhFile.readAsString(); final Map chineseDescriptions = json.decode(descZhContent); // 处理所有分类文件 final directory = Directory('test/science_server/moke/output/category_packages'); int updatedFiles = 0; int updatedPackages = 0; await for (final file in directory.list()) { if (file is File && file.path.endsWith('.json')) { final content = await file.readAsString(); final List packages = json.decode(content); bool fileModified = false; for (final package in packages) { final name = package['name'] as String; if (chineseDescriptions.containsKey(name)) { package['descZh'] = chineseDescriptions[name]; fileModified = true; updatedPackages++; } } if (fileModified) { final updatedContent = json.encode(packages); await file.writeAsString(updatedContent); updatedFiles++; print('已更新文件: ${file.path.split('/').last}'); } } } print('完成!更新了 $updatedFiles 个文件,$updatedPackages 个包添加了中文描述'); } ================================================ FILE: modules/tools_system/pkg_player/test/scripts/update_dev_ip.dart ================================================ import 'dart:io'; import 'package:path/path.dart' as p; void main() async { // 获取当前IP String? currentIP; try { final interfaces = await NetworkInterface.list(); for (final interface in interfaces) { for (final addr in interface.addresses) { if (addr.type == InternetAddressType.IPv4 && !addr.isLoopback) { currentIP = addr.address; break; } } if (currentIP != null) break; } } catch (e) { print('获取IP失败: $e'); return; } if (currentIP == null) { print('未找到IPv4地址'); return; } // 查找并替换文件中的dev IP final hostFile = File( r'D:\Projects\Flutter\toly1994\FlutterUnit\modules\basic_system\unit_env\lib\src\host.dart'); if (!await hostFile.exists()) { print('未找到host文件'); return; } try { String content = await hostFile.readAsString(); // 使用正则表达式替换dev IP final regex = RegExp(r"(HostEnv\.dev:\s*')([^']+)(')"); final newContent = content.replaceAllMapped(regex, (match) => '${match.group(1)}$currentIP${match.group(3)}'); if (content != newContent) { await hostFile.writeAsString(newContent); print('✅ 已将dev IP更新为: $currentIP'); } else { print('⚠️ 未找到需要替换的dev IP配置'); } } catch (e) { print('替换失败: $e'); } } ================================================ FILE: modules/tools_system/treasure_tools/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ build/ ================================================ FILE: modules/tools_system/treasure_tools/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" channel: "stable" project_type: package ================================================ FILE: modules/tools_system/treasure_tools/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/tools_system/treasure_tools/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/tools_system/treasure_tools/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/tools_system/treasure_tools/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/tools_system/treasure_tools/lib/src/bloc/state.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import '../model/class.dart'; class ClassGenBloc extends Cubit{ ClassGenBloc():super(Class(fields: [], name: '')); } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/class_generator.dart ================================================ import 'dart:io'; import 'package:path/path.dart' as path; import 'model/class.dart'; import 'model/field.dart'; main() { // Class clazz1 = Class( // name: 'TaskResult', // fields: [ // Field(name: 'cost', type: 'int'), // Field(name: 'taskName', type: 'String'), // Field(name: 'count', type: 'int'), // Field(name: 'taskCode', type: 'String'), // Field(name: 'taskInfo', type: 'String', nullable: true, isRequired: false), // ], // ); // print(clazz1.buildClass()); // Class clazz2 = Class( // name: 'User', // fields: [ // Field(name: 'age', type: int), // Field(name: 'username', type: String), // Field(name: 'roleId', type: int), // Field(name: 'info', type: String,nullable: true), // Field(name: 'height', type: double, nullable: true, isRequired: false), // ], // ); // clazz1.write2File(Directory(r'E:\Projects\Flutter\Github\skeleton\lib\chatgpt\generator')); // clazz2.write2File(Directory(r'E:\Projects\Flutter\Github\skeleton\lib\chatgpt\generator')); } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/code_gen_page.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'class_generator.dart'; import 'desk_widget_top_bar.dart'; import 'icon_font_gen/icon_font_gen_page.dart'; import 'model/class.dart'; import 'model/field.dart'; import 'popable/class_gen_field.dart'; import 'popable/toly_select.dart'; import 'view/json_display/json_display.dart'; class A {} class CodeGenPage extends StatefulWidget { const CodeGenPage({Key? key}) : super(key: key); @override State createState() => _CodeGenPageState(); } class _CodeGenPageState extends State { TextEditingController _dirPath = TextEditingController(); final PageController _ctrl = PageController(); int selectIndex = 0; final List selectData = [ "final", "static", 'static const', ]; Class clazz1 = Class( name: '', fields: [ ], ); @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Scaffold( body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DeskCodeGenTopBar( onTapGen: _doGen, onTabPressed: (int value) { _ctrl.jumpToPage(value); }, ), Expanded(child: PageView( controller:_ctrl, children: [ IconFontGenPage(), JsonAnalysisTool(), Center( child: Text( '敬请期待' ), ), Center( child: Text( '敬请期待' ), ) ], )), if(false) Padding( padding: const EdgeInsets.only(left: 20,top: 20), child: Row( children: [ GestureDetector( onTap: () async{ // final String? directoryPath = await getDirectoryPath(); // if (directoryPath != null) { // print("====$directoryPath========="); // _dirPath.text = directoryPath; // } }, child: Icon(Icons.file_copy_outlined)), SizedBox(width: 20,), Expanded(child: TextField( controller: _dirPath, )), SizedBox(width: 20,), ], ), ), if(false) Expanded( child: Padding( padding: const EdgeInsets.all(20.0), child: ClassGenField( clazz: clazz1, ), )) ], ), ); } void _onChange(int index) { selectIndex = index; setState(() {}); } void _doGen() { // String className = _classNameCtrl.text; // String fieldType = _fieldTypeCtrl.text; // String fieldName = _fieldNameCtrl.text; // String modifier = selectData[selectIndex]; // // print( // "===$className===$fieldType====$fieldName=======$modifier==================="); // Class clazz = Class( // name: className, // fields: [ // // Field(name: 'cost', type: int), // // Field(name: 'taskName', type: String), // // Field(name: 'count', type: int), // // Field(name: 'taskCode', type: String), // Field( // name: fieldName, // type: fieldType, // nullable: true, // isRequired: false), // ], // ); print(clazz1.buildClass()); if(_dirPath.text.isNotEmpty){ clazz1.write2File(Directory(_dirPath.text)); } } } class GenInput extends StatelessWidget { final String hintText; final String label; final TextEditingController? controller; const GenInput( {Key? key, required this.hintText, this.controller, required this.label}) : super(key: key); @override Widget build(BuildContext context) { return Wrap( direction: Axis.vertical, children: [ Padding( padding: const EdgeInsets.all(6.0), child: Text( label, style: TextStyle(fontSize: 12), ), ), SizedBox( width: 150, height: 30, child: TextField( controller: controller, style: TextStyle(fontSize: 14), decoration: InputDecoration( filled: true, hoverColor: Colors.transparent, contentPadding: EdgeInsets.only(top: 0, left: 15), fillColor: Color(0xffF1F2F3), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Theme.of(context).primaryColor), borderRadius: BorderRadius.all(Radius.circular(6)), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.all(Radius.circular(6)), ), hintText: hintText, hintStyle: TextStyle(fontSize: 12, color: Colors.grey)), ), ), ], ); } } class GenCheckBox extends StatelessWidget { final String label; final bool checked; final ValueChanged? onChanged; const GenCheckBox({ Key? key, required this.label, required this.checked, this.onChanged, }) : super(key: key); @override Widget build(BuildContext context) { return Wrap( crossAxisAlignment: WrapCrossAlignment.center, direction: Axis.vertical, children: [ Checkbox(value: checked, onChanged: onChanged), Text( label, style: TextStyle(fontSize: 12), ), ], ); } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/data/task_result.dart ================================================ class TaskResult { int cost; String taskName; int count; String taskCode; String taskInfo; TaskResult({ required this.cost, required this.taskName, required this.count, required this.taskCode, required this.taskInfo, }); TaskResult copyWith({ int? cost, String? taskName, int? count, String? taskCode, String? taskInfo, }) => TaskResult( cost: cost ?? this.cost, taskName: taskName ?? this.taskName, count: count ?? this.count, taskCode: taskCode ?? this.taskCode, taskInfo: taskInfo ?? this.taskInfo, ); static TaskResult fromJson(Map map) => TaskResult( cost: map["cost"] , taskName: map["taskName"] , count: map["count"] , taskCode: map["taskCode"] , taskInfo: map["taskInfo"] , ); Map toJson()=>{ "cost": cost, "taskName": taskName, "count": count, "taskCode": taskCode, "taskInfo": taskInfo, }; } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/desk_widget_top_bar.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; class DeskCodeGenTopBar extends StatefulWidget { final ValueChanged onTabPressed; final VoidCallback onTapGen; const DeskCodeGenTopBar({Key? key,required this.onTabPressed, required this.onTapGen}) : super(key: key); @override State createState() => _DeskCodeGenTopBarState(); } class _DeskCodeGenTopBarState extends State with SingleTickerProviderStateMixin { late TabController tabController; static const List _tabs = ['IconFont','Json 解析', '数据类' , '状态管理',]; @override void initState() { super.initState(); tabController = TabController(length: _tabs.length, vsync: this); } @override Widget build(BuildContext context) { Color themeColor = Theme.of(context).primaryColor; bool isDark = Theme.of(context).brightness == Brightness.dark; return DragToMoveWrapper( child: Container( height: 64, color: isDark?Color(0xff2C3036):Colors.white, child: Row( children: [ const SizedBox(width: 12,), SizedBox( width: 350, child: TabBar( onTap: widget.onTabPressed, indicatorSize: TabBarIndicatorSize.label, labelPadding: const EdgeInsets.symmetric(horizontal: 6), isScrollable: false, indicator: RoundRectTabIndicator( borderSide: BorderSide(color: themeColor, width: 3), ), labelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), controller: tabController, labelColor: themeColor, indicatorWeight: 3, unselectedLabelColor: Colors.grey, indicatorColor: themeColor, tabs: _tabs.map((String name) => Tab(text: name)).toList(), ), ), Spacer(), const SizedBox(width: 20,), WindowButtons(), ], ), ), ); } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/icon_font_gen/gen_message_action.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; class GenMessageAction extends StatelessWidget { final VoidCallback onGen; const GenMessageAction({Key? key, required this.onGen}) : super(key: key); @override Widget build(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Text( '使用方式:\n1. 在 iconfont.cn 挑选图标,加入项目,下载压缩包。\n2. 选择 Flutter 项目地址,配置资源、产物文件位置。\n3. 点击生成代码按钮,即可生成相关代码。', style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold), )), ElevatedButton( style: ElevatedButton.styleFrom( elevation: 0, shape: const StadiumBorder()), onPressed: onGen, child: Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: 4, children: [ Icon( TolyIcon.icon_fast, size: 16, ), const Text( '生成代码', style: TextStyle(height: 1.1, fontSize: 12), ), ], )), ], ); } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/icon_font_gen/icon_font_class_parser.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:archive/archive_io.dart'; import 'icon_font_gen_config.dart'; import 'package:path/path.dart' as path; class IconFontClassParser{ void gen(IconFontGenConfig config){ final inputStream = InputFileStream(config.srcZip); // 将压缩包有用资源解压到目标文件 final archive = ZipDecoder().decodeStream(inputStream); for (var file in archive.files) { if (file.isFile) { if (file.name.endsWith('.ttf')) { final outputStream = OutputFileStream(config.ttfDistPath); file.writeContent(outputStream); outputStream.close(); } if (file.name.endsWith('.json')) { dynamic data = file.content; String jsonContent = utf8.decode(data); String resultCode = parser(jsonContent,config.fontFamily); File distFile = File(config.distFilePath); if(!distFile.existsSync()){ distFile.createSync(recursive: true); } distFile.writeAsStringSync(resultCode); setYaml(config); } } } } String parser(String input,String fontFamily){ dynamic map = json.decode(input); List glyphs = map['glyphs'] as List; String code = ''; for(int i=0;i lines = pubspecFile.readAsLinesSync(); RegExp fontsRegex = RegExp(r'^ fonts:',multiLine: true); bool hasFonts = fontsRegex.hasMatch(lines.join('\n')); if(!hasFonts){ // 当前没有 fonts 节点,需要添加到 flutter 节点下 int index = lines.indexWhere((e) => e.startsWith('flutter:')); List fonts = [ ' fonts:', ' - family: $familyName', ' fonts:', ' - asset: $fontAssetsDist', ]; lines.insertAll(index+1, fonts); pubspecFile.writeAsStringSync(lines.join('\n')); return; } // 存在 fonts 节点,查询 family ,有没有当前字体图标 bool hasTargetFamily = false; RegExp regExp = RegExp(r'^ +- family: +(\w+)'); for(int i=0;i e.startsWith(fontsRegex)); List fonts = [ ' - family: $familyName', ' fonts:', ' - asset: $fontAssetsDist', ]; lines.insertAll(index+1, fonts); pubspecFile.writeAsStringSync(lines.join('\n')); } } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/icon_font_gen/icon_font_gen_config.dart ================================================ import 'package:path/path.dart' as path; class IconFontGenConfig { final String srcZip; final String projectPath; final String assetsDist; final String fileDist; IconFontGenConfig({ this.srcZip = '', this.projectPath = '', String? assetsDist, String? fileDist, }) : fileDist = fileDist ?? 'lib${spa}app${spa}gen${spa}toly_icon.dart', assetsDist = assetsDist ?? 'assets${spa}iconfont'; static String get spa => path.separator; String get distFilePath => path.join(projectPath, fileDist); String get distAssetsDir => path.join(projectPath, assetsDist); String get ttfDistPath => path.join(distAssetsDir, path.basenameWithoutExtension(fileDist)+".ttf"); String get yamlAssetDist => assetsDist.replaceAll('\\', '/')+"/"+path.basename(ttfDistPath); String get fontFamily => path .basenameWithoutExtension(fileDist) .split('_') .map((e) => e[0].toUpperCase() + e.substring(1,)) .join(''); factory IconFontGenConfig.fromJson(Map map) { return IconFontGenConfig( srcZip: map['srcZip'] ?? '', projectPath: map['projectPath'] ?? '', assetsDist: map["assetsDist"] ?? '', fileDist: map["fileDist"] ?? ''); } Map toJson() => { 'srcZip': srcZip, 'projectPath': projectPath, 'assetsDist': assetsDist, 'fileDist': fileDist, }; } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/icon_font_gen/icon_font_gen_page.dart ================================================ import 'dart:convert'; import 'package:app/app.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:path/path.dart' as path; import 'package:shared_preferences/shared_preferences.dart'; import 'package:utils/utils.dart'; import 'gen_message_action.dart'; import 'icon_font_class_parser.dart'; import 'icon_font_gen_config.dart'; class IconFontGenPage extends StatefulWidget { const IconFontGenPage({Key? key}) : super(key: key); @override State createState() => _IconFontGenPageState(); } class _IconFontGenPageState extends State with AutomaticKeepAliveClientMixin { final TextEditingController _projectCtrl = TextEditingController(); final TextEditingController _iconFontCtrl = TextEditingController(); final TextEditingController _iconFontAssetsCtrl = TextEditingController(); final TextEditingController _resultFileCtrl = TextEditingController(); // String get spa => path.separator; late SharedPreferences _sp; SharedPreferences get sp => _sp; @override void initState() { super.initState(); _initData(); } IconFontGenConfig config = IconFontGenConfig(); void _initData() async{ _sp = await SharedPreferences.getInstance(); String? configStr = sp.getString(SpKey.iconFontGenConfig); if(configStr!=null){ config = IconFontGenConfig.fromJson(json.decode(configStr)); } _iconFontAssetsCtrl.text = config.assetsDist; _resultFileCtrl.text = config.fileDist; _projectCtrl.text = config.projectPath; _iconFontCtrl.text = config.srcZip; } @override Widget build(BuildContext context) { super.build(context); return Center( child: SizedBox( width: 600, child: Column( children: [ const SizedBox( height: 8, ), FileSelectorInput( controller: _iconFontCtrl, label: 'Iconfont 压缩包路径', // controller: clazz.nameCtrl, hintText: '请选择或输入 iconfont 下载的压缩包路径', ), const SizedBox(height: 10), FileSelectorInput( pickerDir: true, controller: _projectCtrl, label: '项目路径', // controller: clazz.nameCtrl, hintText: '请选择或输入项目地址', ), const SizedBox( height: 10, ), Row( children: [ Expanded( child: LabelInputInput( controller: _iconFontAssetsCtrl, label: '资源目录', hintText: 'iconfont 资源存放位置', ), ), const SizedBox(width: 20), Expanded( child: LabelInputInput( controller: _resultFileCtrl, label: '产物位置', hintText: '代码类存放位置', ), ), ], ), Expanded( child: Align( alignment: Alignment(1, -0.8), child: GenMessageAction( onGen: doGen, ), )) ], ), ), ); } final IconFontClassParser parser = IconFontClassParser(); void doGen() { if (_iconFontCtrl.text.isEmpty) return; if (_projectCtrl.text.isEmpty) return; IconFontGenConfig config = IconFontGenConfig( assetsDist: _iconFontAssetsCtrl.text, fileDist: _resultFileCtrl.text, projectPath: _projectCtrl.text, srcZip: _iconFontCtrl.text, ); parser.gen(config); Toast.success(context, '生成代码成功!'); sp.setString(SpKey.iconFontGenConfig,json.encode(config)); } @override bool get wantKeepAlive => true; } class LabelInputInput extends StatelessWidget { final String hintText; final String label; final TextEditingController? controller; const LabelInputInput({ Key? key, required this.hintText, this.controller, required this.label, }) : super(key: key); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; return Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, // direction: Axis.vertical, children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 6), child: Text( label, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), ), Expanded( child: SizedBox( height: 35, child: TextField( controller: controller, style: TextStyle(fontSize: 14), decoration: InputDecoration( filled: true, hoverColor: Colors.transparent, contentPadding: EdgeInsets.only(top: 0, left: 15), fillColor: isDark?null:Color(0xffF1F2F3), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Theme.of(context).primaryColor), borderRadius: BorderRadius.all(Radius.circular(6)), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: isDark?Color(0xff2C3036):Color(0xffE2E7EE)), borderRadius: BorderRadius.all(Radius.circular(6)), ), hintText: hintText, hintStyle: TextStyle(fontSize: 12, color: Colors.grey)), ), ), ), ], ); } } class FileSelectorInput extends StatelessWidget { final String hintText; final String label; final bool pickerDir; final TextEditingController? controller; const FileSelectorInput( {Key? key, required this.hintText, this.controller, required this.label, this.pickerDir = false}) : super(key: key); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, // direction: Axis.vertical, children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 6), child: Text( label, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), ), SizedBox( height: 35, child: TextField( controller: controller, style: TextStyle(fontSize: 14), decoration: InputDecoration( suffixIconConstraints: BoxConstraints(maxWidth: 80), suffixIcon: Row( children: [ VerticalDivider( width: 1, ), Expanded( child: MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: _showSelectFile, child: Center( child: Text( "选择", style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).primaryColor), )), ), )), ], ), filled: true, hoverColor: Colors.transparent, contentPadding: EdgeInsets.only(top: 0, left: 15), fillColor: isDark?null:Color(0xffF1F2F3), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Theme.of(context).primaryColor), borderRadius: BorderRadius.all(Radius.circular(6)), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: isDark?Color(0xff2C3036):Color(0xffE2E7EE)), borderRadius: BorderRadius.all(Radius.circular(6)), ), hintText: hintText, hintStyle: TextStyle(fontSize: 12, color: Colors.grey)), ), ), ], ); } void _showSelectFile() async { String? path; if (pickerDir) { path = await FilePicker.platform.getDirectoryPath(); } else { FilePickerResult? result = await FilePicker.platform.pickFiles(); if (result != null) { path = result.files.single.path; } else { // User canceled the picker } } if (path != null) { controller?.text = path; } else { // User canceled the picker } } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/model/class.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path/path.dart' as path; import 'field.dart'; class Class { final List fields; final String name; final bool toJson; final bool fromJson; final bool copyWith; Class({ required this.fields, this.toJson = true, this.fromJson = true, this.copyWith = true, required this.name, }); Class copy({ List? fields, String? name, bool? toJson, bool? fromJson, bool? copyWith, }) { return Class( fields: fields ?? this.fields, name: name ?? this.name, toJson: toJson ?? this.toJson, fromJson: fromJson ?? this.fromJson, copyWith: copyWith ?? this.copyWith, ); } Future write2File(Directory directory) { File file = File(path.join(directory.path, "${fileName}.dart")); return file.writeAsString(buildClass()); } RegExp regExp = RegExp(r'[A-Z].*?(?=([A-Z]|\b))'); String get fileName { return regExp .allMatches(name) .map((e) => e.group(0)?.toLowerCase()) .join("_"); } String buildClass() { String defines = ''; String constructParams = ''; String toJsonStatements = ''; String fromJsonStatements = ''; String copyWithParamStatements = ''; String copyWithStatements = ''; for (int i = 0; i < fields.length; i++) { Field field = fields[i]; defines += "${field.defineStatement}\n"; constructParams += field.paramStatement; toJsonStatements += field.toJsonStatement; fromJsonStatements += field.fromJsonStatement; copyWithParamStatements += field.copyWithParamStatement; copyWithStatements += field.copyWithStatement; if (i != fields.length - 1) { constructParams += '\n'; toJsonStatements += '\n'; fromJsonStatements += '\n'; copyWithParamStatements += '\n'; copyWithStatements += '\n'; } } String result = """ class $name { $defines $name({ $constructParams }); $name copyWith({ $copyWithParamStatements }) => $name( $copyWithStatements ); static $name fromJson(Map map) => $name( $fromJsonStatements ); Map toJson()=>{ $toJsonStatements }; } """; return result; } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/model/field.dart ================================================ import 'package:flutter/material.dart'; class Field { final String modifier; final String name; final bool nullable; final bool isRequired; final String type ; Field({ this.nullable = false, this.isRequired = true, this.modifier = '', required this.type, required this.name, }); String get defineStatement { String nullableArg = nullable ? '?' : ''; return ' $modifier $type$nullableArg $name;'; } String get paramStatement { String requiredArg = isRequired ? 'required' : ''; return ' $requiredArg this.$name,'; } String get toJsonStatement { return ' "$name": $name,'; } String get fromJsonStatement { return ' $name: map["$name"] ,'; } String get copyWithParamStatement { return ' $type? $name,'; } String get copyWithStatement { return ' $name: $name ?? this.$name,'; } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/popable/class_gen_field.dart ================================================ import 'package:flutter/material.dart'; import '../code_gen_page.dart'; import '../model/class.dart'; import '../model/field.dart'; import 'toly_select.dart'; class ClassGenField extends StatelessWidget { final Class clazz; const ClassGenField({Key? key,required this.clazz}) : super(key: key); final List selectData = const [ "final", "static", 'static const', ]; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ GenInput( label: '类名', // controller: clazz.nameCtrl, hintText: '输入类名', ), const SizedBox( width: 20, ), Wrap( spacing: 20, children: [ GenCheckBox( label: 'toJson', checked: true,onChanged: (v){}, ), GenCheckBox( label: 'fromJson', checked: true,onChanged: (v){}, ), GenCheckBox( label: 'copyWith', checked: true,onChanged: (v){}, ), ], ) ], ), const SizedBox( height: 20, ), Expanded( child: ListView.separated( itemCount: clazz.fields.length, separatorBuilder: (_,__)=> const SizedBox(height: 10,), itemBuilder: (_,index) { Field field = clazz.fields[index]; return Row( children: [ GenInput( label: '类型', // controller: field.typeCtrl, hintText: '输入字段类型名', ), const SizedBox( width: 20, ), GenInput( label: '变量名', // controller: field.nameCtrl, hintText: '输入字段变量名', ), const SizedBox( width: 20, ), Wrap( direction: Axis.vertical, children: [ Padding( padding: const EdgeInsets.all(6.0), child: const Text('修饰',style: TextStyle(fontSize: 12),), ), TolySelect( height: 30, width: 120, change: (int index){ // field.typeCtrl.text = selectData[index]; }, activeIndex: selectData.indexOf(field.modifier)==-1?0:selectData.indexOf(field.modifier), data: selectData, ) ], ), const SizedBox( width: 20, ), GenCheckBox( label: 'nullable', checked: field.nullable,onChanged: (v){}, ), ], ); }), ) , ], ); } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/popable/toly_select.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../wrapper.dart'; typedef OnDropSelected = void Function(int index); class TolySelectTapRegion extends TapRegion { /// Creates a const [TextFieldTapRegion]. /// /// The [child] field is required. const TolySelectTapRegion({ super.key, required super.child, super.enabled, super.onTapOutside, super.onTapInside, super.debugLabel, }) : super(groupId: TolySelect); } class TolySelect extends StatefulWidget { final List data; final int activeIndex; final OnDropSelected change; final Color disableColor; final double iconSize; final double height; final double width; final double fontSize; const TolySelect( {Key? key, this.data = const [], required this.activeIndex, required this.change, this.disableColor = const Color(0xffdcdfe6), this.iconSize = 20, this.height = 40, this.width = 240, this.fontSize = 14, }) : super(key: key); @override _TolySelectState createState() => _TolySelectState(); } class _TolySelectState extends State with SingleTickerProviderStateMixin { late FocusNode _node; bool _focused = false; late FocusAttachment _nodeAttachment; OverlayEntry? _overlayEntry; late AnimationController _ctrl; late Animation animation; final LayerLink layerLink = LayerLink(); // int _selectedIndex = 0; @override void initState() { super.initState(); _ctrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 200), ); animation = Tween(begin: 0, end: pi).animate(_ctrl); _node = FocusNode() ..addListener(() { if (_node.hasFocus != _focused) { if (!_focused) { _ctrl.forward(); _showOverlay(); } else { _hideOverlay(); _ctrl.reverse(); } setState(() { _focused = _node.hasFocus; }); } }); _nodeAttachment = _node.attach(context); } @override void dispose() { _node.dispose(); _ctrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { _nodeAttachment.reparent(); return TolySelectTapRegion( onTapOutside: (PointerDownEvent event){ _node.unfocus(); }, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { if (_focused) { _node.unfocus(); } else { _node.requestFocus(); } }, child: CompositedTransformTarget( link: layerLink, child: buildTarget(), ), ), ); } void _showOverlay() { _overlayEntry = _createOverlayEntry(); Overlay.of(context)?.insert(_overlayEntry!); } void _hideOverlay() { _overlayEntry?.remove(); } Widget buildTarget() { return Container( width: widget.width, height: widget.height, padding: const EdgeInsets.only(left: 10, right: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), border: Border.all( color: _focused ? Color(0xff409eff) : widget.disableColor, )), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( widget.data.isNotEmpty ? widget.data[widget.activeIndex] : "暂无数据",style: TextStyle( height: 1, fontSize: widget.fontSize, color: Color(0xff606266) ),), AnimatedBuilder( animation: animation, builder: (_, child) => Transform.rotate( angle: -animation.value, child: child, ), child: Icon( Icons.keyboard_arrow_down, color: Color(0xffc0c4cc), size: widget.iconSize, ), ), ], ), ); } OverlayEntry _createOverlayEntry() => OverlayEntry( builder: (BuildContext context) => UnconstrainedBox( child: CompositedTransformFollower( link: layerLink, targetAnchor: Alignment.bottomCenter, followerAnchor: Alignment.topCenter, child: Padding( padding: const EdgeInsets.only(top: 4.0), child: Material( color: Colors.transparent, shape: const RoundedRectangleBorder( side: BorderSide.none, borderRadius: BorderRadius.all(Radius.circular(4))), elevation: 0, child: Wrapper( padding: EdgeInsets.zero, spineType: SpineType.top, color: Colors.white, strokeWidth: 1, radius: 4, shadows: [ BoxShadow( offset: Offset(0,2), blurRadius: 8, color: Colors.black.withOpacity(0.1) ) ], // elevation: 3, // shadowColor: Colors.black.withOpacity(0.1), child: Container( height: min(34.0*widget.data.length+10,150), // alignment: Alignment.center, // decoration: const BoxDecoration( // color: Color(0xffDAE3FF), // ), width: widget.width, child: ListView.builder( itemExtent: 34, padding: EdgeInsets.symmetric(vertical: 5), // shrinkWrap: true, itemCount: widget.data.length, itemBuilder: _buildItem), ), ), ), ), ), ), ); Widget _buildItem(BuildContext context, int index) { return Material( // color: Colors.white, color: Colors.transparent, child: TolySelectTapRegion( child: InkWell( onTap: () { if (widget.activeIndex != index) widget.change.call(index); _overlayEntry?.markNeedsBuild(); _node.unfocus(); }, hoverColor: Color(0xffF5F7FA), child: Container( padding: const EdgeInsets.symmetric(vertical: 8,horizontal: 15), color: Colors.transparent, // color: index == _selectedIndex // ? Colors.blue.withOpacity(0.2) // : Colors.transparent, child: Text(widget.data[index], style: TextStyle(fontSize: widget.fontSize, fontWeight: index == widget.activeIndex?FontWeight.bold:null, color: index == widget.activeIndex?Theme.of(context).primaryColor:null ), )), ), ), ); } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/view/json_display/json_display.dart ================================================ import 'dart:convert'; import 'package:flutter/material.dart'; /// 颜色定义 class FieldColor { static const keyColor = Colors.grey; static const intColor = Colors.deepOrange; static const stringColor = Colors.green; static const nullColor = Colors.blueGrey; static const arrayColor = Colors.blue; static const objectColor = Colors.purple; } /// 基础类型抽象类 abstract class BaseValue { final String? key; final T value; bool isExpanded; BaseValue(this.value, {this.key, this.isExpanded = true}); Widget buildValue(); } /// 整数值 class IntValue extends BaseValue { IntValue(super.value, {super.key}); @override Widget buildValue() { return Text(value.toString()+",", style: const TextStyle(color: FieldColor.intColor)); } } /// 字符串值 class StringValue extends BaseValue { StringValue(super.value, {super.key}); @override Widget buildValue() { return Text('"$value",', style: const TextStyle(color: FieldColor.stringColor)); } } /// Null 值 class NullValue extends BaseValue { NullValue({super.key}) : super(null); @override Widget buildValue() { return const Text('null', style: TextStyle(color: FieldColor.nullColor)); } } /// 数组值 class ArrayValue extends BaseValue> { ArrayValue(super.value, {super.key}); @override Widget buildValue() { return StatefulBuilder(builder: (context, setState) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InkWell( onTap: () { setState(() { isExpanded = !isExpanded; }); }, child: Row( children: [ Icon(isExpanded ? Icons.arrow_drop_down : Icons.arrow_right, color: FieldColor.arrayColor), Text('(Array)${isExpanded?'【':'【...'}', style: TextStyle(color: FieldColor.arrayColor)), if (!isExpanded) const Text('】', style: TextStyle(color: FieldColor.arrayColor)), ], ), ), if (isExpanded) ...[ Padding( padding: const EdgeInsets.only(left: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: value.map((e) => parseJson(e).buildValue()).toList(), ), ), const Text('】', style: TextStyle(color: FieldColor.arrayColor)), ] ], ); }); } } /// 对象值 class ObjectValue extends BaseValue> { ObjectValue(super.value, {super.key}); @override Widget buildValue() { return StatefulBuilder( builder: (context, setState) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InkWell( onTap: () { setState(() { isExpanded = !isExpanded; // 触发 UI 重新渲染 }); }, child: Row( children: [ Icon(isExpanded ? Icons.arrow_drop_down : Icons.arrow_right, color: FieldColor.objectColor), Text('(Object)${isExpanded?'{':'{...'}', style: TextStyle(color: FieldColor.objectColor)), if (!isExpanded) const Text('}', style: TextStyle(color: FieldColor.objectColor)), ], ), ), if (isExpanded) ...[ Padding( padding: const EdgeInsets.only(left: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: value.entries .map((e) => KeyBlock(keyText: e.key, value: parseJson(e.value))) .toList(), ), ), const Text('}', style: TextStyle(color: FieldColor.objectColor)), ] ], ); }, ); } } /// 键值对组件 class KeyBlock extends StatelessWidget { final String keyText; final BaseValue value; const KeyBlock({super.key, required this.keyText, required this.value}); @override Widget build(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('$keyText: ', style: const TextStyle( color: FieldColor.keyColor, fontWeight: FontWeight.bold)), Expanded(child: value.buildValue()), ], ); } } /// 解析 JSON 数据 BaseValue parseJson(dynamic json, {String? key}) { if (json == null) return NullValue(key: key); if (json is int) return IntValue(json, key: key); if (json is String) return StringValue(json, key: key); if (json is List) return ArrayValue(json, key: key); if (json is Map) return ObjectValue(json, key: key); return StringValue(json.toString(), key: key); } /// JSON 解析工具 UI class JsonAnalysisTool extends StatefulWidget { const JsonAnalysisTool({super.key}); @override State createState() => _JsonAnalysisToolState(); } class _JsonAnalysisToolState extends State { final TextEditingController _controller = TextEditingController( text: """ { "id": 1, "name": "Container", "localName": "容器组件", "info": "用于容纳单个子组件的容器组件。集成了若干个单子组件的功能,如内外边距、形变、装饰、约束等...", "lever": 5, "family": 0, "linkIds": [ 74, 85, 80, 78, 70, 123 ], "nodes": [ { "file": "node1_base.dart", "name": "可用于显示一个指定宽高的区域", "desc": [ "【width】 : 宽 【int】", "【height】: 高 【int】", "【color】: 颜色 【Color】" ] }, { "file": "node2_child.dart", "name": "可以在区域中放入一个子组件", "desc": [ "【padding】 : 内边距 【EdgeInsetsGeometry】", "【margin】: 外边距 【EdgeInsetsGeometry】", "【child】: 子组件 【Widget】" ] }, { "file": "node3_alignment.dart", "name": "可对子组件进行对齐定位", "desc": [ "【alignment】 : 对齐定位 【AlignmentGeometry】" ] }, { "file": "node4_decoration.dart", "name": "可对子组件进行装饰", "desc": [ "【decoration】 : 装饰 【Decoration】", "可装饰: 边线、圆弧、颜色、渐变色、阴影、图片等内容" ] }, { "file": "node5_transform.dart", "name": "Container还具有变换性", "desc": [ "【transform】 : 变换矩阵 【Matrix4】", "基于Matrix4的矩阵变换,变换详情见线性代数" ] }, { "file": "node6_constraints.dart", "name": "Container的约束性", "desc": [ "【constraints】 : 约束 【BoxConstraints】", "会约束该区域的尺寸,不会小于指定的最小宽高,也不会大于指定的最大宽高。" ] } ] }""", ); BaseValue? _parsedData; String? _error; final ScrollController _hCtrl = ScrollController(); final ScrollController _vCtrl = ScrollController(); @override void initState() { super.initState(); _parseJson(); } void _parseJson() { setState(() { _error = null; try { final parsed = jsonDecode(_controller.text); _parsedData = parseJson(parsed); } catch (e) { _error = 'JSON 解析失败: $e'; _parsedData = null; } }); } @override Widget build(BuildContext context) { return Scaffold( body: Row( children: [ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( style: TextStyle(fontSize: 14,fontFamily: '楷体'), controller: _controller, onChanged: _onChanged, expands: true, maxLines: null, decoration: const InputDecoration( fillColor: Colors.white, filled: true, labelText: '输入 JSON', border: OutlineInputBorder(), ), ), ), ), VerticalDivider(), Expanded( child: Align( alignment: Alignment.topLeft, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4) ), margin: EdgeInsets.all(8), child: Scrollbar( //-> ::tag1:: thumbVisibility: true, //-> ::tag6:: notificationPredicate: (ScrollNotification notification) => notification.depth == 1, key: const Key('debuggerCodeViewVerticalScrollbarKey'), controller: _vCtrl, child: LayoutBuilder( builder: (context,cts) { final double boxHeight = 800; return Scrollbar( //-> ::tag2:: key: const Key('debuggerCodeViewHorizontalScrollbarKey'), thumbVisibility: true, controller: _hCtrl, child: SingleChildScrollView( controller: _hCtrl, //-> ::tag3:: child: SingleChildScrollView( controller: _vCtrl, scrollDirection: Axis.horizontal, padding: EdgeInsets.symmetric(horizontal: 8,vertical: 6), child: SizedBox( width: boxHeight, child: _error != null ? Text(_error!, style: const TextStyle(color: Colors.red)) : _parsedData?.buildValue() ?? const Text('请输入 JSON'), ), ), ), ); } ), ), ), ), ), ], ), ); } void _onChanged(String value) { _parseJson(); } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/view/mobile/mobile_tool_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:l10n/l10n.dart'; class MobileToolPage extends StatelessWidget { const MobileToolPage({super.key}); @override Widget build(BuildContext context) { AppLocalizations l10n = context.l10n; String title = l10n.treasureTools; String building = l10n.knowledgeConstruction; return Scaffold( appBar: AppBar(title: Text(title),), body: Center( child: Text(building), ), ); } } ================================================ FILE: modules/tools_system/treasure_tools/lib/src/wrapper.dart ================================================ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020/9/18 /// contact me by email 1981462002@qq.com /// 说明: /// spineHeight : 针尖高度 /// angle : 针尖角度 (角度值) /// radius : 圆角半径 /// offset : 偏移 /// spineType : 类型 /// color : 颜色 /// /// 宽高由父容器指定: 如 /// Container( // height: 50, // width: 100, // child: Wrapper() // ) typedef SpinePathBuilder = Path Function( Canvas canvas, SpineType spineType, Rect range); class Wrapper extends StatelessWidget { final double spineHeight; final double angle; final double radius; final double offset; final SpineType spineType; final Color color; final Widget? child; final SpinePathBuilder? spinePathBuilder; final double? strokeWidth; final bool formEnd; final EdgeInsets padding; final double? elevation; final Color shadowColor; final List? shadows; Wrapper( { this.spineHeight = 8.0, this.angle = 75, this.radius = 5.0, this.offset = 15, this.shadows, this.strokeWidth, this.child, this.elevation, this.shadowColor = Colors.grey, this.formEnd = false, this.color = Colors.green, this.spinePathBuilder, this.padding = const EdgeInsets.all(8), this.spineType = SpineType.left}); Wrapper.just({ this.radius = 5.0, this.strokeWidth, this.child, this.elevation, this.shadows, this.shadowColor = Colors.grey, this.color = Colors.green, this.padding = const EdgeInsets.all(8), }) : spineHeight = 0, angle = 0, offset = 0, spineType = SpineType.bottom, spinePathBuilder = null, formEnd = false; @override Widget build(BuildContext context) { var _padding = padding; switch (spineType) { case SpineType.top: _padding = padding + EdgeInsets.only(top: spineHeight); break; case SpineType.left: _padding = padding + EdgeInsets.only(left: spineHeight); break; case SpineType.right: _padding = padding + EdgeInsets.only(right: spineHeight); break; case SpineType.bottom: _padding = padding + EdgeInsets.only(bottom: spineHeight); break; } return CustomPaint( child: Padding( padding: _padding, child: child, ), painter: WrapperPainter( spineHeight: spineHeight, angle: angle, radius: radius, offset: offset, shadows: shadows, strokeWidth: strokeWidth, color: color, shadowColor: shadowColor, elevation: elevation, spineType: spineType, formBottom: formEnd, spinePathBuilder: spinePathBuilder), ); } } enum SpineType { top, left, right, bottom } class WrapperPainter extends CustomPainter { final Paint mPaint; var path = Path(); final double? strokeWidth; final SpinePathBuilder? spinePathBuilder; final List? shadows; final double? elevation; final Color shadowColor; final double spineHeight; final double angle; final bool formBottom; final double radius; final double offset; final SpineType spineType; final Color color; WrapperPainter( {this.spineHeight = 10.0, this.angle = 75, this.spinePathBuilder, this.radius = 5.0, this.offset = 15, this.elevation, this.shadows, this.strokeWidth, this.shadowColor = Colors.grey, this.color = Colors.green, this.formBottom = false, this.spineType = SpineType.left}) : mPaint = Paint() ..color = color ..style = strokeWidth == null ? PaintingStyle.fill : PaintingStyle.stroke ..strokeWidth = strokeWidth == null ? 1 : strokeWidth; @override void paint(Canvas canvas, Size size) { path = buildBoxBySpineType( canvas, spineType, size.width, size.height, ); Path? spinePath; if (spinePathBuilder == null) { spinePath = buildDefaultSpinePath(canvas, spineHeight, spineType, size); } else { Rect range ; switch(spineType){ case SpineType.top: range = Rect.fromLTRB(0, -spineHeight, size.width, 0); break; case SpineType.left: range = Rect.fromLTRB(-spineHeight, 0, 0, size.height); break; case SpineType.right: range = Rect.fromLTRB(-spineHeight, 0, 0, size.height).translate(size.width, 0); break; case SpineType.bottom: range = Rect.fromLTRB(0, 0, size.width, spineHeight).translate(0, size.height-spineHeight); break; } if(spinePathBuilder !=null){ spinePath = spinePathBuilder!(canvas, spineType, range); } } if (spinePath != null) { path = Path.combine(PathOperation.union, spinePath, path); if(shadows!=null){ drawShadows(canvas,path, shadows!); } // if (elevation != null) { // canvas.drawShadow(path, shadowColor, elevation!, true); // return; // } canvas.drawPath(path, mPaint..color=Color(0xffe4e7ed)); } } void drawShadows(Canvas canvas, Path path, List shadows) { for (final BoxShadow shadow in shadows) { final Paint shadowPainter = shadow.toPaint(); if (shadow.spreadRadius == 0) { canvas.drawPath(path.shift(shadow.offset), shadowPainter); } else { Rect zone = path.getBounds(); double xScale = (zone.width + shadow.spreadRadius) / zone.width; double yScale = (zone.height + shadow.spreadRadius) / zone.height; Matrix4 m4 = Matrix4.identity(); m4.translate(zone.width / 2, zone.height / 2); m4.scale(xScale, yScale); m4.translate(-zone.width / 2, -zone.height / 2); canvas.drawPath(path.shift(shadow.offset).transform(m4.storage), shadowPainter); } } Paint whitePaint = Paint()..color = Colors.white; canvas.drawPath(path, whitePaint); } buildDefaultSpinePath( Canvas canvas, double spineHeight, SpineType spineType, Size size) { switch (spineType) { case SpineType.top: return _drawTop(size.width, size.height, canvas); case SpineType.left: return _drawLeft(size.width, size.height, canvas); case SpineType.right: return _drawRight(size.width, size.height, canvas); case SpineType.bottom: return _drawBottom(size.width, size.height, canvas); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; Path _drawTop(double width, double height, Canvas canvas) { var angleRad = pi / 180 * angle; var spineMoveX = spineHeight * tan(angleRad / 2); var spineMoveY = spineHeight; if (spineHeight != 0) { return Path() ..moveTo(!formBottom ? offset : width - offset - spineHeight, 0) ..relativeLineTo(spineMoveX, -spineMoveY) ..relativeLineTo(spineMoveX, spineMoveY); } return Path(); } Path _drawBottom(double width, double height, Canvas canvas) { var lineHeight = height - spineHeight; var angleRad = pi / 180 * angle; var spineMoveX = spineHeight * tan(angleRad / 2); var spineMoveY = spineHeight; if (spineHeight != 0) { return Path() ..moveTo( !formBottom ? offset : width - offset - spineHeight, lineHeight) ..relativeLineTo(spineMoveX, spineMoveY) ..relativeLineTo(spineMoveX, -spineMoveY); } return Path(); } Path _drawLeft(double width, double height, Canvas canvas) { var angleRad = pi / 180 * angle; var spineMoveX = spineHeight; var spineMoveY = spineHeight * tan(angleRad / 2); if (spineHeight != 0) { return Path() ..moveTo(0, !formBottom ? offset : height - offset - spineHeight) ..relativeLineTo(-spineMoveX, spineMoveY) ..relativeLineTo(spineMoveX, spineMoveY); } return Path(); } Path _drawRight(double width, double height, Canvas canvas) { var lineWidth = width - spineHeight; var angleRad = pi / 180 * angle; var spineMoveX = spineHeight; var spineMoveY = spineHeight * tan(angleRad / 2); if (spineHeight != 0) { return Path() ..moveTo(lineWidth, !formBottom ? offset : height - offset - spineHeight) ..relativeLineTo(spineMoveX, spineMoveY) ..relativeLineTo(-spineMoveX, spineMoveY); } return Path(); } Path buildBoxBySpineType( Canvas canvas, SpineType spineType, double width, double height, ) { double lineHeight, lineWidth; switch (spineType) { case SpineType.top: lineHeight = height - spineHeight; canvas.translate(0, spineHeight); lineWidth = width; break; case SpineType.left: lineWidth = width - spineHeight; lineHeight = height; canvas.translate(spineHeight, 0); break; case SpineType.right: lineWidth = width - spineHeight; lineHeight = height; break; case SpineType.bottom: lineHeight = height - spineHeight; lineWidth = width; break; } Rect box = Rect.fromCenter( center: Offset(lineWidth / 2, lineHeight / 2), width: lineWidth, height: lineHeight); return Path()..addRRect(RRect.fromRectXY(box, radius, radius)); } } ================================================ FILE: modules/tools_system/treasure_tools/lib/treasure_tools.dart ================================================ library treasure_tools; export 'src/code_gen_page.dart'; export 'src/view/mobile/mobile_tool_page.dart'; ================================================ FILE: modules/tools_system/treasure_tools/pubspec.yaml ================================================ name: treasure_tools description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/tools_system/treasure_tools/test/iconfont_parser_test.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:treasure_tools/src/icon_font_gen/icon_font_class_parser.dart'; void main(){ final String filePath = r'E:\Download\out\font_1717416_cwm89ioqkfo\iconfont.json'; File jsonFile = File(filePath); final String jsonContent = jsonFile.readAsStringSync(); IconFontClassParser parser = IconFontClassParser(); String result = parser.parser(jsonContent,'TolyIcon'); print(result); } ================================================ FILE: modules/tools_system/treasure_tools/test/treasure_tools_test.dart ================================================ // import 'package:flutter_test/flutter_test.dart'; // // import 'package:treasure_tools/treasure_tools.dart'; // // void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); // } ================================================ FILE: modules/tools_system/treasure_tools/test/yaml_parser_test.dart ================================================ // // import 'dart:convert'; // import 'dart:io'; // // // import 'package:yaml/yaml.dart'; // import 'package:yaml_modify/yaml_modify.dart'; // // void main(){ // final String filePath = r'E:\Projects\Flutter\FlutterUnit\pubspec.yaml'; // File pubspecFile = File(filePath); // final String pubspec = pubspecFile.readAsStringSync(); // print(pubspec); // // // YamlEditor doc = YamlEditor(pubspec); // // print(doc); // final doc = loadYaml(pubspec); // print(doc); // // YamlList fontsList = doc['flutter']['fonts'] as YamlList; // // final modifiableDoc = getModifiableNode(doc); // final modifiableList = getModifiableNode(fontsList); // modifiableList.removeWhere((e) => e['family'] == 'TolyIcon'); // modifiableList.add( // YamlMap.wrap({ // 'family': 'TolyIcon3', // 'fonts':YamlList.wrap([YamlMap.wrap({'asset':'assets/iconfont/iconfont.ttf'})]) // }) // ); // modifiableDoc['flutter']['fonts'] = modifiableList; // final strYaml = toYamlString(modifiableDoc); // print(modifiableList); // // // // YamlMap flutterNode = doc[]; // // doc.update('flutter', (value) { // // // YamlList fontsNode = value as YamlList; // // // List filter = fontsNode.where((e) => e['family']!='TolyIcon').toList(); // // // // return YamlMap.wrap({ // // 'family': 'TolyIcon3', // // 'fonts':YamlList.wrap([YamlMap.wrap({'asset':'assets/iconfont/iconfont.ttf'})]) // // }); // // }); // // // YamlList fontsList = doc['flutter']['fonts'] as YamlList; // // // // // // doc. // print(doc); // // } ================================================ FILE: modules/tools_system/treasure_tools/test/yaml_parser_test2.dart ================================================ // // import 'dart:convert'; // import 'dart:io'; // // // import 'package:yaml/yaml.dart'; // import 'package:yaml_modify/yaml_modify.dart'; // // void main(){ // String familyName = 'TolyIcon'; // String fontAssetsDist = 'assets/iconfont/iconfont.ttf'; // // // final String filePath = r'E:\Projects\Flutter\FlutterUnit\pubspec.yaml'; // final String filePath = r'E:\Projects\Flutter\Work\toly_image_edit\pubspec.yaml'; // File pubspecFile = File(filePath); // // List lines = pubspecFile.readAsLinesSync(); // // RegExp fontsRegex = RegExp(r'^ fonts:',multiLine: true); // bool hasFonts = fontsRegex.hasMatch(lines.join('\n')); // // if(!hasFonts){ // // 当前没有 fonts 节点,需要添加到 flutter 节点下 // int index = lines.indexWhere((e) => e.startsWith('flutter:')); // // List fonts = [ // ' fonts:', // ' - family: TolyIcon', // ' fonts:', // ' - asset: assets/iconfont/iconfont.ttf', // ]; // // lines.insertAll(index+1, fonts); // print(lines); // pubspecFile.writeAsStringSync(lines.join('\n')); // return; // } // // 存在 fonts 节点,查询 family ,有没有当前字体图标 // bool hasTargetFamily = false; // RegExp regExp = RegExp(r'^ +- family: +(\w+)'); // // for(int i=0;i e.startsWith(fontsRegex)); // List fonts = [ // ' - family: TolyIcon', // ' fonts:', // ' - asset: $fontAssetsDist', // ]; // lines.insertAll(index+1, fonts); // print(lines); // pubspecFile.writeAsStringSync(lines.join('\n')); // return; // } // // } ================================================ FILE: modules/widget_system/widget_module/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .packages build/ ================================================ FILE: modules/widget_system/widget_module/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 channel: stable project_type: package ================================================ FILE: modules/widget_system/widget_module/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/widget_system/widget_module/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/widget_system/widget_module/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/widget_system/widget_module/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/widget_system/widget_module/lib/blocs/action/widget_action.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:app/app.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_ui/widget_ui.dart'; import '../../widget_module.dart'; extension WidgetContext on BuildContext { void initWidgetData() { switchWidgetFamily(WidgetFamily.stateless); } void switchWidgetFamily(WidgetFamily family) { String locale = read().state.language.code; read().add(EventTabTap(family, locale: locale)); } void toggleLike(int widgetId) { read().toggle(widgetId); } void handleWidgetAction(WidgetAction value) { switch (value) { case JumpWidgetDetail(): String? name = value.widgetName ?? value.model?.name; push('${AppRoute.widgetDetail.url}$name', extra: value.model); return; case ToggleLikeWidget(): toggleLike(value.widgetId); } } } ================================================ FILE: modules/widget_system/widget_module/lib/blocs/blocs.dart ================================================ export 'category_bloc/category_bloc.dart'; export 'category_widget_bloc/category_widget_bloc.dart'; export 'widget_detail_bloc/widget_detail_bloc.dart'; export 'widgets_bloc/widgets_bloc.dart'; ================================================ FILE: modules/widget_system/widget_module/lib/blocs/category_bloc/category_bloc.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_repository/widget_repository.dart'; part 'category_event.dart'; part 'category_state.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: class CategoryBloc extends Bloc { final CategoryRepository repository; CategoryBloc({required this.repository}) : super(const CategoryLoadingState()){ on(_onCategoryEvent); } void _onCategoryEvent(CategoryEvent event,Emitter emit) async { if (event is EventLoadCategory) { emit (const CategoryLoadingState()); // 使用 repository 加载 收藏集数据 final category = await repository.loadCategories(); category.isEmpty ? emit (const CategoryEmptyState()) : emit (CategoryLoadedState(category)); } if (event is EventDeleteCategory) { await repository.deleteCategory(event.id); add(const EventLoadCategory()); } if (event is EventToggleWidget) { await repository.toggleCategory(event.categoryId, event.widgetId); add(const EventLoadCategory()); } if (event is EventAddCategory) { CategoryPo categoryPo = CategoryPo( name: event.name, color: event.color ?? '#FFF2F2F2', info: event.info ?? '这里什么都没有...', created: DateTime.now(), updated: DateTime.now()); final success = await repository.addCategory(categoryPo); if (success) { emit(const AddCategorySuccess()) ; add(const EventLoadCategory()); } else { emit(const AddCategoryFailed()) ; } } if (event is EventUpdateCategory) { CategoryPo categoryPo = CategoryPo( id: event.id, name: event.name, priority: event.priority ?? 0, image: event.image ?? '', color: event.color ??'#FFF2F2F2', info: event.info ?? '这里什么都没有...', updated: DateTime.now()); final success = await repository.updateCategory(categoryPo); if (success) { // yield AddCategorySuccess(); add(const EventLoadCategory()); } else { // yield AddCategoryFailed(); } } } List get categories { if(state is CategoryLoadedState){ return (state as CategoryLoadedState).categories; }else{ return []; } } } ================================================ FILE: modules/widget_system/widget_module/lib/blocs/category_bloc/category_event.dart ================================================ part of 'category_bloc.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: 收藏集相关事件 abstract class CategoryEvent extends Equatable{ const CategoryEvent(); @override List get props => []; } // 加载 收藏集 事件 class EventLoadCategory extends CategoryEvent{ const EventLoadCategory(); @override List get props => []; } // 将一个 widget 添加/移除 收藏集 class EventToggleWidget extends CategoryEvent{ final int widgetId; final int categoryId; const EventToggleWidget({ required this.widgetId, required this.categoryId, }); @override List get props => [widgetId,categoryId]; } // 删除 收藏集 class EventDeleteCategory extends CategoryEvent{ final int id; const EventDeleteCategory({required this.id}); @override List get props => [id]; } // 添加 收藏集 class EventAddCategory extends CategoryEvent{ final String name; final String? info; final String? color; const EventAddCategory({ required this.name, required this.info, required this.color, }); @override List get props => [name, info, color]; } // 更新 收藏集 class EventUpdateCategory extends CategoryEvent { final int id; final String name; final String? info; final String? color; final int? priority; final String? image; const EventUpdateCategory({ required this.name, required this.info, required this.color, this.priority, this.image, required this.id, }); @override List get props => [name, info, color, priority, image, id]; } ================================================ FILE: modules/widget_system/widget_module/lib/blocs/category_bloc/category_state.dart ================================================ part of 'category_bloc.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: class CategoryState extends Equatable { const CategoryState(); @override List get props => []; } class CategoryLoadedState extends CategoryState { final List categories; const CategoryLoadedState(this.categories); @override List get props => [categories]; } class CategoryLoadingState extends CategoryState { const CategoryLoadingState(); @override List get props => []; } class CategoryEmptyState extends CategoryState { const CategoryEmptyState(); @override List get props => []; } class AddCategorySuccess extends CategoryState { const AddCategorySuccess(); } class AddCategoryFailed extends CategoryState { const AddCategoryFailed(); } ================================================ FILE: modules/widget_system/widget_module/lib/blocs/category_widget_bloc/category_widget_bloc.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_repository/widget_repository.dart'; import '../category_bloc/category_bloc.dart'; part 'category_widget_event.dart'; part 'category_widget_state.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: class CategoryWidgetBloc extends Bloc { final CategoryBloc categoryBloc; CategoryWidgetBloc({required this.categoryBloc}) : super(CategoryWidgetEmptyState()) { on(_onEventLoadCategoryWidget); on(_onEventToggleCategoryWidget); } CategoryRepository get repository => categoryBloc.repository; void _onEventLoadCategoryWidget( EventLoadCategoryWidget event, Emitter emit) async { final widgets = await repository.loadCategoryWidgets(categoryId: event.categoryId); widgets.isNotEmpty ? emit(CategoryWidgetLoadedState(widgets)) : emit(CategoryWidgetEmptyState()); categoryBloc.add(const EventLoadCategory()); } void _onEventToggleCategoryWidget(EventToggleCategoryWidget event, Emitter emit) async { await repository.toggleCategory(event.categoryId, event.widgetId); add(EventLoadCategoryWidget(event.categoryId, 'zh-cn')); } } ================================================ FILE: modules/widget_system/widget_module/lib/blocs/category_widget_bloc/category_widget_event.dart ================================================ part of 'category_widget_bloc.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: abstract class CategoryWidgetEvent extends Equatable { @override List get props => []; } class EventLoadCategoryWidget extends CategoryWidgetEvent { final int categoryId; final String locale; EventLoadCategoryWidget(this.categoryId, this.locale); @override List get props => [categoryId, locale]; } class EventToggleCategoryWidget extends CategoryWidgetEvent { final int categoryId; final int widgetId; EventToggleCategoryWidget(this.categoryId, this.widgetId); @override List get props => [categoryId, widgetId]; } ================================================ FILE: modules/widget_system/widget_module/lib/blocs/category_widget_bloc/category_widget_state.dart ================================================ part of 'category_widget_bloc.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: class CategoryWidgetState extends Equatable{ @override List get props => []; } class CategoryWidgetLoadedState extends CategoryWidgetState { final List widgets; CategoryWidgetLoadedState(this.widgets); @override List get props => [widgets]; } class CategoryWidgetEmptyState extends CategoryWidgetState{ @override List get props => []; } ================================================ FILE: modules/widget_system/widget_module/lib/blocs/widget_detail_bloc/widget_detail_bloc.dart ================================================ import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_repository/widget_repository.dart'; part 'widget_detail_state.dart'; /// create by 张风捷特烈 on 2020-03-03 /// contact me by email 1981462002@qq.com /// 说明: 组件详情页状态数据维护核心类 /// [_modelStack] 组件详情页支持管理跳转,栈数据交由 Bloc 维护 class WidgetDetailBloc extends Cubit { final WidgetRepository widgetRepo; final NodeRepository nodeRepo; WidgetDetailBloc({ required this.widgetRepo, required this.nodeRepo, }) : super(DetailLoading()); List _modelStack = []; WidgetModel get currentWidget => _modelStack.last; void push(WidgetModel model, {String? locale}) { _modelStack.add(model); queryDetail(model, locale: locale); } Future pop() async { if (_modelStack.isEmpty) { return true; } _modelStack.removeLast(); if (_modelStack.isNotEmpty) { queryDetail(currentWidget); return false; } else { return true; } } void queryDetail(WidgetModel widget, {String? locale}) async { emit(DetailLoading()); try { final List nodes = await nodeRepo.loadNode(widget.id, locale: locale); final List links = await widgetRepo.loadWidget(widget.links, locale); emit(DetailWithData(widgetModel: widget, nodes: nodes, links: links)); } catch (e, s) { print("queryDetail=error===${e}=$s=="); emit(DetailFailed()); } } void changeLocale(Locale locale) async { String localeStr = '${locale.languageCode}-${locale.countryCode}'.toLowerCase(); List ids = _modelStack.map((e) => e.id).toList(); _modelStack = await widgetRepo.loadWidget(ids, localeStr); queryDetail(currentWidget, locale: localeStr); } } ================================================ FILE: modules/widget_system/widget_module/lib/blocs/widget_detail_bloc/widget_detail_state.dart ================================================ part of 'widget_detail_bloc.dart'; /// create by 张风捷特烈 on 2020-03-03 /// contact me by email 1981462002@qq.com /// 说明: 详情状态类 abstract class DetailState extends Equatable { const DetailState(); @override List get props => []; } class DetailWithData extends DetailState { final WidgetModel widgetModel; final List links; final List nodes; const DetailWithData({ required this.widgetModel, required this.nodes, required this.links, }); @override List get props => [widgetModel, nodes]; @override String toString() { return 'DetailWithData{widget: $widgetModel, nodes: $nodes}'; } DetailWithData copyWith({ WidgetModel? widgetModel, List? links, List? nodes, }) { return DetailWithData( widgetModel: widgetModel ?? this.widgetModel, nodes: this.nodes, links: this.links, ); } } class DetailLoading extends DetailState {} class DetailFailed extends DetailState {} ================================================ FILE: modules/widget_system/widget_module/lib/blocs/widgets_bloc/widgets_bloc.dart ================================================ import 'dart:async'; import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fx_platform_adapter/fx_platform_adapter.dart'; import 'package:widget_repository/widget_repository.dart'; part 'widgets_event.dart'; part 'widgets_state.dart'; /// create by 张风捷特烈 on 2020-03-03 /// contact me by email 1981462002@qq.com /// 说明: 处理主页 Widget 列表加载逻辑 class WidgetsBloc extends Bloc { final WidgetRepository repository; WidgetsBloc({required this.repository}) : super(const WidgetsLoading()) { on(_onEventTabTap); on(_onEventLoadMore); on(_onEventRefresh); on(_onEventSearchWidget); } /// 切换页签,以 [family] 为过滤项 void _onEventTabTap(EventTabTap event, Emitter emit) async { // emit(const WidgetsLoading(operate: LoadOperate.load)); int size = kAppEnv.isDesktop ? 1000 : 20; WidgetFilter filter = WidgetFilter.family(event.family, pageSize: size, locale: event.locale); try { final List widgets = await repository.searchWidgets(filter); emit(WidgetsLoaded( widgets: widgets, filter: filter, operate: LoadOperate.load, )); } catch (err, t) { print("======$err==========$t=============="); emit(WidgetsLoadFailed( err.toString(), filter: filter, operate: LoadOperate.load, )); } } FutureOr _onEventRefresh( EventRefresh event, Emitter emit) async { try { await Future.delayed(const Duration(milliseconds: 500)); final List widgets = await repository.searchWidgets(state.filter.copyWith(page: 1)); emit(WidgetsLoaded( widgets: widgets, filter: state.filter, operate: LoadOperate.refresh, fetchTime: DateTime.now().millisecondsSinceEpoch, )); } catch (err) { print(err); emit(WidgetsLoadFailed( err.toString(), filter: state.filter, operate: LoadOperate.refresh, )); } } FutureOr _onEventLoadMore( EventLoadMore event, Emitter emit) async { if (state is WidgetsLoaded) { WidgetsLoaded old = (state as WidgetsLoaded); int total = await repository.total(old.filter); if (old.widgets.length < old.filter.pageSize) { // 不满一页 emit(old.copyWith( full: true, operate: LoadOperate.more, fetchTime: DateTime.now().millisecondsSinceEpoch, )); return; } if (total <= old.widgets.length) { // 已满 emit(old.copyWith( full: true, operate: LoadOperate.more, fetchTime: DateTime.now().millisecondsSinceEpoch, )); return; } // 未满,继续加载下一页 int pageIndex = old.widgets.length ~/ old.filter.pageSize + 1; WidgetFilter filter = old.filter.copyWith(page: pageIndex); final List newData = await repository.searchWidgets(filter); List newWidget = old.widgets + newData; emit(old.copyWith( widgets: newWidget, full: newWidget.length == total, operate: LoadOperate.more, fetchTime: DateTime.now().millisecondsSinceEpoch, filter: filter, )); } } void _onEventSearchWidget( EventSearchWidget event, Emitter emit) async { emit(const WidgetsLoading(operate: LoadOperate.load)); try { final List widgets = await repository.searchWidgets(event.filter); emit(WidgetsLoaded( widgets: widgets, filter: event.filter, operate: LoadOperate.load, )); } catch (err) { print(err); emit(WidgetsLoadFailed( err.toString(), filter: event.filter, operate: LoadOperate.load, )); } } void changeLocale(Locale locale) { add(EventTabTap(state.filter.family ?? WidgetFamily.stateless, locale: '${locale.languageCode}-${locale.countryCode}'.toLowerCase())); } } ================================================ FILE: modules/widget_system/widget_module/lib/blocs/widgets_bloc/widgets_event.dart ================================================ part of 'widgets_bloc.dart'; /// create by 张风捷特烈 on 2020-03-03 /// contact me by email 1981462002@qq.com /// 说明: abstract class WidgetsEvent extends Equatable { const WidgetsEvent(); @override List get props => []; } class EventTabTap extends WidgetsEvent { final WidgetFamily family; final String? locale; const EventTabTap(this.family,{this.locale}); @override List get props => [family]; } class EventLoadMore extends WidgetsEvent { @override List get props => []; } class EventRefresh extends WidgetsEvent { @override List get props => []; } class EventSearchWidget extends WidgetsEvent { final WidgetFilter filter;//参数 const EventSearchWidget({required this.filter}); } ================================================ FILE: modules/widget_system/widget_module/lib/blocs/widgets_bloc/widgets_state.dart ================================================ part of 'widgets_bloc.dart'; /// create by 张风捷特烈 on 2020-03-03 /// contact me by email 1981462002@qq.com /// 说明: 主页 Widget 列表 状态类 /// 对于一个可查询、可分页的数据集 /// 状态类持有过滤数据 Filter, 任何衍生状态都需要感知过滤的信息,比如加载异常,点击重试,可以基于最后的过滤状态。 /// 分页加载 信息需要 了解 总数据、页数、每页含量 /// sealed class WidgetsState extends Equatable { final WidgetFilter filter; final LoadOperate operate; const WidgetsState({required this.filter, required this.operate}); @override List get props => [filter]; } class WidgetsLoading extends WidgetsState { const WidgetsLoading({ super.filter = const WidgetFilter(), super.operate = LoadOperate.load, }); } /// [full] 是否已满,用于加载更多到底的标识 /// [widgets] 加载的数据 class WidgetsLoaded extends WidgetsState { final List widgets; final bool full; final int fetchTime; const WidgetsLoaded({ this.widgets = const [], required super.filter, required super.operate, this.full = false, this.fetchTime = 0, }); @override List get props => [widgets, full, filter, operate, fetchTime]; @override String toString() { return 'WidgetsLoaded{widgets: $widgets}'; } WidgetsLoaded copyWith({ List? widgets, bool? full, LoadOperate? operate, WidgetFilter? filter, int? fetchTime, }) { return WidgetsLoaded( widgets: widgets ?? this.widgets, full: full ?? this.full, operate: operate ?? this.operate, filter: filter ?? this.filter, fetchTime: fetchTime ?? this.fetchTime, ); } } class WidgetsLoadFailed extends WidgetsState { final String error; const WidgetsLoadFailed(this.error, {required super.filter, required super.operate}); @override List get props => [error]; } /// 加载类型 /// [load] 加载第一页数据, 用于首次加载, /// [refresh] 下拉刷新状态标识。之前加载的更多数据将会被移除 /// [more] 加载更多状态标识。 /// enum LoadOperate { load, refresh, more, } ================================================ FILE: modules/widget_system/widget_module/lib/event/widget_event.dart ================================================ import 'package:fx_trace/fx_trace.dart'; import '../widget_module.dart'; class SelectWidgetEvent extends FxEvent { final String name; final int? id; final WidgetModel? model; SelectWidgetEvent({ required this.name, this.id, this.model, }); } ================================================ FILE: modules/widget_system/widget_module/lib/event/widget_statistics_event.dart ================================================ import 'package:flutter/material.dart'; import 'package:storage/storage.dart'; import 'package:widget_repository/widget_repository.dart'; /// 初始化统计数据 Future initWidgetStatistics() async { await WidgetStatisticsProvider().loadStatistics( AppStorage().flutter(), ); } ================================================ FILE: modules/widget_system/widget_module/lib/views/components/collected_tag.dart ================================================ // import 'package:flutter/material.dart'; // import 'package:l10n/ext.dart'; // import 'package:wrapper/wrapper.dart'; // // class CollectedTag extends StatelessWidget { // const CollectedTag({super.key}); // // @override // Widget build(BuildContext context) { // bool isDark = Theme.of(context).brightness == Brightness.dark; // Color color = Theme.of(context).primaryColor; // String text = context.l10n.favorite; // return Wrapper.just( // radius: 10, // color: isDark ? const Color(0xff292A2D) : const Color(0xffF3F3F5), // padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), // child: Text( // text, // style: TextStyle(color: color, height: 1, fontSize: 10, shadows: [ // Shadow( // color: isDark ? Colors.black : Colors.white, // blurRadius: 2, // offset: const Offset(1, 1), // ) // ]), // ), // ); // } // } ================================================ FILE: modules/widget_system/widget_module/lib/views/components/widget_logo_map.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:fx_env/fx_env.dart'; import 'package:flutter_svg/flutter_svg.dart'; class WidgetLogo extends StatelessWidget { final Color background; final String widgetName; const WidgetLogo({ super.key, required this.background, required this.widgetName, }); @override Widget build(BuildContext context) { return Container( width: 110, height: 110, alignment: Alignment.center, decoration: BoxDecoration( color: background, gradient: LinearGradient( transform: const GradientRotation(270 * 180 / pi), colors: [ background.withValues(alpha: 0.9), background.withValues(alpha: 0.5) ]), borderRadius: const BorderRadius.only( topLeft: Radius.circular(6), bottomLeft: Radius.circular(6)), ), child: SvgPicture.asset( 'assets/images/widgets/${widgetLogo(widgetName)}', width: kApp.isDesktopUI ? 90 : 80, ), ); } } String widgetLogo(String widgetName) { return switch (widgetName) { 'Container' => 'Container.svg', 'Text' => 'Text.svg', 'GestureDetector' => 'GestureDetector.svg', 'CircleAvatar' => 'CircleAvatar.svg', 'Card' => 'Card.svg', 'ListView' => 'ListView.svg', 'GridView' => 'GridView.svg', 'SingleChildScrollView' => 'SingleChildScrollView.svg', 'PageView' => 'PageView.svg', 'InputChip' => 'InputChip.svg', 'Chip' => 'Chip.svg', 'FilterChip' => 'FilterChip.svg', 'MaterialButton' => 'MaterialButton.svg', 'FlutterLogo' => 'FlutterLogo.svg', 'RichText' => 'RichText.svg', 'FloatingActionButton' => 'FloatingActionButton.svg', 'Banner' => 'Banner.svg', 'Icon' => 'Icon.svg', _ => 'Widget.svg', }; } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/category_panel/desk_category_page.dart ================================================ import 'package:app/app.dart'; import 'package:components/components.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; import '../../mobile/category_page/category_list_item.dart'; import '../../mobile/category_page/delete_category_dialog.dart'; import '../../mobile/category_page/edit_category_panel.dart'; import 'desk_top_like_panel.dart'; class DeskCategoryPage extends StatefulWidget { const DeskCategoryPage({Key? key}) : super(key: key); @override State createState() => _DeskCategoryPageState(); } class _DeskCategoryPageState extends State { final PageController _ctrl = PageController(); @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ DeskTabTopBar( onTabPressed: (int value) { _ctrl.jumpToPage(value); }, tabs: ['组件酒肆', '珍藏组件'], ), Expanded( child: PageView( controller: _ctrl, children: [ DeskCateGoryPage(), DeskLikePage(), ], )) ], ), ); } } class DeskCateGoryPage extends StatelessWidget { const DeskCateGoryPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { const SliverGridDelegate deskGridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 240, mainAxisSpacing: 8, mainAxisExtent: 220, crossAxisSpacing: 8, ); CategoryBloc bloc = context.read(); CategoryState state = bloc.state; if (state is CategoryLoadedState) { return GridView.builder( itemCount: state.categories.length, padding: EdgeInsets.all(12), gridDelegate: deskGridDelegate, itemBuilder: (_, index) => GestureDetector( onTap: () => _toDetailPage(context, state.categories[index]), child: CategoryListItem( data: state.categories[index], onDeleteItemClick: (model) => _deleteCollect(context, model), onEditItemClick: (model) => _editCollect(context, model), ))); } return SizedBox.shrink(); } ShapeBorder get rRectBorder => const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10))); void _deleteCollect(BuildContext context, CategoryModel model) { showDialog( context: context, builder: (ctx) => Dialog( elevation: 5, shape: rRectBorder, child: SizedBox( width: 50, child: DeleteCategoryDialog( title: '删除收藏集', content: ' 删除【${model.name}】收藏集,你将会失去其中的所有收藏组件,是否确定继续执行?', onSubmit: () { BlocProvider.of(context) .add(EventDeleteCategory(id: model.id!)); Navigator.of(context).pop(); }, ), ), )); } void _editCollect(BuildContext context, CategoryModel model) { showDialog( context: context, builder: (ctx) => Dialog( backgroundColor: const Color(0xFFF2F2F2), elevation: 5, shape: rRectBorder, child: Column( mainAxisSize: MainAxisSize.min, children: [ Gap.H5, Row( children: [ Padding( padding: const EdgeInsets.only(left: 20, right: 10), child: Circle( color: Theme.of(context).primaryColor, ), ), const Text( '修改收藏集', style: TextStyle(fontSize: 20), ), const Spacer(), const CloseButton() ], ), Padding( padding: const EdgeInsets.all(8.0), child: EditCategoryPanel( model: model, type: EditType.update, ), ), ], ), )); } void _toDetailPage(BuildContext context, CategoryModel model) { String locale = context.read().language.code; BlocProvider.of(context) .add(EventLoadCategoryWidget(model.id!, locale)); // Navigator.pushNamed(context, UnitRouter.category_show, arguments: model); context.push('${AppRoute.collectionDetail.url}${model.id}', extra: model); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/category_panel/desk_top_like_panel.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_module/blocs/action/widget_action.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_ui/widget_ui.dart'; import '../../mobile/widget_detail/collect_widget_list_item.dart'; import '../../mobile/widget_detail/widget_detail_page.dart'; class DeskLikePage extends StatelessWidget { const DeskLikePage({Key? key}) : super(key: key); final SliverGridDelegate deskGridDelegate = const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 300, mainAxisSpacing: 15, mainAxisExtent: 80, crossAxisSpacing: 15, ); @override Widget build(BuildContext context) { LikeWidgetBloc bloc = context.watch(); List state = bloc.state; return GridView.builder( itemCount: state.length, padding: EdgeInsets.all(20), gridDelegate: deskGridDelegate, itemBuilder: (_, index) => GestureDetector( onTap: () => _toDetailPage(context, state[index]), child: CollectWidgetListItem( data: state[index], onDeleteItemClick: (model) => context.toggleLike(model.id), ))); } ShapeBorder get rRectBorder => const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10))); _toDetailPage(BuildContext context, WidgetModel model) { // BlocProvider.of(context).add(FetchWidgetDetail(model)); Navigator.push( context, SlidePageRoute(child: WidgetDetailPageScope(model: model))); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/desk_ui.dart ================================================ export 'widget_detail/widget_detail_page.dart'; export 'category_panel/desk_category_page.dart'; export 'widget_panel/widget_panel.dart'; export 'widget_panel/desk_search_bar.dart'; export 'widget_panel/desk_search_bar_v2.dart'; ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/widget_detail/link_widget_buttons.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:widget_repository/widget_repository.dart'; class LinkWidgetButtons extends StatelessWidget { final List links; final ValueChanged onSelect; const LinkWidgetButtons( {Key? key, required this.links, required this.onSelect}) : super(key: key); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; Color? chipColor = isDark ? Theme.of(context).floatingActionButtonTheme.backgroundColor : Theme.of(context).primaryColor; if (links.isEmpty) { return Padding( padding: const EdgeInsets.only(left: 10), child: Chip( backgroundColor: Colors.grey.withAlpha(120), labelStyle: const TextStyle(fontSize: 12, color: Colors.white), label: const Text('暂无链接组件'), )); } else { return Padding( padding: const EdgeInsets.only(left: 10.0, top: 10), child: Wrap( spacing: 5, runSpacing: 5, children: links .map((WidgetModel model) => ActionChip( labelPadding: EdgeInsets.zero, side: BorderSide.none, onPressed: () => onSelect(model), elevation: 1, // shadowColor: chipColor, backgroundColor: chipColor, labelStyle: model.deprecated ? UnitTextStyle.deprecatedChip : UnitTextStyle.commonChip, label: Text(model.name), )) .toList(), ), ); } } } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/widget_detail/widget_detail_bar.dart ================================================ import 'dart:math'; import 'package:app/app.dart'; import 'package:components/components.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:utils/utils.dart'; import 'package:widget_module/blocs/action/widget_action.dart'; import 'package:widget_ui/widget_ui.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; class DeskSliverWidgetDetailBar extends StatelessWidget { final WidgetModel model; const DeskSliverWidgetDetailBar({Key? key, required this.model}) : super(key: key); final Color backgroundColor = const Color(0xffFAFAFA); static const Color textColor = Color(0xff262626); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; Color? appBarColor = Theme.of(context).appBarTheme.backgroundColor; Color? appBarTextColor = Theme.of(context).appBarTheme.titleTextStyle?.color; return SliverAppBar( pinned: true, backgroundColor: isDark ? appBarColor : backgroundColor, titleTextStyle: TextStyle(color: isDark ? appBarTextColor : Color(0xff696969)), iconTheme: IconThemeData(color: isDark ? appBarTextColor : Color(0xff696969)), expandedHeight: 120.0, scrolledUnderElevation: 0.5, flexibleSpace: DragToMoveWrapper( child: DiyFlexibleSpaceBar( centerTitle: false, expandedTitleScale: 2, titleIconBuilder: (t) => WindmillWidget( rotate: t * 2 * pi * 2, radius: 15, ), fixedSubtitle: Text( model.name, style: TextStyle( color: isDark ? appBarTextColor : Color(0xff696969), fontSize: 12), ), title: Padding( padding: const EdgeInsets.only(bottom: 3), child: Text( model.nameCN, style: TextStyle( color: isDark ? appBarTextColor : textColor, fontSize: 16), ), ), //伸展处布局 titlePadding: const EdgeInsets.only(left: 20, bottom: 10), //标题边距 collapseMode: CollapseMode.parallax, ), ), elevation: 0, actions: [ WindowButtons( actions: [ _buildToHome(context), FeedbackWidget( onPressed: () => context.toggleLike(model.id), child: BlocConsumer>( listener: _listenLikeStateChange, builder: _buildByLikeState, ), ), ], ) ], ); } // 监听 LikeWidgetBloc 伺机弹出 toast void _listenLikeStateChange(BuildContext context, List state) { bool collected = state.contains(model); String msg = collected ? "收藏【${model.name}】组件成功!" : "已取消【${model.name}】组件收藏!"; Toast.toast( context, msg, duration: Duration(milliseconds: collected ? 1500 : 600), action: collected ? SnackBarAction( textColor: Colors.white, label: '收藏夹管理', onPressed: () => Scaffold.of(context).openEndDrawer()) : null, ); } // 根据 [LikeWidgetState ] 构建图标 Widget _buildByLikeState(BuildContext context, List state) { bool liked = state.contains(model); return SizedBox( width: 30, height: 30, child: Icon( liked ? TolyIcon.icon_star_ok : TolyIcon.icon_star_add, size: 20, ), ); } Widget _buildToHome(BuildContext context) => GestureDetector( onLongPress: () => Scaffold.of(context).openEndDrawer(), child: const SizedBox( width: 30, height: 30, child: Icon( Icons.home, size: 20, ), ), onTap: () => Navigator.of(context).pop()); } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/widget_detail/widget_detail_page.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:l10n/l10n.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; import '../../mobile/mobile_ui.dart'; import '../../mobile/widget_detail/category_end_drawer.dart'; import '../../mobile/widget_detail/node_display/node_display.dart'; import '../../mobile/widget_detail/widget_fields_sliver.dart'; import 'link_widget_buttons.dart'; import 'widget_detail_bar.dart'; import 'widget_detail_panel.dart'; // 用于组件详情不需要在一开始就加载 // WidgetDetailBloc 可以在稍后提供 class DeskWidgetDetailPageScope extends StatefulWidget { final WidgetModel? model; final String? widgetName; const DeskWidgetDetailPageScope( {super.key, required this.model, this.widgetName}); @override State createState() => _DeskWidgetDetailPageScopeState(); } class _DeskWidgetDetailPageScopeState extends State { WidgetModel? _model; WidgetRepository get widgetRepository => context.read().repository; NodeRepository get nodeRepository => kIsWeb ? MemoryNodeRepository() : const NodeDbRepository(); @override void initState() { super.initState(); _model = widget.model; if (_model == null) { _loadModelByName(); } } void _loadModelByName() async { _model = await widgetRepository.queryWidgetByName(widget.widgetName); setState(() {}); } @override Widget build(BuildContext context) { if (_model == null) return const Center(child: CupertinoActivityIndicator()); Locale locale = context.read().state.language.locale; // Locale locale = Localizations.localeOf(context); // String? countryCode = locale.countryCode; // if (countryCode == null) {} String localeStr = '${locale.languageCode}-${locale.countryCode}'.toLowerCase(); return BlocProvider( create: (_) => WidgetDetailBloc( widgetRepo: widgetRepository, nodeRepo: nodeRepository, )..push(_model!, locale: localeStr), child: DeskWidgetDetailPage( model: widget.model, ), ); } } class DeskWidgetDetailPage extends StatelessWidget { final WidgetModel? model; const DeskWidgetDetailPage({Key? key, required this.model}) : super(key: key); @override Widget build(BuildContext context) { WidgetDetailBloc bloc = context.watch(); DetailState state = context.watch().state; WidgetModel widget = bloc.currentWidget; return BlocListener( listenWhen: (p, n) => p.language != n.language, listener: (_, state) { BlocProvider.of(context) .changeLocale(state.language.locale); }, child: Scaffold( backgroundColor: Theme.of(context).appBarTheme.backgroundColor, endDrawer: CategoryEndDrawer(widget: widget), body: Builder(builder: (ctx) { return _buildContent(ctx, bloc, state); }), ), ); } Widget linkText(BuildContext context) => Row( children: [ const Padding( padding: EdgeInsets.only(left: 15, right: 5), child: Icon(Icons.link, color: Colors.blue), ), Text(context.l10n.relatedComponents, style: UnitTextStyle.labelBold), ], ); Widget _buildContent( BuildContext context, WidgetDetailBloc bloc, DetailState state) { return WillPopScope( onWillPop: () => _whenPop(context), child: CustomScrollView( slivers: [ DeskSliverWidgetDetailBar(model: bloc.currentWidget), SliverToBoxAdapter( child: Column( children: [ DeskWidgetDetailPanel( model: bloc.currentWidget, state: state, ), const Divider( height: 18, ) ], ), ), if (state is DetailWithData) state.nodes.isNotEmpty ? SliverNodeList( nodes: state.nodes, model: state.widgetModel, ) : SliverWidgetFieldsList(widgetId: model!.id), ], )); } Future _whenPop(BuildContext context) async { WidgetDetailBloc detailBloc = context.read(); if (Scaffold.of(context).isEndDrawerOpen) { return true; } return detailBloc.pop(); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/widget_detail/widget_detail_panel.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_star/flutter_star.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_ui/widget_ui.dart'; import 'dart:math'; import '../../../blocs/blocs.dart'; import '../../components/widget_logo_map.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:l10n/l10n.dart'; import 'link_widget_buttons.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class DeskWidgetDetailPanel extends StatelessWidget { final WidgetModel model; final DetailState state; const DeskWidgetDetailPanel( {Key? key, required this.model, required this.state}) : super(key: key); @override Widget build(BuildContext context) { Color color = Theme.of(context).primaryColor; return Padding( padding: const EdgeInsets.all(12.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLeft(model, context), const SizedBox(width: 12), Hero( tag: "hero_widget_image_${model.id}", child: WidgetDetailLogo( model: model, background: color, widgetName: model.name, ), ) ], ), ); } Widget linkText(BuildContext context) => Row( children: [ const Padding( padding: EdgeInsets.only(left: 15, right: 5), child: Icon(Icons.link, color: Colors.blue), ), Text(context.l10n.relatedComponents, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), ], ); Widget _buildLeft(WidgetModel model, BuildContext context) => Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Panel( color: Color(0x33E5EAE1), child: Text(model.info), ), const SizedBox(height: 16), linkText(context), if (state is DetailWithData) LinkWidgetButtons( links: (state as DetailWithData).links, onSelect: (v) { context.read().push(v); }) ], ), ); } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/widget_detail/widget_node_panel.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:toggle_rotate/toggle_rotate.dart'; import 'package:utils/utils.dart'; /// create by 张风捷特烈 on 2020-04-13 /// contact me by email 1981462002@qq.com /// 说明: 一个Widget的知识点对应的界面 class DeskWidgetNodePanel extends StatefulWidget { final String text; final String subText; final String code; final Widget? show; final HighlighterStyle? codeStyle; final String? codeFamily; final bool death; const DeskWidgetNodePanel( {Key? key, this.text = '', this.subText = '', this.code = '', this.death = false, this.show, required this.codeStyle, this.codeFamily}) : super(key: key); @override _DeskWidgetNodePanelState createState() => _DeskWidgetNodePanelState(); } class _DeskWidgetNodePanelState extends State { CrossFadeState _crossFadeState = CrossFadeState.showFirst; bool get isFirst => _crossFadeState == CrossFadeState.showFirst; Color get themeColor => Theme.of(context).primaryColor; @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ buildNodeTitle(), const SizedBox( height: 20, ), _buildCode(context), Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: Center( child: Padding( padding: const EdgeInsets.only(top: 10, bottom: 20), child: widget.show, ), ), ), if (!widget.death) Expanded(child: _buildNodeInfo()), ], ), const SizedBox( height: 16, ), const Divider(), ], ), ); } Widget buildNodeTitle() => Row( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Circle( color: themeColor, radius: 5, ), ), Expanded( child: Text( widget.text, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), ), _buildShareButton(), _buildCodeButton(), ], ); Widget _buildNodeInfo() => SizedBox( width: double.infinity, child: Panel( color: Color(0x33E5EAE1), child: Text( widget.subText, style: const TextStyle(fontSize: 12), )), ); Widget _buildCodeButton() => Padding( padding: const EdgeInsets.only(right: 10.0), child: ToggleRotate( durationMs: 300, child: Icon( TolyIcon.icon_code, color: themeColor, ), onTap: _toggleCodePanel, ), ); Widget _buildShareButton() => FeedbackWidget( mode: FeedMode.fade, a: 0.4, onPressed: _doShare, child: Padding( padding: const EdgeInsets.only(right: 10), child: Icon( Icons.copy, size: 20, color: themeColor, ), ), ); Widget _buildCode(BuildContext context) => AnimatedCrossFade( firstCurve: Curves.easeInCirc, secondCurve: Curves.easeInToLinear, firstChild: const SizedBox(), secondChild: SizedBox( width: MediaQuery.of(context).size.width, child: CodeWidget( fontFamily: widget.codeFamily, code: isFirst ? '' : widget.code, style: widget.codeStyle ?? HighlighterStyle.fromColors(HighlighterStyle.lightColor), ), ), duration: const Duration(milliseconds: 200), crossFadeState: _crossFadeState, ); //执行分享 void _doShare() async { // Share.share(widget.code); await Clipboard.setData(ClipboardData(text: widget.code)); Toast.success(context, '代码复制成功!'); } // 折叠代码面板 void _toggleCodePanel() { setState(() { _crossFadeState = !isFirst ? CrossFadeState.showFirst : CrossFadeState.showSecond; }); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/widget_panel/desk_search_bar.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:l10n/l10n.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; class DeskSearchBar extends StatefulWidget { final ValueChanged? onChanged; const DeskSearchBar({Key? key, this.onChanged}) : super(key: key); @override State createState() => _DeskSearchBarState(); } class _DeskSearchBarState extends State { late TextEditingController _controller; @override Widget build(BuildContext context) { return Autocomplete( optionsBuilder: buildOptions, onSelected: onSelected, optionsViewBuilder: _buildOptionsView, fieldViewBuilder: _buildFieldView, ); } void onSelected(WidgetModel model) { final FocusScopeNode focusScope = FocusScope.of(context); if (focusScope.hasFocus) { focusScope.unfocus(); } _controller.clear(); context.push('/widget/detail/${model.name}',extra: model); } Future> buildOptions(TextEditingValue textEditingValue) async { if (textEditingValue.text == '') { return const Iterable.empty(); } return searchByArgs(textEditingValue.text); } Future> searchByArgs(String text) { WidgetRepository repository = context.read().repository; return repository.searchWidgets(WidgetFilter( name: text )); } Widget _buildOptionsView(BuildContext context, AutocompleteOnSelected onSelected, Iterable options) { return Align( alignment: Alignment.topLeft, child: Material( elevation: 6, color: Colors.white, borderRadius: BorderRadius.circular(8), shadowColor: Colors.black, child: ConstrainedBox( constraints: BoxConstraints(maxHeight: 350,maxWidth: 250), child:ListView.builder( itemCount:options.length , padding: EdgeInsets.symmetric(vertical: 10), itemBuilder: (_,index) { WidgetModel model = options.elementAt(index); return InkWell( onTap: ()=>onSelected(model), child: Ink( padding: EdgeInsets.symmetric(vertical: 6, horizontal: 15), child: Row(children: [ Expanded(child: Text.rich(formSpan(model.name,_controller.text),maxLines:1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 12))), // Spacer(), const SizedBox(width: 10,), Text(model.nameCN,style: TextStyle(fontSize: 12),), ],), ), ); }), ), ), ); } Widget _buildFieldView(BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) { _controller = textEditingController; bool isDark = Theme.of(context).brightness == Brightness.dark; return TextField( controller: textEditingController, onChanged: widget.onChanged, style: const TextStyle(fontSize: 12), maxLines: 1, focusNode: focusNode, decoration: InputDecoration( prefixIconConstraints: BoxConstraints( minWidth: 30, ), filled: true, hoverColor: Colors.transparent, contentPadding: EdgeInsets.only(top: 0), fillColor: isDark?null:Color(0xffF1F2F3), prefixIcon: Icon( Icons.search, size: 18, color: Colors.grey, ), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Theme.of(context).primaryColor), borderRadius: BorderRadius.all(Radius.circular(8)), ), enabledBorder : OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.all(Radius.circular(8)), ), hintText: context.l10n.enterComponentName, hintStyle: TextStyle(fontSize: 12, color: Colors.grey)), ); } final TextStyle lightTextStyle = const TextStyle( color: Colors.red, fontSize: 12, fontWeight: FontWeight.bold, ); InlineSpan formSpan(String src, String pattern) { bool isDark = Theme.of(context).brightness == Brightness.dark; Color? textColor = Theme.of(context).listTileTheme.textColor; List span = []; RegExp regExp = RegExp(RegExp.escape(pattern), caseSensitive: false); src.splitMapJoin(regExp, onMatch: (Match match) { span.add(TextSpan(text: match.group(0), style: lightTextStyle)); return ''; }, onNonMatch: (str) { span.add(TextSpan( text: str, style: lightTextStyle.copyWith(color: isDark?textColor:const Color(0xff2F3032),fontSize: 12))); return ''; }); return TextSpan(children: span); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/widget_panel/desk_search_bar_v2.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:l10n/l10n.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_module/event/widget_event.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:tolyui/tolyui.dart'; import 'package:fx_trace/fx_trace.dart'; class GlobalFindDialog extends StatefulWidget { final ValueChanged? onChanged; const GlobalFindDialog({Key? key, this.onChanged}) : super(key: key); @override State createState() => _GlobalFindDialogState(); } class _GlobalFindDialogState extends State { late TextEditingController _controller = TextEditingController(); // final PopoverController controller = PopoverController(); @override void initState() { _focusNode.addListener(_onFocusChange); super.initState(); } @override void dispose() { super.dispose(); _focusNode.removeListener(_onFocusChange); } void _onFocusChange() { // if (_focusNode.hasFocus) { // controller.open(); // } else { // controller.close(); // } } @override Widget build(BuildContext context) { return Dialog( surfaceTintColor: Colors.transparent, backgroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), child: SizedBox( width: 400, height: 400, child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ SizedBox(height: 32, child: _buildFieldView()), Expanded(child: _buildOptionsView()), ], ), ), )); ; } void onSelected(WidgetModel model) { final FocusScopeNode focusScope = FocusScope.of(context); if (focusScope.hasFocus) { focusScope.unfocus(); } _controller.clear(); Navigator.of(context).pop(); FxEmitter().emit(SelectWidgetEvent(name: model.name)); } Iterable options = []; void searchByArgs(String text) async { WidgetRepository repository = context.read().repository; options = await repository.searchWidgets(WidgetFilter(name: text)); setState(() {}); } Widget _buildOptionsView() { return Align( alignment: Alignment.topLeft, child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), child: ListView.builder( itemCount: options.length, padding: const EdgeInsets.symmetric(vertical: 10), itemBuilder: (_, index) { WidgetModel model = options.elementAt(index); return InkWell( onTap: () => onSelected(model), child: Ink( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 15), child: Row( children: [ Expanded( child: Text.rich( formSpan(model.name, _controller.text), maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12))), // Spacer(), const SizedBox( width: 10, ), Text( model.nameCN, style: const TextStyle(fontSize: 12), ), ], ), ), ); }), ), ); } final FocusNode _focusNode = FocusNode(); FocusNode get focusNode => _focusNode; Widget _buildFieldView() { bool isDark = Theme.of(context).brightness == Brightness.dark; return TextField( autofocus: true, controller: _controller, onChanged: (value) { searchByArgs(value); }, style: const TextStyle(fontSize: 12), maxLines: 1, focusNode: focusNode, decoration: InputDecoration( prefixIconConstraints: const BoxConstraints(minWidth: 30), filled: true, hoverColor: Colors.transparent, contentPadding: const EdgeInsets.only(top: 0), fillColor: isDark ? null : const Color(0xffF1F2F3), prefixIcon: const Icon( Icons.search, size: 18, color: Colors.grey, ), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Theme.of(context).primaryColor), borderRadius: const BorderRadius.all(Radius.circular(8)), ), enabledBorder: const OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.all(Radius.circular(8)), ), hintText: '搜索', hintStyle: const TextStyle(fontSize: 12, color: Colors.grey)), ); } final TextStyle lightTextStyle = const TextStyle( color: Colors.red, fontSize: 12, fontWeight: FontWeight.bold, ); InlineSpan formSpan(String src, String pattern) { bool isDark = Theme.of(context).brightness == Brightness.dark; Color? textColor = Theme.of(context).listTileTheme.textColor; List span = []; RegExp regExp = RegExp(RegExp.escape(pattern), caseSensitive: false); src.splitMapJoin(regExp, onMatch: (Match match) { span.add(TextSpan(text: match.group(0), style: lightTextStyle)); return ''; }, onNonMatch: (str) { span.add(TextSpan( text: str, style: lightTextStyle.copyWith( color: isDark ? textColor : const Color(0xff2F3032), fontSize: 12))); return ''; }); return TextSpan(children: span); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/widget_panel/desk_widget_top_bar.dart ================================================ import 'package:app/app.dart'; import 'package:l10n/l10n.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:widget_repository/widget_repository.dart'; import 'desk_search_bar.dart'; class DeskWidgetTopBar extends StatefulWidget { final ValueChanged onTabPressed; const DeskWidgetTopBar({Key? key, required this.onTabPressed}) : super(key: key); @override State createState() => _DeskWidgetTopBarState(); } class _DeskWidgetTopBarState extends State with SingleTickerProviderStateMixin { late TabController tabController; List get _tabs { final provider = WidgetStatisticsProvider(); final stats = provider.statistics; if (stats == null) { return [ context.l10n.stateless, context.l10n.stateful, context.l10n.single, context.l10n.multi, context.l10n.sliver, context.l10n.proxy, context.l10n.other, ]; } return [ context.l10n.stateless, context.l10n.stateful, context.l10n.single, context.l10n.multi, context.l10n.sliver, context.l10n.proxy, context.l10n.other, ]; } List _buildTabWidgets() { final provider = WidgetStatisticsProvider(); final stats = provider.statistics; final counts = [ stats?.familyCount[WidgetFamily.stateless] ?? 0, stats?.familyCount[WidgetFamily.stateful] ?? 0, stats?.familyCount[WidgetFamily.singleChildRender] ?? 0, stats?.familyCount[WidgetFamily.multiChildRender] ?? 0, stats?.familyCount[WidgetFamily.sliver] ?? 0, stats?.familyCount[WidgetFamily.proxy] ?? 0, stats?.familyCount[WidgetFamily.other] ?? 0, ]; return List.generate( _tabs.length, (index) => Stack( clipBehavior: Clip.none, children: [ Text(_tabs[index]), if (tabController.index == index) Positioned( right: -10, top: -6, child: Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 3), decoration: BoxDecoration( color: Theme.of(context) .primaryColor .withValues(alpha: 0.6), borderRadius: BorderRadius.circular(6), ), child: Text('${counts[index]}', style: const TextStyle( fontFamily: '黑体', height: 1, fontSize: 9, color: Colors.white)), ), ), ], )); } @override void initState() { super.initState(); tabController = TabController(length: 7, vsync: this); } @override Widget build(BuildContext context) { Color themeColor = Theme.of(context).primaryColor; bool isDark = Theme.of(context).brightness == Brightness.dark; return DragToMoveWrapper( child: Container( padding: const EdgeInsets.only(left: 20), height: 64, color: isDark ? Color(0xff2C3036) : Colors.white, child: Row( children: [ SizedBox( width: 380, child: TabBar( onTap: widget.onTabPressed, tabAlignment: TabAlignment.start, indicatorSize: TabBarIndicatorSize.label, labelPadding: const EdgeInsets.symmetric(horizontal: 6), isScrollable: true, indicator: RoundRectTabIndicator( borderSide: BorderSide(color: themeColor, width: 3), ), labelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), controller: tabController, labelColor: themeColor, indicatorWeight: 3, unselectedLabelColor: Colors.grey, indicatorColor: themeColor, tabs: _buildTabWidgets() .map((Widget widget) => Tab(child: widget)) .toList(), ), ), Spacer(), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: SizedBox(width: 250, height: 30, child: DeskSearchBar()), ), // const SizedBox(width: 20,), const WindowButtons(), ], ), ), ); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/desk_ui/widget_panel/widget_panel.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_module/widget_module.dart'; import 'package:widget_repository/widget_repository.dart'; import 'desk_widget_top_bar.dart'; import 'package:widget_ui/widget_ui.dart'; class DeskWidgetPanel extends StatefulWidget { final Widget? header; const DeskWidgetPanel({super.key, this.header}); @override State createState() => _DeskWidgetPanelState(); } class _DeskWidgetPanelState extends State { @override Widget build(BuildContext context) { WidgetsState state = context.watch().state; return Scaffold( body: Column( children: [ DeskWidgetTopBar(onTabPressed: _switchTab), const Divider(height: 1), Expanded( child: switch (state) { WidgetsLoading() => const CupertinoActivityIndicator(), WidgetsLoaded() => WidgetList( state: state, header: widget.header, ), WidgetsLoadFailed() => Center(child: Text("${state.runtimeType}")), }, ), ], ), ); } void _switchTab(int index) { WidgetFamily widgetFamily = WidgetFamily.values[index]; context.switchWidgetFamily(widgetFamily); } } class WidgetList extends StatelessWidget { final Widget? header; final WidgetsLoaded state; const WidgetList({super.key, required this.state, this.header}); @override Widget build(BuildContext context) { SliverGridDelegate gridDelegate = const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 400, mainAxisSpacing: 10, mainAxisExtent: 110, crossAxisSpacing: 10, ); return CustomScrollView( slivers: [ SliverToBoxAdapter( child: Container( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), margin: EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: Theme.of(context).listTileTheme.tileColor, borderRadius: BorderRadius.circular(8)), height: 180, child: header, ), ), SliverPadding( padding: const EdgeInsets.only(left: 14, right: 14, bottom: 8), sliver: SliverGrid.builder( gridDelegate: gridDelegate, itemBuilder: (_, index) => WidgetItem( model: state.widgets[index], onWidget: context.handleWidgetAction, ), itemCount: state.widgets.length, ), ) ], ); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/category_detail.dart ================================================ import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_star/flutter_star.dart'; import 'package:go_router/go_router.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; /// create by 张风捷特烈 on 2020-04-22 /// contact me by email 1981462002@qq.com /// 说明: class CategoryShow extends StatelessWidget { final CategoryModel model; const CategoryShow({Key? key, required this.model}) : super(key: key); @override Widget build(BuildContext context) { CategoryWidgetState state = context.watch().state; Widget child = const SizedBox(); if (state is CategoryWidgetLoadedState) { child = _buildWidgetList(state.widgets); } return Scaffold( appBar: AppBar(title: Text(model.name)), body: child, ); } Widget _buildWidgetList(List widgets) { return ListView.separated( separatorBuilder: (_, index) => const Divider(height: 1), itemBuilder: (context, index) => Dismissible( direction: DismissDirection.endToStart, key: ValueKey(widgets[index].id), background: Container( padding: const EdgeInsets.only(right: 20), alignment: Alignment.centerRight, color: Colors.red, child: const Icon( CupertinoIcons.delete_solid, color: Colors.white, size: 30, ), ), onDismissed: (v) { BlocProvider.of(context).add( EventToggleCategoryWidget(model.id!, widgets[index].id)); }, child: Container( margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), child: FeedbackWidget( duration: const Duration(milliseconds: 200), onPressed: () => _toDetailPage(context, widgets[index]), child: // Container(height: 60,) SimpleWidgetItem( data: widgets[index], )), ), ), itemCount: widgets.length); } void _toDetailPage(BuildContext context, WidgetModel model) async { // Navigator.pushNamed(context, UnitRouter.widget_detail, arguments: model); context.push('/widget/detail/${model.name}',extra: model); } } class SimpleWidgetItem extends StatelessWidget { final WidgetModel data; const SimpleWidgetItem({Key? key, required this.data}) : super(key: key); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; Color shadowColor = isDark?Colors.black:Colors.white; return Container( height: 64, child: Row( children: [ _buildLeading(shadowColor), const SizedBox( width: 8, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [_buildTitle(shadowColor), _buildSummary(shadowColor)], ), ) ], ), ); } Widget _buildTitle(Color shadowColor) { return Row( children: [ Expanded( child: Text(data.name, overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, shadows: [ Shadow(color: shadowColor, offset: Offset(.3, .3)) ])), ), const SizedBox(width: 15), StarScore( star: Star(emptyColor: shadowColor, size: 12, fillColor: data.color), score: data.lever, ) ], ); } Widget _buildLeading(Color shadowColor) => Padding( padding: const EdgeInsets.only(left: 5, right: 5), child: data.image == null ? Material( color: Colors.transparent, child: CircleText( text: data.name, size: 50, color: data.color, ), ) : CircleImage( image: data.image!, size: 50, ), ); Widget _buildSummary(Color shadowColor) { return Text( data.info, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( color: Color(0xFF757575), fontSize: 12, shadows: [Shadow(color: shadowColor, offset: Offset(.5, .5))]), ); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/category_list_item.dart ================================================ import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:widget_repository/widget_repository.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: class CategoryListItem extends StatelessWidget { final CategoryModel data; final Function(CategoryModel)? onDeleteItemClick; final Function(CategoryModel)? onEditItemClick; const CategoryListItem({Key? key, required this.data, this.onDeleteItemClick,this.onEditItemClick}) : super(key: key); @override Widget build(BuildContext context) { return Container( child: _buildChild(context), padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Theme.of(context).listTileTheme.tileColor, boxShadow: [ BoxShadow(color: Colors.black.withOpacity(0.01),blurRadius: 6,offset: Offset(0,2)) ], borderRadius: BorderRadius.all(Radius.circular(8)), ), ); } _buildChild(BuildContext context) { final Color themeColor = Theme.of(context).primaryColor; return Column( children: [ _buildTitle(themeColor), const SizedBox(height: 8,), const Divider(), Expanded( child: Container( padding: const EdgeInsets.only(top: 10, left: 10, right: 10), child: Text(data.info, maxLines: 4, style: TextStyle( color: Colors.grey, fontSize: 14, shadows: [ Shadow(color: themeColor.withOpacity(0.4), offset:const Offset(.2,.2),blurRadius: .5) ])), ),), const Divider(), const SizedBox(height: 8,), Text( '创建于: ${data.createDate}', style: const TextStyle(color: Colors.grey, fontSize: 12), ), ], ); } Row _buildTitle(Color themeColor) { return Row( children: [ CircleText( text: "${data.count}", size: 35, fontSize: 14, backgroundColor: data.color, ), const SizedBox( width: 10, ), Expanded( child: Text( data.name, style: const TextStyle(fontWeight: FontWeight.bold), )), FeedbackWidget( onPressed: () { onEditItemClick?.call(data); }, child: Icon( Icons.edit, size: 20, color: themeColor, ), ), const SizedBox(width: 4,), FeedbackWidget( onPressed: () { onDeleteItemClick?.call(data); }, child: const Icon( CupertinoIcons.delete_solid, color: Colors.red, size: 20, ), ), ], ); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/category_page.dart ================================================ import 'package:app/app.dart'; import 'package:components/project_ui/project_ui.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; import 'category_list_item.dart'; import 'delete_category_dialog.dart'; import 'edit_category_panel.dart'; import 'empty_category.dart'; class CategoryPage extends StatelessWidget { final SliverGridDelegateWithMaxCrossAxisExtent gridDelegate = const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, mainAxisExtent: 200, mainAxisSpacing: 8, crossAxisSpacing: 8, // crossAxisCount: 2, // childAspectRatio: 0.8, ); final SliverGridDelegateWithFixedCrossAxisCount deskGridDelegate = const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 0.9, ); const CategoryPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder(builder: (ctx, state) { if (state is CategoryLoadedState) { return CustomScrollView( slivers: [ SliverOverlapInjector( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(ctx), ), _buildContent(context, state), ], ); } if (state is CategoryLoadingState) return const LoadingShower(); return const EmptyCategory(); }); } _buildContent(BuildContext context, CategoryLoadedState state) { double bottom = MediaQuery.of(context).padding.bottom; return SliverPadding( padding: EdgeInsets.only(top: 10, left: 10, right: 10, bottom: 20 + bottom), sliver: SliverLayoutBuilder(builder: (_, c) { SliverGridDelegate delegate = gridDelegate; if (c.crossAxisExtent > 500) { delegate = deskGridDelegate; } return SliverGrid( delegate: SliverChildBuilderDelegate( (_, index) => GestureDetector( onTap: () => _toDetailPage(context, state.categories[index]), child: CategoryListItem( data: state.categories[index], onDeleteItemClick: (model) => _deleteCollect(context, model), onEditItemClick: (model) => _editCollect(context, model), )), childCount: state.categories.length), gridDelegate: delegate); }), ); } ShapeBorder get rRectBorder => const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10))); void _deleteCollect(BuildContext context, CategoryModel model) { showDialog( context: context, builder: (ctx) => Dialog( elevation: 5, shape: rRectBorder, child: SizedBox( width: 50, child: DeleteCategoryDialog( title: '删除收藏集', content: ' 删除【${model.name}】收藏集,你将会失去其中的所有收藏组件,是否确定继续执行?', onSubmit: () { BlocProvider.of(context) .add(EventDeleteCategory(id: model.id!)); Navigator.of(context).pop(); }, ), ), )); } void _editCollect(BuildContext context, CategoryModel model) { showDialog( context: context, builder: (ctx) => Dialog( backgroundColor: const Color(0xFFF2F2F2), elevation: 5, shape: rRectBorder, child: Column( mainAxisSize: MainAxisSize.min, children: [ Gap.H5, Row( children: [ Padding( padding: const EdgeInsets.only(left: 20, right: 10), child: Circle( color: Theme.of(context).primaryColor, ), ), const Text( '修改收藏集', style: TextStyle(fontSize: 20), ), const Spacer(), const CloseButton() ], ), Padding( padding: const EdgeInsets.all(8.0), child: EditCategoryPanel( model: model, type: EditType.update, ), ), ], ), )); } void _toDetailPage(BuildContext context, CategoryModel model) { Locale l = Localizations.localeOf(context); String locale = '${l.languageCode}'; if (l.countryCode == null) { if (locale == 'en') { locale += '-US'; } } BlocProvider.of(context) .add(EventLoadCategoryWidget(model.id!, locale.toLowerCase())); context.push('/collection/widgets/${model.id}', extra: model); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/collect_page.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:l10n/l10n.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:authentication/views/authentic_widget.dart'; import '../../desk_ui/category_panel/desk_category_page.dart'; import 'category_page.dart'; import 'like_widget_page.dart'; import 'sync/async_button.dart'; import 'sync/upload_button.dart'; class CollectPageAdapter extends StatelessWidget { const CollectPageAdapter({Key? key,}) : super(key: key); @override Widget build(BuildContext context) { bool isDesk = Platform.isMacOS||Platform.isWindows||Platform.isLinux; if(isDesk){ return const DeskCategoryPage(); }else{ return const CollectPage(canPop:true); } } } class CollectPage extends StatefulWidget { final bool canPop; const CollectPage({Key? key, this.canPop=false}) : super(key: key); @override _CollectPageState createState() => _CollectPageState(); } class _CollectPageState extends State with AutomaticKeepAliveClientMixin { late final List _tabs = [ context.l10n.widgetsInn, context.l10n.likedWidgets, ]; @override Widget build(BuildContext context) { super.build(context); BuildContext _topContext = context; final Color color = Colors.blue.withAlpha(11); return Scaffold( // backgroundColor: Colors.white, body: Container( color: color, child: DefaultTabController( length: _tabs.length, child: NestedScrollView( headerSliverBuilder: (context, innerBoxIsScrolled) => [ SliverOverlapAbsorber( handle: NestedScrollView.sliverOverlapAbsorberHandleFor( context), sliver: _buildAppBar(_topContext, innerBoxIsScrolled)), ], body: const TabBarView( children: [ CategoryPage(), LikeWidgetPage(), ], ), ), ), )); } Widget _buildAppBar(BuildContext context, bool index) { // final Color color = Colors.blue; bool isDark = Theme.of(context).brightness == Brightness.dark; String image = isDark?'draw_bg3.webp':'caver.webp'; return SliverAppBar( systemOverlayStyle: const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light ), leading: widget.canPop?BackButton(color: Colors.white,):Container( margin: const EdgeInsets.all(10), child: FeedbackWidget( onPressed: () { // Navigator.of(context).pushNamed(UnitRouter.login); }, child: const CircleImage( image: AssetImage('assets/images/icon_head.webp'), borderSize: 1.5, ), )), backgroundColor: Theme.of(context).primaryColor, actionsIconTheme: IconThemeData(color: Colors.white), actions: [ SizedBox( width: 32, child: AuthenticWidget.just(const UploadCategoryButton())), // SizedBox(width: 5,), SizedBox( width: 32, child: AuthenticWidget.just(const SyncCategoryButton())), if(!widget.canPop) SizedBox(child: _buildAddAction(context)) ], title: Text( context.l10n.collectCollection, style: TextStyle( color: Colors.white, //标题 fontSize: 18, shadows: [ Shadow(color: Colors.blue, offset:Offset(1, 1), blurRadius: 2) ]), ), pinned: true, expandedHeight: 150.0, flexibleSpace: FlexibleSpaceBar( collapseMode: CollapseMode.parallax, //视差效果 background: Image.asset( "assets/images/$image", fit: BoxFit.cover, ), ), forceElevated: index, bottom: PreferredSize( preferredSize: const Size.fromHeight(30), child: TabBar( indicatorColor: Colors.transparent, unselectedLabelColor: Colors.white, labelColor: Colors.black, labelStyle: TextStyle(fontSize: 16, shadows: [ Shadow( color: Theme.of(context).primaryColor, offset: const Offset(1, 1), blurRadius: 10) ]), tabs: _buildTabs(), ), ), ); } Widget _buildAddAction(BuildContext context) => GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Scaffold.of(context).openEndDrawer(), child: SizedBox( width: 32, child: const Icon( Icons.add, size: 24, ), ), ); List _buildTabs() => _tabs .map( (String name) => Container( margin: const EdgeInsets.only(bottom: 5), alignment: Alignment.center, child: Text(name), ), ) .toList(); @override bool get wantKeepAlive => true; } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/delete_category_dialog.dart ================================================ import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: class DeleteCategoryDialog extends StatelessWidget { final String title; final String content; final VoidCallback? onSubmit; const DeleteCategoryDialog({Key? key, this.title='', this.content ='',this.onSubmit }) : super(key: key); @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ _buildBar(context), _buildTitle(context), _buildContent(), _buildFooter(context), ], ); } Widget _buildTitle(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( title, style: const TextStyle(color: Colors.red, fontSize: 20), ), ], ); } Widget _buildContent() { return Padding( padding: const EdgeInsets.all(15.0), child: Text(content, style: const TextStyle(color: Colors.grey, fontSize: 16), textAlign: TextAlign.justify, ), ); } Widget _buildFooter(context) { return Padding( padding: const EdgeInsets.only(bottom: 15.0, top: 10,left: 10,right: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ FeedbackWidget( onPressed: onSubmit, child: Container( alignment: Alignment.center, height: 40, width: 100, decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(30)), color: Theme.of(context).primaryColor), child: const Text('确 定', style: TextStyle(color: Colors.white, fontSize: 16)), ), ), FeedbackWidget( onPressed: ()=>Navigator.of(context).pop(), child: Container( alignment: Alignment.center, height: 40, width: 100, decoration: const BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(30)), color: Colors.orangeAccent), child: const Text('取 消', style: TextStyle(color: Colors.white, fontSize: 16)), ), ) ], ), ); } _buildBar(context) => Row( children: [ const Spacer(), GestureDetector( onTap: () => Navigator.of(context).pop(), child: Container( height: 30, alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 10, top: 5), child: Icon( Icons.close, color:Theme.of(context).primaryColor, ), ), ), ], ); } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/edit_category_panel.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:utils/utils.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; /// create by 张风捷特烈 on 2020-04-23 /// contact me by email 1981462002@qq.com /// 说明: enum EditType { add, update } class EditCategoryPanel extends StatefulWidget { final CategoryModel? model; final EditType type; const EditCategoryPanel({Key? key, this.model, this.type = EditType.add}) : super(key: key); @override _EditCategoryPanelState createState() => _EditCategoryPanelState(); } class _EditCategoryPanelState extends State { String name=''; String color=''; String info=''; int get colorIndex => widget.model == null ? 0 : UnitColor.collectColorSupport .map((e) => e.value) .toList() .indexOf(widget.model!.color.value); @override void initState() { super.initState(); info = widget.model?.info??''; color = (widget.model == null ? null : ColorUtils.colorString(widget.model!.color))??''; } @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), child: InputButton( defaultText: '${widget.model?.name??''}', config: const InputButtonConfig(hint: '收藏集名称', iconData: Icons.check), onSubmit: _doEdit, ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), child: EditPanel( defaultText: '${widget.model?.info??''}', submitClear: false, hint: '收藏集简介...', onChange: (v) => info = v, ), ), Container( alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), child: ColorChooser( defaultIndex: colorIndex, colors: UnitColor.collectColorSupport, onChecked: (v) => color = ColorUtils.colorString(v), ), ), ], ); } void _doEdit(String str){ name = str; if (name.isNotEmpty) { if (widget.type == EditType.add) { BlocProvider.of(context).add( EventAddCategory(name: name, info: info, color: color)); } if (widget.type == EditType.update) { BlocProvider.of(context).add( EventUpdateCategory( id: widget.model!.id!, name: name, info: info, color: color)); } } Navigator.of(context).pop(); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/empty_category.dart ================================================ import 'dart:io'; import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:path/path.dart' as path; import 'package:sqflite/sqflite.dart'; /// create by 张风捷特烈 on 2021/2/25 /// contact me by email 1981462002@qq.com /// 说明: class EmptyCategory extends StatelessWidget { const EmptyCategory({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Panel( radius: 15, color: UnitColor.warning_color.withOpacity(0.3), child: Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, direction: Axis.vertical, spacing: 5, children: [ const Text( " 您还没有收藏集! ", style: TextStyle(fontSize: 18,color: UnitColor.head_text_color), ), const SizedBox( width: 10, ), FeedbackWidget( onPressed: ()=>_recallDatabase(context), child: const Icon( Icons.refresh, textDirection: TextDirection.rtl, color: Colors.blue, size: 36, ), ), const Text( "恢复默认", style: TextStyle(fontSize: 14,color: UnitColor.input_hit_text_color), ), ], ), )); } _recallDatabase(BuildContext context) async{ String databasesPath = await getDatabasesPath(); String dbPath = path.join(databasesPath, "flutter.db"); ByteData data = await rootBundle.load(path.join("assets", "flutter.db")); List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); await File(dbPath).writeAsBytes(bytes, flush: true); BlocProvider.of(context).add(const EventLoadCategory()); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/home_right_drawer.dart ================================================ import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; import '../widget_page/unit_drawer_header.dart'; import 'edit_category_panel.dart'; class HomeRightDrawer extends StatefulWidget { const HomeRightDrawer({Key? key}) : super(key: key); @override _HomeRightDrawerState createState() => _HomeRightDrawerState(); } class _HomeRightDrawerState extends State { String name=''; String color=''; String info=''; @override Widget build(BuildContext context) { return Drawer( elevation: 3, child: _buildChild(context), ); } Widget _buildChild(BuildContext context) { // final Color color = BlocProvider.of(context).state.color; final Color color = Theme.of(context).scaffoldBackgroundColor; return Container( color: color, child: ListView(padding: EdgeInsets.zero, children: [ UnitDrawerHeader(color:color), _buildTitle(context), const EditCategoryPanel( ) ]), ); } Widget _buildTitle(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 5.0, bottom: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Circle( color: Theme.of(context).primaryColor, radius: 5, ), const Padding( padding: EdgeInsets.symmetric(horizontal: 8), child: Text( '添加收藏集', style: TextStyle(fontSize: 16, shadows: [ Shadow( color: Colors.white, offset: Offset(.5, .5), blurRadius: 1) ]), ), ), Circle( color: Theme.of(context).primaryColor, radius: 5, ), ], ), ); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/like_widget_page.dart ================================================ import 'package:app/app.dart'; import 'package:components/project_ui/project_ui.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_module/blocs/action/widget_action.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_ui/widget_ui.dart'; import 'package:widget_module/blocs/blocs.dart'; import '../widget_detail/collect_widget_list_item.dart'; import '../widget_detail/widget_detail_page.dart'; /// create by 张风捷特烈 on 2020/6/16 /// contact me by email 1981462002@qq.com /// 说明: class LikeWidgetPage extends StatelessWidget { const LikeWidgetPage({Key? key}) : super(key: key); final SliverGridDelegate gridDelegate = const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: 8, crossAxisSpacing: 8, childAspectRatio: 1 / 0.5, ); final SliverGridDelegate deskGridDelegate = const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1 / 0.5, ); @override Widget build(BuildContext context) { return BlocBuilder>( builder: (ctx, state) { return CustomScrollView( slivers: [ SliverOverlapInjector( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(ctx), ), _buildContent(context, state), const SliverToBoxAdapter( child: NoMoreWidget(), ) ], ); }); } Widget _buildContent(BuildContext context, List state) { return SliverPadding( padding: const EdgeInsets.only(top: 10, left: 10, right: 10, bottom: 0), sliver: SliverLayoutBuilder( builder: (_, c) { SliverGridDelegate delegate = gridDelegate; if (c.crossAxisExtent > 500) { delegate = deskGridDelegate; } return SliverGrid( delegate: SliverChildBuilderDelegate( (_, index) => GestureDetector( onTap: () => _toDetailPage(context, state[index]), child: CollectWidgetListItem( data: state[index], onDeleteItemClick: (model) => context.toggleLike(model.id), )), childCount: state.length), gridDelegate: delegate); }, )); } _toDetailPage(BuildContext context, WidgetModel model) { Navigator.push( context, SlidePageRoute(child: WidgetDetailPageScope(model: model))); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/sync/async_button.dart ================================================ import 'dart:convert'; import 'package:app/app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'category_api.dart'; import 'package:utils/utils.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:storage/storage.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_ui/widget_ui.dart'; import 'package:toly_ui/toly_ui.dart'; /// create by 张风捷特烈 on 2021/2/24 /// contact me by email 1981462002@qq.com /// 说明: 同步数据按钮,点击时请求服务器,获取备份数据。 class SyncCategoryButton extends StatefulWidget { const SyncCategoryButton({Key? key}) : super(key: key); @override _SyncCategoryButtonState createState() => _SyncCategoryButtonState(); } enum AsyncType { loading, error, none, success } class _SyncCategoryButtonState extends State { AsyncType state = AsyncType.none; @override Widget build(BuildContext context) { Widget result; switch (state) { case AsyncType.loading: result = _buildLoading(); break; case AsyncType.error: result = _buildError(); break; case AsyncType.none: result = _buildDefault(); break; case AsyncType.success: result = _buildSuccess(); break; } return result; } Widget _buildLoading() { return const CupertinoActivityIndicator(); } Widget _buildError() { return const Icon( TolyIcon.error, size: 25, color: Colors.red, ); } Widget _buildDefault() { return FeedbackWidget( child: const Icon( TolyIcon.download, size: 24, ), onPressed: _doSync); } void _doSync() async { setState(() => state = AsyncType.loading); TaskResult result = await CategoryApi.getCategoryData(); if (result.success) { // 说明请求成功 if (result.data != null) { //说明有后台备份数据,进行同步操作 CategoryRepository repository = BlocProvider.of(context).repository; await repository.syncCategoryByData(result.data!.data,result.data!.likeData); BlocProvider.of(context).add(const EventLoadCategory()); context.read().loadLikeData(); } else { // 说明还没有后台数据, // 这里防止有傻孩子没点备份,就点同步,哥哥好心,给备份一下。 CategoryRepository rep = BlocProvider.of(context).repository; List loadCategories = await rep.loadCategoryData(); List likeData = await AppStorage().flutter().likeWidgetIds(); String json = jsonEncode(loadCategories); String likeJson = jsonEncode(likeData); await CategoryApi.uploadCategoryData(data: json,likeData: likeJson); } setState(() => state = AsyncType.success); _toDefault(); } else { setState(() => state = AsyncType.error); _toDefault(); } } Widget _buildSuccess() => const Icon( TolyIcon.upload_success, size: 22, color: Colors.green, ); void _toDefault() async { await Future.delayed(const Duration(milliseconds: 800)); setState(() { state = AsyncType.none; }); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/sync/category_api.dart ================================================ import 'package:app/app.dart'; import 'package:utils/utils.dart'; /// create by 张风捷特烈 on 2021/2/24 /// contact me by email 1981462002@qq.com /// 说明: class CategoryApi { static Future> uploadCategoryData( {required String data, required String likeData}) async { String errorMsg = ""; try { var result = await HttpUtil.instance.client.post( PathUnit.categoryDataSync, data: {"data": data, "likeData": likeData}); print(result.data); if (result.data != null) { return TaskResult.success(data:result.data['status']); } } catch (e) { print(e); errorMsg = e.toString(); } return TaskResult.error(msg: '请求错误: $errorMsg'); } static Future> getCategoryData() async { String errorMsg = ""; var result = await HttpUtil.instance .client .get(PathUnit.categoryData) .catchError((err) { errorMsg =err.toString(); }); // 获取的数据非空且 status = true if (result.data != null && result.data['status']) { // 说明有数据 if (result.data['data'] != null) { return TaskResult.success(data:CategoryData.fromJson(result.data['data'])); } else { return const TaskResult.success(data:null); } } return TaskResult.error(msg: '请求错误: $errorMsg'); } } class CategoryData{ final int categoryDataId; final int userId; final String data; final String likeData; CategoryData( {required this.categoryDataId, required this.userId, required this.data, required this.likeData}); factory CategoryData.fromJson(Map map) { return CategoryData( categoryDataId: map['categoryDataId'], userId: map["userId"], likeData: map["likeData"], data: map["data"]); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/category_page/sync/upload_button.dart ================================================ import 'dart:convert'; import 'package:app/app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:storage/storage.dart'; import 'category_api.dart'; import 'package:utils/utils.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:widget_repository/widget_repository.dart'; /// create by 张风捷特烈 on 2021/2/24 /// contact me by email 1981462002@qq.com /// 说明: class UploadCategoryButton extends StatefulWidget { const UploadCategoryButton({Key? key}) : super(key: key); @override _UploadCategoryButtonState createState() => _UploadCategoryButtonState(); } enum AsyncType { loading, error, none, success } class _UploadCategoryButtonState extends State { AsyncType state = AsyncType.none; @override Widget build(BuildContext context) { Widget result; switch (state) { case AsyncType.loading: result = _buildLoading(); break; case AsyncType.error: result = _buildError(); break; case AsyncType.none: result = _buildDefault(); break; case AsyncType.success: result = _buildSuccess(); break; } return result; } Widget _buildLoading() { return const CupertinoActivityIndicator(); } Widget _buildError() { return const Icon( TolyIcon.error, size: 22, color: Colors.redAccent, ); } Widget _buildDefault() { return FeedbackWidget( child: const Icon( TolyIcon.upload, size: 24, ), onPressed: _doUploadCategoryData); } void _doUploadCategoryData() async { setState(() => state = AsyncType.loading); CategoryRepository rep = BlocProvider.of(context).repository; List loadCategories = await rep.loadCategoryData(); List likeData = await AppStorage().flutter().likeWidgetIds(); String json = jsonEncode(loadCategories); String likeJson = jsonEncode(likeData); TaskResult result = await CategoryApi.uploadCategoryData(data: json, likeData: likeJson); if (result.success) { setState(() => state = AsyncType.success); _toDefault(); } else { setState(() => state = AsyncType.error); _toDefault(); } } Widget _buildSuccess() { return const Icon( TolyIcon.upload_success, size: 22, color: Colors.green, ); } void _toDefault() async { await Future.delayed(const Duration(milliseconds: 800)); setState(() { state = AsyncType.none; }); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/mobile_ui.dart ================================================ export 'widget_detail/widget_detail_page.dart'; export 'widget_detail/node_display/node_title.dart'; export 'widget_page/standard_home_page.dart'; export 'widget_page/widget_page.dart'; export 'category_page/category_page.dart'; export 'category_page/collect_page.dart'; export 'category_page/category_detail.dart'; export 'category_page/home_right_drawer.dart'; ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/search_page/app_search_bar.dart ================================================ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_module/blocs/blocs.dart'; class AppSearchBar extends StatefulWidget { const AppSearchBar({Key? key}):super(key: key); @override _AppSearchBarState createState() => _AppSearchBarState(); } class _AppSearchBarState extends State { final TextEditingController _controller = TextEditingController(); //文本控制器 @override Widget build(BuildContext context) { bool isDesk = Platform.isMacOS||Platform.isWindows||Platform.isLinux; return Stack( alignment: Alignment.centerRight, children: [ SizedBox( height: 35, child: TextField( autofocus: true, controller: _controller, maxLines: 1, decoration: InputDecoration( filled: true, fillColor: Colors.white, contentPadding: EdgeInsets.only(top: isDesk?6:1),//调整文字边距 prefixIcon: Icon(Icons.search), border: UnderlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.all(Radius.circular(15)), ), hintText: "搜点啥...", hintStyle: TextStyle(fontSize: 14)), onChanged: _doSearch, onSubmitted: (str) { //提交后,收起键盘 FocusScope.of(context).requestFocus(FocusNode()); }, )), _buildClearIcon() ], ); } void _doSearch(String str) { WidgetsBloc widgetsBloc = BlocProvider.of(context); final WidgetFilter filter = widgetsBloc.state.filter.copyWith( name: str, ); widgetsBloc.add( EventSearchWidget(filter: filter), ); } @override void dispose() { _controller.dispose(); super.dispose(); } Widget _buildClearIcon() { return ValueListenableBuilder( valueListenable: _controller, builder: (_, TextEditingValue value, __) => value.text.isNotEmpty ? GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { _controller.clear(); _doSearch(''); }, child: const Padding( padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8.0), child: Icon( CupertinoIcons.clear_circled, color: Colors.black, size: 20, ), ), ) : const SizedBox.shrink()); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/search_page/standard_search_bar.dart ================================================ import 'package:app/app.dart'; import 'package:l10n/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_module/blocs/blocs.dart'; class StandardSearchBarInner extends StatelessWidget implements PreferredSizeWidget { const StandardSearchBarInner({Key? key}) : super(key: key); @override Size get preferredSize => const Size.fromHeight(35 + 8 * 2); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; Color? color = Theme.of(context).appBarTheme.backgroundColor; return Container( color: isDark ? color : Colors.white, padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( children: [ const SizedBox( width: 15, ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { Navigator.of(context).maybePop(); }, child: const SizedBox( height: 32, width: 32, child: Icon(Icons.arrow_back)), ), Expanded( child: Container( height: 35, padding: const EdgeInsets.only(left: 10, right: 10), child: Material( color: Colors.transparent, child: TextField( autofocus: true, enabled: true, cursorColor: Colors.blue, maxLines: 1, onChanged: (str) => _doSearch(context, str), onSubmitted: (str) { //提交后,收起键盘 FocusScope.of(context).requestFocus(FocusNode()); }, decoration: InputDecoration( filled: true, fillColor: isDark ? Color(0xff292929) : Color(0xffF3F6F9), prefixIcon: Icon( Icons.search, color: Colors.grey, size: 20, ), prefixIconConstraints: BoxConstraints( maxHeight: 24, minWidth: 36 ), isCollapsed: true, contentPadding: EdgeInsets.only(top: 4,bottom: 4,right: 8), border: UnderlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.all(Radius.circular(8)), ), hintText:context.l10n.searchWidget, hintStyle: TextStyle(fontSize: 14)), ), )), ), Wrap( spacing: 3, alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Icon(TolyIcon.icon_sound), // Text('已签',style: TextStyle(color: Colors.grey),) ], ), const SizedBox(width: 15) ], ), ); } void _doSearch(BuildContext context, String str) { WidgetsBloc widgetsBloc = BlocProvider.of(context); final WidgetFilter filter = widgetsBloc.state.filter.copyWith( name: str, ); widgetsBloc.add( EventSearchWidget(filter: filter), ); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/search_page/standard_search_page.dart ================================================ import 'package:app/app.dart'; import 'package:components/components.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:l10n/l10n.dart'; import 'package:widget_module/widget_module.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_ui/widget_ui.dart'; import 'standard_search_bar.dart'; // SearchPage 可以复用 WidgetsBloc,进行局部的 Bloc // 不必单独提供 SearchBloc 增加复杂性 class StandardSearchPageProvider extends StatelessWidget { const StandardSearchPageProvider({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocProvider( lazy: false, create: (BuildContext context) => WidgetsBloc( repository: BlocProvider.of(context).repository, ), child: const StandardSearchPage(), ); } } class StandardSearchPage extends StatelessWidget { const StandardSearchPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; Color color = isDark ? Theme.of(context).appBarTheme.backgroundColor ?? Colors.black : Colors.white; return Scaffold( body: Column( children: [ Container( color: color, height: MediaQuery.of(context).padding.top, width: MediaQuery.of(context).size.width, ), const StandardSearchBarInner(), Expanded( child: BlocBuilder( builder: _buildBodyByState, )) ], ), ); } Widget _buildBodyByState(BuildContext context, WidgetsState state) { Widget noSearchArg = NotSearchPage( tips: context.l10n.searchSomething, ); if (state.filter.name.isEmpty) { return noSearchArg; } if (state is WidgetsLoaded) { if (state.widgets.isEmpty) { return EmptyShower(message: context.l10n.emptySearch); } return ListView.builder( padding: EdgeInsets.zero, itemBuilder: (_, index) => Padding( padding: const EdgeInsets.only(left: 8.0, right: 8, top: 8), child: WidgetItem( searchArgs: state.filter.name, model: state.widgets[index], onWidget: context.handleWidgetAction, ), ), itemCount: state.widgets.length, ); } if (state is WidgetsLoading) { return const LoadingShower(); } if (state is WidgetsLoadFailed) { return const ErrorPage(); } return noSearchArg; } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/category_end_drawer.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; import '../widget_page/unit_drawer_header.dart'; /// create by 张风捷特烈 on 2020-04-22 /// contact me by email 1981462002@qq.com /// 说明: class CategoryEndDrawer extends StatelessWidget { final WidgetModel widget; const CategoryEndDrawer({Key? key, required this.widget}) : super(key: key); @override Widget build(BuildContext context) { return Drawer( child: ListView(padding: EdgeInsets.zero, children: [ UnitDrawerHeader(color: Theme.of(context).primaryColor), Padding( padding: const EdgeInsets.all(10.0), child: Row( children: [ Circle( color: widget.color, ), const SizedBox( width: 10, ), Text(widget.name) ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 15.0), child: Panel( child: Text(widget.info, style: UnitTextStyle.shadowTextStyle), ), ), const Divider(), _buildTitle(context), const Divider(), CategoryInfo(widget.id) ]), ); } Widget _buildTitle(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 5.0, bottom: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Circle( color: Theme.of(context).primaryColor, radius: 5, ), const Padding( padding: EdgeInsets.symmetric(horizontal: 8), child: Text( '当前组件收藏情况', style: TextStyle(fontSize: 16, shadows: [ Shadow( color: Colors.white, offset: Offset(.5, .5), blurRadius: 1) ]), ), ), Circle( color: Theme.of(context).primaryColor, radius: 5, ), ], ), ); } } class CategoryInfo extends StatefulWidget { final int id; const CategoryInfo(this.id, {Key? key}) : super(key: key); @override _CategoryInfoState createState() => _CategoryInfoState(); } class _CategoryInfoState extends State { List categoryIds = []; List _categories = []; @override void didChangeDependencies() { _loadCategoryIds(); super.didChangeDependencies(); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Wrap( alignment: WrapAlignment.spaceBetween, spacing: 4, runSpacing: 8, children: categories.map((e) => _buildItem(e)).toList(), ), ); } Widget _buildItem(CategoryModel category) { bool inHere = categoryIds.contains(category.id); return FilterChip( backgroundColor: Theme.of(context).primaryColor.withAlpha(33), selectedColor: Colors.orange.withAlpha(120), shadowColor: Theme.of(context).primaryColor, elevation: 1, // labelPadding: const EdgeInsets.only(right: 4,left: 4), avatar: Circle( radius: 10, color: category.color, ), selected: inHere, label: Text(category.name), onSelected: (v) async { await repository.toggleCategory(category.id!, widget.id); _loadCategoryIds(); String locale = context.read().language.code; BlocProvider.of(context) .add(EventLoadCategoryWidget(category.id!, locale)); }); } CategoryRepository get repository => BlocProvider.of(context).repository; List get categories { CategoryState state = BlocProvider.of(context).state; if (state is CategoryLoadedState) { _categories = state.categories; } return _categories; } void _loadCategoryIds() async { categoryIds = await repository.getCategoryByWidget(widget.id); if (mounted) setState(() {}); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/collect_widget_list_item.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_star/flutter_star.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:widget_repository/widget_repository.dart'; class CollectWidgetListItem extends StatelessWidget { final WidgetModel data; final Function(WidgetModel model)? onDeleteItemClick; const CollectWidgetListItem({Key? key, required this.data, this.onDeleteItemClick}) : super(key: key); @override Widget build(BuildContext context) { return Stack( children: [ Material( color: Theme.of(context).listTileTheme.tileColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0,vertical: 6), child: Row( children: [ _buildLeading(), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTitle(), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, children: [ _buildSummary(), StarScore( star: Star( emptyColor: Colors.white, size: 12, fillColor: itemColor), score: data.lever, ) ], ), const Spacer(), FeedbackWidget( onPressed: () { onDeleteItemClick?.call(data); }, child: const Icon( CupertinoIcons.delete_solid, color: Colors.red, size: 20, ), ) ], ) ], ), ), ], ), ), ), ], ); } Widget _buildLeading() => Padding( padding: const EdgeInsets.only(right: 8), child: data.image == null ? Material( color: Colors.transparent, child: CircleText( text: data.name, size: 40, fontSize: 18, color: itemColor, ), ) : CircleImage( image: data.image!, size: 40, ), ); Color get itemColor => Cons.tabColors[data.family.index]; Widget _buildTitle() => Row( children: [ Expanded( child: Text(data.name, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, shadows: [ Shadow( color: Colors.white, offset: Offset(.3, .3)) ])), ), ], ); Widget _buildSummary() => Padding( padding: const EdgeInsets.only(bottom: 5, top: 5), child: Text( data.nameCN, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( color: Colors.grey[600], fontSize: 12, shadows: const [Shadow(color: Colors.white, offset: Offset(.5, .5))]), ), ); } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/node_display/code_display.dart ================================================ import 'dart:math'; import 'package:app/app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:share_plus/share_plus.dart'; import 'package:toly_ui/code/code.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:tolyui/tolyui.dart' hide TolyCollapse,CollapseController; // import 'package:flutter_highlight/flutter_highlight.dart'; import 'collapse.dart'; class CodeDisplay extends StatefulWidget { final Widget display; final HighlighterStyle style; final String code; const CodeDisplay({ super.key, required this.display, required this.code, required this.style, }); @override State createState() => _CodeDisplayState(); } class _CodeDisplayState extends State { String? codeRes; void _loadAssets() async { codeRes = await codeData(); setState(() {}); } Future codeData() async { if (widget.code.startsWith('assets')) { return await rootBundle.loadString(widget.code); } else { return widget.code; } } @override Widget build(BuildContext context) { return Container( width: double.maxFinite, decoration: BoxDecoration( color: Theme.of(context).listTileTheme.tileColor, border: Border.all( color: Theme.of(context).dividerTheme.color ?? Colors.grey, width: Theme.of(context).dividerTheme.space ?? 1, ), boxShadow: [ BoxShadow(color: Colors.grey.withOpacity(0.05),spreadRadius: 1,blurRadius: 4) ], borderRadius: BorderRadius.circular(4)), child: Column( children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 12), child: widget.display, ), const Divider(), TolyCollapse( titleBuilder: _buildTitle, sizeCurve: Curves.ease, content: CodeWidget( code: widget.code, style: widget.style, ), duration: const Duration(milliseconds: 500), ) ], ), ); } _doShare() { Share.share(widget.code); } Widget _buildTitle(BuildContext context, Animation anima, CollapseController ctrl) { Color color = Theme.of(context).primaryColor; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8.0), child: Row( children: [ const Spacer(), FeedbackWidget( mode: FeedMode.fade, a: 0.4, onPressed: _doShare, child: Padding( padding: const EdgeInsets.only( right: 10, ), child: Icon( TolyIcon.icon_share, size: 20, color: color, ), ), ), FeedbackWidget( mode: FeedMode.fade, a: 0.4, onPressed: _copyCode, child: Padding( padding: const EdgeInsets.only( right: 10, ), child: Icon( Icons.copy_rounded, size: 20, color: color, ), ), ), GestureDetector( onTap: () => _toggleCode(ctrl), child: AnimatedBuilder( animation: anima, builder: (_, child) { return Transform.rotate( angle: pi / 2 * Curves.ease.transform(anima.value), child: Icon( TolyIcon.icon_code, color: color, ), ); }, )), ], )); } void _toggleCode(CollapseController ctrl) { if (!ctrl.isOpen) { _loadAssets(); } else { codeRes = null; } ctrl.toggle(); } void _copyCode() async { String code = await codeData(); await Clipboard.setData(ClipboardData(text: code)); $message.success(message: '代码复制成功!'); } } const githubTheme = { 'root': TextStyle(color: Color(0xff333333), backgroundColor: Colors.transparent), 'comment': TextStyle(color: Color(0xff999988), fontStyle: FontStyle.italic), 'quote': TextStyle(color: Color(0xff999988), fontStyle: FontStyle.italic), 'keyword': TextStyle(color: Color(0xff333333), fontWeight: FontWeight.bold), 'selector-tag': TextStyle(color: Color(0xff333333), fontWeight: FontWeight.bold), 'subst': TextStyle(color: Color(0xff333333), fontWeight: FontWeight.normal), 'number': TextStyle(color: Color(0xff008080)), 'literal': TextStyle(color: Color(0xff008080)), 'variable': TextStyle(color: Color(0xff008080)), 'template-variable': TextStyle(color: Color(0xff008080)), 'string': TextStyle(color: Color(0xffdd1144)), 'doctag': TextStyle(color: Color(0xffdd1144)), 'title': TextStyle(color: Color(0xff990000), fontWeight: FontWeight.bold), 'section': TextStyle(color: Color(0xff990000), fontWeight: FontWeight.bold), 'selector-id': TextStyle(color: Color(0xff990000), fontWeight: FontWeight.bold), 'type': TextStyle(color: Color(0xff445588), fontWeight: FontWeight.bold), 'tag': TextStyle(color: Color(0xff000080), fontWeight: FontWeight.normal), 'name': TextStyle(color: Color(0xff000080), fontWeight: FontWeight.normal), 'attribute': TextStyle(color: Color(0xff000080), fontWeight: FontWeight.normal), 'regexp': TextStyle(color: Color(0xff009926)), 'link': TextStyle(color: Color(0xff009926)), 'symbol': TextStyle(color: Color(0xff990073)), 'bullet': TextStyle(color: Color(0xff990073)), 'built_in': TextStyle(color: Color(0xff0086b3)), 'builtin-name': TextStyle(color: Color(0xff0086b3)), 'meta': TextStyle(color: Color(0xff999999), fontWeight: FontWeight.bold), 'deletion': TextStyle(backgroundColor: Color(0xffffdddd)), 'addition': TextStyle(backgroundColor: Color(0xffddffdd)), 'emphasis': TextStyle(fontStyle: FontStyle.italic), 'strong': TextStyle(fontWeight: FontWeight.bold), }; ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/node_display/collapse.dart ================================================ // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math'; import 'package:flutter/material.dart' hide CrossFadeState; import 'package:flutter/rendering.dart'; typedef AnimatedTitleBuilder = Widget Function( BuildContext context, Animation anima, CollapseController ctrl, ); /// A widget that cross-fades between two given children and animates itself /// between their sizes. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=PGK2UUAyE54} /// /// The animation is controlled through the [crossFadeState] parameter. /// [firstCurve] and [opacityCurve] represent the opacity curves of the two /// children. The [firstCurve] is inverted, i.e. it fades out when providing a /// growing curve like [Curves.linear]. The [sizeCurve] is the curve used to /// animate between the size of the fading-out child and the size of the /// fading-in child. /// /// This widget is intended to be used to fade a pair of widgets with the same /// width. In the case where the two children have different heights, the /// animation crops overflowing children during the animation by aligning their /// top edge, which means that the bottom will be clipped. /// /// The animation is automatically triggered when an existing /// [TolyCollapse] is rebuilt with a different value for the /// [crossFadeState] property. /// /// {@tool snippet} /// /// This code fades between two representations of the Flutter logo. It depends /// on a boolean field `_first`; when `_first` is true, the first logo is shown, /// otherwise the second logo is shown. When the field changes state, the /// [TolyCollapse] widget cross-fades between the two forms of the logo /// over three seconds. /// /// ```dart /// AnimatedCrossFade( /// duration: const Duration(seconds: 3), /// firstChild: const FlutterLogo(style: FlutterLogoStyle.horizontal, size: 100.0), /// secondChild: const FlutterLogo(style: FlutterLogoStyle.stacked, size: 100.0), /// crossFadeState: _first ? CrossFadeState.showFirst : CrossFadeState.showSecond, /// ) /// ``` /// {@end-tool} /// /// See also: /// /// * [AnimatedOpacity], which fades between nothing and a single child. /// * [AnimatedSwitcher], which switches out a child for a new one with a /// customizable transition, supporting multiple cross-fades at once. /// * [AnimatedSize], the lower-level widget which [TolyCollapse] uses to /// automatically change size. class TolyCollapse extends StatefulWidget { /// Creates a cross-fade animation widget. /// /// The [duration] of the animation is the same for all components (fade in, /// fade out, and size), and you can pass [Interval]s instead of [Curve]s in /// order to have finer control, e.g., creating an overlap between the fades. const TolyCollapse({ super.key, required this.content, this.title, this.opacityCurve = Curves.linear, this.sizeCurve = Curves.linear, this.alignment = Alignment.topCenter, this.titlePadding = const EdgeInsets.symmetric(vertical: 12.0), this.contentPadding = const EdgeInsets.only(top: 0, right: 8, left: 8, bottom: 8), required this.duration, this.reverseDuration, this.titleBuilder, this.controller, this.onOpen, this.onClose, this.excludeBottomFocus = true, }) : assert(title == null && titleBuilder != null || titleBuilder == null && title != null || titleBuilder != null && title != null); final Widget content; final Widget? title; final VoidCallback? onOpen; final VoidCallback? onClose; final AnimatedTitleBuilder? titleBuilder; final EdgeInsetsGeometry titlePadding; final EdgeInsetsGeometry contentPadding; final CollapseController? controller; /// The duration of the whole orchestrated animation. final Duration duration; /// The duration of the whole orchestrated animation when running in reverse. /// /// If not supplied, this defaults to [duration]. final Duration? reverseDuration; /// The fade curve of the second child. /// /// Defaults to [Curves.linear]. final Curve opacityCurve; /// The curve of the animation between the two children's sizes. /// /// Defaults to [Curves.linear]. final Curve sizeCurve; /// How the children should be aligned while the size is animating. /// /// Defaults to [Alignment.topCenter]. /// /// See also: /// /// * [Alignment], a class with convenient constants typically used to /// specify an [AlignmentGeometry]. /// * [AlignmentDirectional], like [Alignment] for specifying alignments /// relative to text direction. final AlignmentGeometry alignment; /// When true, this is equivalent to wrapping the bottom widget with an [ExcludeFocus] /// widget while it is at the bottom of the cross-fade stack. /// /// Defaults to true. When it is false, the bottom widget in the cross-fade stack /// can remain in focus until the top widget requests focus. This is useful for /// animating between different [TextField]s so the keyboard remains open during the /// cross-fade animation. final bool excludeBottomFocus; @override State createState() => _TolyCollapseState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty( 'alignment', alignment, defaultValue: Alignment.topCenter)); properties .add(IntProperty('duration', duration.inMilliseconds, unit: 'ms')); properties.add(IntProperty( 'reverseDuration', reverseDuration?.inMilliseconds, unit: 'ms', defaultValue: null)); } } class _TolyCollapseState extends State with TickerProviderStateMixin { late AnimationController _controller; late Animation _firstAnimation; late Animation _secondAnimation; CollapseController? _internalController; CollapseController get _collapseCtrl => widget.controller ?? _internalController!; @override void initState() { super.initState(); if (widget.controller == null) { _internalController = CollapseController(); } _collapseCtrl._attach(this); _controller = AnimationController( duration: widget.duration, reverseDuration: widget.reverseDuration, vsync: this, ); // if (widget.crossFadeState == CrossFadeState.showSecond) { // _controller.value = 1.0; // } _firstAnimation = _initAnimation(Curves.linear, true); _secondAnimation = _initAnimation(widget.opacityCurve, false); _controller.addStatusListener((AnimationStatus status) { setState(() { // Trigger a rebuild because it depends on _isTransitioning, which // changes its value together with animation status. }); }); } Animation _initAnimation(Curve curve, bool inverted) { Animation result = _controller.drive(CurveTween(curve: curve)); if (inverted) { result = result.drive(Tween(begin: 1.0, end: 0.0)); } return result; } @override void dispose() { _controller.dispose(); _collapseCtrl._detach(this); super.dispose(); } @override void didUpdateWidget(TolyCollapse oldWidget) { super.didUpdateWidget(oldWidget); if (widget.duration != oldWidget.duration) { _controller.duration = widget.duration; } if (widget.reverseDuration != oldWidget.reverseDuration) { _controller.reverseDuration = widget.reverseDuration; } if (widget.opacityCurve != oldWidget.opacityCurve) { _secondAnimation = _initAnimation(widget.opacityCurve, false); } } /// Whether we're in the middle of cross-fading this frame. bool get _isTransitioning => _controller.status == AnimationStatus.forward || _controller.status == AnimationStatus.reverse; bool get _isOpen => _controller.value == 1.0; @override Widget build(BuildContext context) { const Key closeKey = ValueKey(false); const Key openKey = ValueKey(true); final bool transitioningForwards = _controller.status == AnimationStatus.completed || _controller.status == AnimationStatus.forward; final Key topKey; Widget topChild; final Animation topAnimation; final Key bottomKey; Widget bottomChild; final Animation bottomAnimation; if (transitioningForwards) { topKey = openKey; topChild = Align( alignment: Alignment.topLeft, child: Padding(padding: widget.contentPadding, child: widget.content), ); topAnimation = _secondAnimation; bottomKey = closeKey; bottomChild = Container( height: 0, ); bottomAnimation = _firstAnimation; } else { topKey = closeKey; topChild = Container( height: 0, ); topAnimation = _firstAnimation; bottomKey = openKey; bottomChild = Align( alignment: Alignment.topLeft, child: Padding( padding: widget.contentPadding, child: widget.content, ), ); bottomAnimation = _secondAnimation; } bottomChild = TickerMode( key: bottomKey, enabled: _isTransitioning, child: IgnorePointer( child: ExcludeSemantics( // Always exclude the semantics of the widget that's fading out. child: ExcludeFocus( excluding: widget.excludeBottomFocus, child: FadeTransition( opacity: bottomAnimation, child: bottomChild, ), ), ), ), ); topChild = TickerMode( key: topKey, enabled: true, // Top widget always has its animations enabled. child: IgnorePointer( ignoring: false, child: ExcludeSemantics( excluding: false, // Always publish semantics for the widget that's fading in. child: ExcludeFocus( excluding: false, child: FadeTransition( opacity: topAnimation, child: topChild, ), ), ), ), ); Widget title; if (widget.titleBuilder != null) { title = widget.titleBuilder!(context, _controller, _collapseCtrl); } else { title = GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { _toggleCodePanel(); }, child: Row( children: [ Expanded( child: Padding( padding: widget.titlePadding, child: widget.title, )), Spacer(), AnimatedBuilder( animation: _controller, builder: (_, child) => Transform.rotate( angle: pi * _controller.value, child: child, ), child: const Icon(Icons.expand_more)) ], )); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ title, ClipRect( child: AnimatedSize( alignment: widget.alignment, duration: widget.duration, reverseDuration: widget.reverseDuration, curve: widget.sizeCurve, child: defaultLayoutBuilder(topChild, topKey, bottomChild, bottomKey), ), ), ], ); } Widget defaultLayoutBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey) { return Stack( clipBehavior: Clip.none, children: [ Positioned( key: bottomChildKey, left: 0.0, top: 0.0, right: 0.0, child: bottomChild, ), Positioned( key: topChildKey, child: topChild, ), ], ); } @override void debugFillProperties(DiagnosticPropertiesBuilder description) { super.debugFillProperties(description); // description.add(EnumProperty('crossFadeState', widget.crossFadeState)); description.add(DiagnosticsProperty( 'controller', _controller, showName: false)); description.add(DiagnosticsProperty( 'alignment', widget.alignment, defaultValue: Alignment.topCenter)); } void _close() { widget.onClose?.call(); _controller.reverse(); } void _open() { widget.onOpen?.call(); _controller.forward(); } // 折叠代码面板 void _toggleCodePanel() { if (_isOpen) { _close(); } else { _open(); } } } class CollapseController { _TolyCollapseState? _state; bool get isOpen { assert(_state != null); return _state!._isOpen; } void toggle() { if (isOpen) { close(); } else { open(); } } void close() { assert(_state != null); _state!._close(); } void open() { assert(_state != null); _state!._open(); } void _attach(_TolyCollapseState state) { _state = state; } void _detach(_TolyCollapseState state) { if (_state == state) { _state = null; } } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/node_display/node_display.dart ================================================ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:app/app.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_module/views/mobile/widget_detail/node_display/code_display.dart'; import 'node_title.dart'; typedef NodeWidgetMapper = Widget Function(NodeModel model); class NodeDisplay extends StatelessWidget { final NodeModel node; final NodeWidgetMapper mapper; final bool isDeath; const NodeDisplay({ super.key, required this.node, required this.isDeath, required this.mapper, }); @override Widget build(BuildContext context) { Color primaryColor = Theme.of(context).primaryColor; var style = context.select((AppConfigBloc bloc) => bloc.state.codeStyle); bool isDesk = kIsWeb || Platform.isMacOS || Platform.isWindows || Platform.isLinux; EdgeInsets pd = isDesk ? const EdgeInsets.symmetric(horizontal: 24, vertical: 8) : const EdgeInsets.all(8.0); return Padding( padding: pd, child: Column( children: [ NodeTitle(text: node.name), const SizedBox(height: 10), CodeDisplay(display: mapper(node), code: node.code, style: style), if (!isDeath) _buildNodeInfo(primaryColor), ], ), ); } Widget _buildNodeInfo(Color primaryColor) => Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Panel( color: primaryColor.withOpacity(0.04), child: Text( node.subtitle, style: const TextStyle(fontSize: 12), )), ); } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/node_display/node_title.dart ================================================ import 'package:flutter/material.dart'; import 'package:toly_ui/toly_ui.dart'; class NodeTitle extends StatelessWidget { final String text; const NodeTitle({super.key, required this.text}); @override Widget build(BuildContext context) { return Row( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Circle( color: Theme.of(context).primaryColor, radius: 5, ), ), Expanded( child: Text( text, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), ), ], ); } } class LinkedButton extends StatelessWidget { const LinkedButton({super.key}); @override Widget build(BuildContext context) { return const Placeholder(); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/widget_detail_bar.dart ================================================ import 'dart:math'; import 'package:app/app.dart'; import 'package:components/components.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:utils/utils.dart'; import 'package:widget_module/blocs/action/widget_action.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_ui/widget_ui.dart'; class SliverWidgetDetailBar extends StatelessWidget { final WidgetModel model; const SliverWidgetDetailBar({Key? key, required this.model}) : super(key: key); final Color backgroundColor = const Color(0xffFAFAFA); static const Color textColor = Color(0xff262626); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; Color? appBarColor = Theme.of(context).appBarTheme.backgroundColor; Color? appBarTextColor = Theme.of(context).appBarTheme.titleTextStyle?.color; return SliverAppBar( pinned: true, backgroundColor: isDark? appBarColor:backgroundColor, titleTextStyle: TextStyle(color:isDark?appBarTextColor: textColor), iconTheme: isDark? null:const IconThemeData(color: textColor), centerTitle: false, expandedHeight: 120.0, scrolledUnderElevation: 0.5, flexibleSpace: DiyFlexibleSpaceBar( centerTitle: false, expandedTitleScale: 2, titleIconBuilder: (t) => WindmillWidget( rotate: t * 2 * pi * 2, radius: 15, ), fixedSubtitle: Text( model.name, style: TextStyle(color: isDark?appBarTextColor:Color(0xff696969), fontSize: 12), ), title: Padding( padding: const EdgeInsets.only(bottom: 3), child: Text( model.nameCN, style: TextStyle( fontSize: 16,color:isDark?appBarTextColor: textColor), ), ), //伸展处布局 titlePadding: const EdgeInsets.only(left: 20, bottom: 10), //标题边距 collapseMode: CollapseMode.parallax, ), elevation: 0, actions: [ _buildToHome(context), FeedbackWidget( onPressed: () => context.toggleLike(model.id), child: BlocConsumer>( listener: _listenLikeStateChange, builder: _buildByLikeState, ), ) ], ); } // 监听 LikeWidgetBloc 伺机弹出 toast void _listenLikeStateChange(BuildContext context, List state) { bool collected = state.contains(model); String msg = collected ? "收藏【${model.name}】组件成功!" : "已取消【${model.name}】组件收藏!"; Toast.toast( context, msg, duration: Duration(milliseconds: collected ? 1500 : 600), action: collected ? SnackBarAction( textColor: Colors.white, label: '收藏夹管理', onPressed: () => Scaffold.of(context).openEndDrawer()) : null, ); } // 根据 [LikeWidgetState ] 构建图标 Widget _buildByLikeState(BuildContext context, List state) { bool liked = state.contains(model); return Padding( padding: const EdgeInsets.only(right: 20.0), child: Icon( liked ? TolyIcon.icon_star_ok : TolyIcon.icon_star_add, size: 25, ), ); } Widget _buildToHome(BuildContext context) => GestureDetector( onLongPress: () => Scaffold.of(context).openEndDrawer(), child: const Padding( padding: EdgeInsets.all(15.0), child: Icon(Icons.home), ), onTap: () => Navigator.of(context).pop()); } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/widget_detail_page.dart ================================================ import 'dart:io'; import 'package:app/app.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:l10n/l10n.dart'; import 'package:unit_widgets_display/unit_widgets_display.dart'; import 'package:widget_repository/widget_repository.dart'; import '../../desk_ui/widget_detail/link_widget_buttons.dart'; import 'node_display/node_display.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'category_end_drawer.dart'; import 'widget_detail_bar.dart'; import 'widget_detail_panel.dart'; import 'widget_fields_sliver.dart'; import 'package:tolyui/tolyui.dart'; // 用于组件详情不需要在一开始就加载 // WidgetDetailBloc 可以在稍后提供 class WidgetDetailPageScope extends StatelessWidget { final WidgetModel model; const WidgetDetailPageScope({super.key, required this.model}); @override Widget build(BuildContext context) { WidgetRepository widgetRepository = context.read().repository; NodeRepository nodeRepository = kIsWeb ? MemoryNodeRepository() : const NodeDbRepository(); return BlocProvider( create: (_) => WidgetDetailBloc( widgetRepo: widgetRepository, nodeRepo: nodeRepository, )..push(model), child: WidgetDetailPage(model: model), ); } } class WidgetDetailPage extends StatelessWidget { final WidgetModel model; const WidgetDetailPage({super.key, required this.model}); @override Widget build(BuildContext context) { WidgetDetailBloc bloc = context.watch(); bool isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: isDark ? Colors.black : Colors.white, endDrawer: CategoryEndDrawer(widget: bloc.currentWidget), body: Builder(builder: (ctx) => _buildContent(ctx, bloc)), ); } Widget linkText(BuildContext context) => Row( children: [ const Padding( padding: EdgeInsets.only(left: 15, right: 5), child: Icon(Icons.link, color: Colors.blue), ), Text(context.l10n.relatedComponents, style: UnitTextStyle.labelBold), ], ); Widget _buildContent(BuildContext context, WidgetDetailBloc bloc) { DetailState state = bloc.state; return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) => _onPop(context, didPop, result), child: CustomScrollView( slivers: [ SliverWidgetDetailBar(model: bloc.currentWidget), SliverToBoxAdapter( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ WidgetDetailPanel(model: bloc.currentWidget), linkText(context), if (state is DetailWithData) LinkWidgetButtons(links: state.links, onSelect: bloc.push), ], ), ), if (state is DetailWithData) state.nodes.isNotEmpty ? SliverNodeList( nodes: state.nodes, model: state.widgetModel, ) : SliverWidgetFieldsList(widgetId: model.id), ], )); } void _onPop(BuildContext context, bool didPop, dynamic result) { if (didPop) { return; } WidgetDetailBloc detailBloc = context.read(); // 检查抽屉是否打开 if (Scaffold.of(context).isEndDrawerOpen) { if (context.mounted) { Navigator.of(context).pop(); } return; } // 调用原来的 pop 逻辑 detailBloc.pop().then((bool canPop) { if (canPop && context.mounted) { Navigator.of(context).pop(); } }); } } class SliverNodeList extends StatelessWidget { final List nodes; final WidgetModel model; const SliverNodeList({super.key, required this.nodes, required this.model}); @override Widget build(BuildContext context) { return SliverList( delegate: SliverChildBuilderDelegate( (_, i) => NodeDisplay( mapper: _nodeMapper, node: nodes[i], isDeath: model.death, ), childCount: nodes.length, )); } Widget _nodeMapper(NodeModel node) { NodeType type = node.type(model.name, node.priority); Widget display = mapNodeDisplay(model.id, node.priority); return switch (type) { NodeType.display => display, NodeType.newPage => newPageDisplay(display), NodeType.description => display, NodeType.deprecated => display, }; } } Widget newPageDisplay(Widget page) { return Builder( builder: (ctx) => ElevatedButton( onPressed: () { Navigator.of(ctx).push(MaterialPageRoute(builder: (_) => page)); }, child: Wrap( spacing: 6, crossAxisAlignment: WrapCrossAlignment.center, children: [ Icon( Icons.open_in_new, size: 16, color: Colors.white, ), Text('新界面打开'), ], ), style: FillButtonPalette( foregroundPalette: Palette.all(Colors.white), borderRadius: BorderRadius.circular(6), backgroundPalette: const Palette( normal: Color(0xff1890ff), hover: Color(0xff40a9ff), pressed: Color(0xff096dd9), ), ).style, ), ); } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/widget_detail_panel.dart ================================================ import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_star/flutter_star.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_ui/widget_ui.dart'; class WidgetDetailPanel extends StatelessWidget { final WidgetModel model; const WidgetDetailPanel({Key? key, required this.model}) : super(key: key); @override Widget build(BuildContext context) { Color color = Theme.of(context).primaryColor; return Row( children: [ _buildLeft(model, context), Hero( tag: "hero_widget_image_${model.id}", child: WidgetDetailLogo( model: model, background: color, widgetName: model.name, ), ) ], ); } Widget _buildLeft(WidgetModel model, BuildContext context) => Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: Panel( constraints: BoxConstraints(minHeight: 110), alignment: Alignment.topLeft, color: Theme.of(context).scaffoldBackgroundColor, child: Text(model.info)), ), ); } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/widget_fields_sliver.dart ================================================ import 'package:flutter/material.dart'; import 'package:widget_repository/widget_repository.dart'; class SliverWidgetFieldsList extends StatefulWidget { final int widgetId; const SliverWidgetFieldsList({ super.key, required this.widgetId, }); @override State createState() => _SliverWidgetFieldsListState(); } class _SliverWidgetFieldsListState extends State { List? _fields; bool _isLoading = true; @override void initState() { super.initState(); _loadFields(); } Future _loadFields() async { try { final repository = const WidgetDbRepository(); final fields = await repository.loadWidgetFields(widget.widgetId); setState(() { _isLoading = false; _fields = fields; }); } catch (e) { setState(() { _isLoading = false; _fields = []; }); } } @override Widget build(BuildContext context) { if (_isLoading) { return const SliverToBoxAdapter( child: Center( child: Padding( padding: EdgeInsets.all(32), child: CircularProgressIndicator(), ), ), ); } if (_fields!.isEmpty) { return const SliverToBoxAdapter( child: Center( child: Padding( padding: EdgeInsets.all(32), child: Text('暂无属性信息'), ), ), ); } return SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, index) => _buildFieldItem(_fields![index]), childCount: _fields!.length, ), ), ); } Widget _buildFieldItem(WidgetFieldModel field) { return Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 4, height: 20, decoration: BoxDecoration( color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 12), Expanded( child: Text( field.fieldName, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, color: Colors.black87, ), ), ), // if (field.isRequired) _buildRequiredBadge(), ], ), const SizedBox(height: 2), Padding( padding: const EdgeInsets.only(left: 16), child: Text( field.fieldType, style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 14, fontWeight: FontWeight.w600, ), ), ), if (field.fieldDescZh != null) const SizedBox(height: 8), Text( field.fieldDescZh!, style: const TextStyle( fontSize: 13, color: Colors.grey, height: 1.4, ), ), ], ), ); } Widget _buildRequiredBadge() { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(12), ), child: const Text( '必需', style: TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w500, ), ), ); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_detail/widget_node_panel.dart ================================================ import 'package:app/app.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:share_plus/share_plus.dart'; import 'package:toggle_rotate/toggle_rotate.dart'; /// create by 张风捷特烈 on 2020-04-13 /// contact me by email 1981462002@qq.com /// 说明: 一个Widget的知识点对应的界面 class WidgetNodePanel extends StatefulWidget { final String text; final String subText; final String code; final Widget? show; final HighlighterStyle? codeStyle; final String? codeFamily; final bool death; const WidgetNodePanel( {Key? key, this.text='', this.subText='', this.code='', this.death=false, this.show, required this.codeStyle, this.codeFamily}) : super(key: key); @override _WidgetNodePanelState createState() => _WidgetNodePanelState(); } class _WidgetNodePanelState extends State { CrossFadeState _crossFadeState = CrossFadeState.showFirst; bool get isFirst => _crossFadeState == CrossFadeState.showFirst; Color get themeColor => Theme.of(context).primaryColor; @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ buildNodeTitle(), const SizedBox( height: 20, ), _buildCode(context), Padding( padding: const EdgeInsets.only(top: 10, bottom: 20), child: widget.show, ), if(!widget.death) _buildNodeInfo(), const Divider(), ], ), ); } Widget buildNodeTitle() => Row( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Circle( color: themeColor, radius: 5, ), ), Expanded( child: Text( widget.text, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), ), _buildShareButton(), _buildCodeButton() ], ); Widget _buildNodeInfo() => SizedBox( width: double.infinity, child: Panel( color: Theme.of(context).scaffoldBackgroundColor, child: Text( widget.subText, style: const TextStyle(fontSize: 12), )), ); Widget _buildCodeButton() => Padding( padding: const EdgeInsets.only(right: 10.0), child: ToggleRotate( durationMs: 300, child: Icon( TolyIcon.icon_code, color: themeColor, ), onTap: _toggleCodePanel, ), ); Widget _buildShareButton() => FeedbackWidget( mode: FeedMode.fade, a: 0.4, onPressed: _doShare, child: Padding( padding: const EdgeInsets.only( right: 10, ), child: Icon( TolyIcon.icon_share, size: 20, color: themeColor, ), ), ); Widget _buildCode(BuildContext context) => AnimatedCrossFade( firstCurve: Curves.easeInCirc, secondCurve: Curves.easeInToLinear, firstChild: const SizedBox(), secondChild: SizedBox( width: MediaQuery.of(context).size.width, child: CodeWidget( fontFamily: widget.codeFamily, code: isFirst?'':widget.code, style: widget.codeStyle ?? HighlighterStyle.fromColors(HighlighterStyle.lightColor), ), ), duration: const Duration(milliseconds: 200), crossFadeState: _crossFadeState, ); //执行分享 _doShare() { Share.share(widget.code); } // 折叠代码面板 _toggleCodePanel() { setState(() { _crossFadeState = !isFirst ? CrossFadeState.showFirst : CrossFadeState.showSecond; }); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_page/home_drawer.dart ================================================ import 'package:app/app.dart'; import 'package:components/components.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'unit_drawer_header.dart'; /// create by 张风捷特烈 on 2020-03-26 /// contact me by email 1981462002@qq.com /// 说明: class HomeDrawer extends StatelessWidget { const HomeDrawer({super.key}); @override Widget build(BuildContext context) { return Drawer( elevation: 3, child: _buildChild(context), ); } Widget _buildChild(BuildContext context) { final Color color = Theme.of(context).primaryColor; return Container( color: color.withAlpha(33), child: ListView( padding: EdgeInsets.zero, children: [ UnitDrawerHeader(color: color), _buildItem(context, TolyIcon.icon_them, '应用设置', '/settings'), _buildItem( context, TolyIcon.icon_layout, '数据管理', '/data_manage'), const Divider(height: 1), _buildFlutterUnit(context), _buildItem(context, TolyIcon.icon_code, 'Dart 手册', ''), const Divider(height: 1), _buildItem(context, Icons.info, '关于应用', '/about_app'), _buildItem(context, TolyIcon.icon_kafei, '联系本王', '/about_me'), ], ), ); } Widget _buildFlutterUnit(BuildContext context) => NoBorderExpansionTile( backgroundColor: Colors.white70, leading: Icon( Icons.extension, color: Theme.of(context).primaryColor, ), title: const Text('Flutter 集录'), children: [ _buildItem(context, TolyIcon.icon_tag, '属性集录', ''), _buildItem(context, Icons.palette, '绘画集录', ''), _buildItem(context, Icons.widgets, '布局集录', ''), _buildItem(context, TolyIcon.icon_bug, '要点集录', ''), ], ); Widget _buildItem( BuildContext context, IconData icon, String title, String linkTo, {VoidCallback? onTap}) => ListTile( leading: Icon( icon, color: Theme.of(context).primaryColor, ), title: Text(title), trailing: Icon(Icons.chevron_right, color: Theme.of(context).primaryColor), onTap: () { if (linkTo.isNotEmpty) { context.push(linkTo); if (onTap != null) onTap(); } }, ); } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_page/phone_widget_content.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:widget_module/widget_module.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_ui/widget_ui.dart'; class PhoneWidgetContent extends StatelessWidget { final List items; const PhoneWidgetContent({Key? key, required this.items}) : super(key: key); @override Widget build(BuildContext context) { return SliverList( delegate: SliverChildBuilderDelegate( (_, int index) => Padding( padding: const EdgeInsets.only(left: 8.0, right: 8, top: 8), child: WidgetItem( model: items[index], onWidget: context.handleWidgetAction, ), ), childCount: items.length, ), ); } void _toDetail(BuildContext context, WidgetModel model) { context.push('/widget/detail/${model.name}', extra: model); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_page/slider.dart ================================================ // import 'package:flutter/gestures.dart'; // import 'package:flutter/material.dart'; // import 'package:app/app.dart'; // // import 'widget_page.dart'; // // class Carousel extends StatefulWidget { // final List data; // // const Carousel({Key? key, required this.data}) : super(key: key); // // @override // _CarouselState createState() => _CarouselState(); // } // // class _CarouselState extends State { // final ValueNotifier factor = ValueNotifier(0); // // late PageController _ctrl; // // final int _firstOffset = 1000; //初始偏移 // int _position = 0; //页面位置 // // @override // void initState() { // super.initState(); // _position = _position + _firstOffset; // // double value = ((_position - _firstOffset + 1) % 5) / 5; // factor.value = value == 0 ? 1 : value; // _ctrl = PageController( // viewportFraction: 0.9, // initialPage: _position, // )..addListener(() { // if (_ctrl.page != null) { // double value = (_ctrl.page! - _firstOffset + 1) % 5 / 5; // factor.value = value == 0 ? 1 : value; // } // }); // } // // @override // void dispose() { // _ctrl.dispose(); // factor.dispose(); // super.dispose(); // } // // Color get color => Colors.blue; // // Color get nextColor => Colors.orangeAccent; // // bool get isDark => Theme.of(context).brightness == Brightness.dark; // // BoxDecoration get boxDecoration => BoxDecoration( // color: isDark ? Colors.white.withAlpha(33) : Colors.white, // borderRadius: const BorderRadius.only( // topLeft: Radius.circular(40), topRight: Radius.circular(40)), // ); // // @override // Widget build(BuildContext context) { // List data = widget.data; // Widget child = PageView.builder( // controller: _ctrl, // itemCount: 7, // itemBuilder: (_, index) { // return AnimatedBuilder( // child: _buildByIndex(context, index, data), // animation: _ctrl, // builder: (context, child) => _buildAnimItemByIndex( // context, // child, // index, // ), // ); // }, // onPageChanged: (index) { // _position = index; // setState(() {}); // }, // ); // // int realIndex = _fixPosition(_position, _firstOffset, data.length); // // child = Stack( // alignment: Alignment.bottomCenter, // children: [ // child, // Padding( // padding: const EdgeInsets.only(bottom: 16.0), // child: Wrap( // spacing: 6, // children: widget.data.asMap().keys.map((int index) { // return GestureDetector( // onTap: () { // int deta = index - realIndex; // _position += deta; // print('$_position,$realIndex'); // // _position = index; // _ctrl.animateToPage(_position, // duration: Duration(milliseconds: 500), // curve: Curves.easeIn); // }, // child: Container( // width: 8, // height: 8, // decoration: BoxDecoration( // color: realIndex == index // ? Colors.white // : Colors.black.withValues(alpha: 0.4), // shape: BoxShape.circle, // boxShadow: [ // BoxShadow( // color: Colors.white.withValues(alpha: 0.3), // spreadRadius: 1, // blurRadius: 10, // blurStyle: BlurStyle.outer) // ]), // ), // ); // }).toList(), // ), // ) // ], // ); // // if (!kIsDesk) { // return child; // } // // return MouseRegion( // onEnter: _onEnter, // onExit: _onExit, // child: Stack( // alignment: Alignment.center, // children: [ // Padding( // padding: const EdgeInsets.symmetric(horizontal: 48.0), // child: child), // Positioned( // right: 0, // child: IconButton( // onPressed: () { // _position += 1; // _ctrl.animateToPage(_position, // duration: Duration(milliseconds: 500), // curve: Curves.easeIn); // }, // icon: Icon(Icons.navigate_next_outlined))), // Positioned( // left: 0, // child: IconButton( // onPressed: () { // _position -= 1; // _ctrl.animateToPage(_position, // duration: Duration(milliseconds: 500), // curve: Curves.easeIn); // }, // icon: Icon(Icons.navigate_before))), // ], // ), // ); // } // // Widget? _buildByIndex( // BuildContext context, int index, List data) { // int realIndex = _fixPosition(index, _firstOffset, data.length); // return WidgetDisplay( // widget: data[realIndex], // ); // } // // Widget _buildAnimItemByIndex(BuildContext context, Widget? child, int index) { // double value; // if (_ctrl.position.haveDimensions && _ctrl.page != null) { // value = _ctrl.page! - index; // } else { // value = (_position - index).toDouble(); // } // value = (1 - ((value.abs()) * .3)).clamp(0, 1).toDouble(); // value = Curves.easeOut.transform(value); // // return Transform( // transform: Matrix4.diagonal3Values(1.0, value, 1.0), // alignment: Alignment.center, // child: Padding( // padding: const EdgeInsets.all(4.0), // child: child, // ), // ); // } // // int _fixPosition(int realPos, int initPos, int length) { // final int offset = realPos - initPos; // int result = offset % length; // return result < 0 ? length + result : result; // } // // bool _hover = false; // // void _onEnter(PointerEnterEvent event) { // setState(() { // _hover = true; // }); // } // // void _onExit(PointerExitEvent event) { // setState(() { // _hover = false; // }); // } // } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_page/standard_home_page.dart ================================================ import 'package:l10n/ext.dart'; import 'package:widget_module/blocs/action/widget_action.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:toly_ui/toly_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'home_drawer.dart'; import 'standard_home_search.dart'; import 'widget_page.dart'; class StandardHomePage extends StatefulWidget { final Widget? heard; const StandardHomePage({super.key, this.heard}); @override State createState() => _StandardHomePageState(); } class _StandardHomePageState extends State with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { List get _tabs { final provider = WidgetStatisticsProvider(); final stats = provider.statistics; if (stats == null) { return [ context.l10n.stateless, context.l10n.stateful, context.l10n.single, context.l10n.multi, context.l10n.sliver, context.l10n.proxy, context.l10n.other, ]; } return [ context.l10n.stateless, context.l10n.stateful, context.l10n.single, context.l10n.multi, context.l10n.sliver, context.l10n.proxy, context.l10n.other, ]; } ValueNotifier indexValue = ValueNotifier(0); List _buildTabWidgets() { final provider = WidgetStatisticsProvider(); final stats = provider.statistics; final counts = [ stats?.familyCount[WidgetFamily.stateless] ?? 0, stats?.familyCount[WidgetFamily.stateful] ?? 0, stats?.familyCount[WidgetFamily.singleChildRender] ?? 0, stats?.familyCount[WidgetFamily.multiChildRender] ?? 0, stats?.familyCount[WidgetFamily.sliver] ?? 0, stats?.familyCount[WidgetFamily.proxy] ?? 0, stats?.familyCount[WidgetFamily.other] ?? 0, ]; return List.generate( _tabs.length, (index) => ValueListenableBuilder( valueListenable: indexValue, builder: (context, value, __) { return Stack( clipBehavior: Clip.none, children: [ Text(_tabs[index]), if (value == index) Positioned( right: -10, top: -6, child: Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 3), decoration: BoxDecoration( color: Theme.of(context) .primaryColor .withValues(alpha: 0.6), borderRadius: BorderRadius.circular(6), ), child: Text('${counts[index]}', style: const TextStyle( fontFamily: '黑体', height: 1, fontSize: 9, color: Colors.white)), ), ), ], ); })); } late TabController tabController; @override void initState() { super.initState(); tabController = TabController(length: 7, vsync: this); tabController.addListener(_onChange); } int maxCount = 60; @override void dispose() { tabController.removeListener(_onChange); tabController.dispose(); super.dispose(); } void _switchTab(int index) { WidgetFamily widgetFamily = WidgetFamily.values[index]; WidgetsBloc bloc = context.read(); if (bloc.state.filter.family == widgetFamily) return; PrimaryScrollController.of(context).jumpTo(0); context.switchWidgetFamily(widgetFamily); } @override Widget build(BuildContext context) { super.build(context); final AppBarThemeData appBarTheme = AppBarTheme.of(context); bool isDark = Theme.of(context).brightness == Brightness.dark; double bottom = MediaQuery.of(context).padding.bottom; return Scaffold( extendBody: true, drawer: const HomeDrawer(), body: Column( children: [ AnnotatedRegion( value: appBarTheme.systemOverlayStyle!, child: Container( color: isDark ? Colors.black : Colors.white, height: MediaQuery.of(context).padding.top, ), ), Expanded( child: NestedScrollView( floatHeaderSlivers: true, headerSliverBuilder: _buildHeader, body: const WidgetPage()), ), SizedBox(height: bottom) ], ), ); } List _buildHeader(BuildContext context, bool innerBoxIsScrolled) { Color themeColor = Theme.of(context).primaryColor; bool isDark = Theme.of(context).brightness == Brightness.dark; return [ // const SliverSnapHeader(child: StandardHomeSearch()), const SliverFloatingHeader( snapMode: FloatingHeaderSnapMode.scroll, child: StandardHomeSearch(), ), if (widget.heard != null) SliverToBoxAdapter( child: Container( height: 168, color: isDark ? Colors.black : Colors.white, child: widget.heard, ), ), SliverPinnedHeader( color: isDark ? Colors.black : Colors.white, child: TabBar( onTap: _switchTab, tabAlignment: TabAlignment.start, indicatorSize: TabBarIndicatorSize.label, isScrollable: true, indicator: RoundRectTabIndicator( borderSide: BorderSide(color: themeColor, width: 3), ), labelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), controller: tabController, labelColor: themeColor, indicatorWeight: 3, unselectedLabelColor: Colors.grey, indicatorColor: themeColor, tabs: _buildTabWidgets() .map((Widget widget) => Tab(child: widget)) .toList(), )), // handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), // ), ]; } @override bool get wantKeepAlive => true; void _onChange() { indexValue.value = tabController.index; } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_page/standard_home_search.dart ================================================ import 'package:app/app.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:l10n/ext.dart'; import 'package:toly_ui/toly_ui.dart'; import '../search_page/standard_search_page.dart'; class StandardHomeSearch extends StatelessWidget implements PreferredSizeWidget { const StandardHomeSearch({Key? key}) : super(key: key); @override Size get preferredSize => const Size.fromHeight(35 + 8 * 2); @override Widget build(BuildContext context) { bool isDark = Theme.of(context).brightness == Brightness.dark; return ColoredBox( color: isDark ? Colors.black : Colors.white, child: Row( children: [ _buildHead(context), Expanded( child: GestureDetector( onTap: () => _toSearchPage(context), child: Container( margin: const EdgeInsets.symmetric(vertical: 8), height: 35, child: TextField( autofocus: false, enabled: false, cursorColor: Colors.blue, maxLines: 1, decoration: _topSearchInputDecoration(isDark, context), )), ), ), _buildCollectIcon(context), ], ), ); } InputDecoration _topSearchInputDecoration(bool isDark, BuildContext context) { String hintText = context.l10n.searchWidget; return InputDecoration( filled: true, fillColor: isDark ? const Color(0xff292929) : const Color(0xffF3F6F9), prefixIcon: const Icon( Icons.search, color: Colors.grey, size: 20, ), prefixIconConstraints: const BoxConstraints(maxHeight: 24, minWidth: 36), isCollapsed: true, contentPadding: const EdgeInsets.only(top: 4, bottom: 4, right: 8), border: const UnderlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.all(Radius.circular(6)), ), hintText: hintText, hintStyle: const TextStyle(fontSize: 14)); } Widget _buildHead(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: FeedbackWidget( onPressed: () => _openDrawer(context), child: const CircleAvatar( radius: 16, backgroundImage: AssetImage('assets/images/icon_head.webp'), ), ), ); } Widget _buildCollectIcon(BuildContext context) { return IconButton( onPressed: () => context.push(AppRoute.collection.url), icon: const Icon( TolyIcon.icon_collect, size: 22, ), ); } void _toSearchPage(BuildContext context) { Navigator.of(context) .push(FadePageRoute(child: const StandardSearchPageProvider())); } void _openDrawer(BuildContext context) { // Scaffold.of(context).openDrawer(); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_page/unit_drawer_header.dart ================================================ import 'package:flutter/material.dart'; /// create by 张风捷特烈 on 2020-04-22 /// contact me by email 1981462002@qq.com /// 说明: class UnitDrawerHeader extends StatelessWidget { final Color color; const UnitDrawerHeader({super.key, required this.color}); @override Widget build(BuildContext context) { return DrawerHeader( padding: const EdgeInsets.only(top: 10, left: 15), decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/wy_300x200_filter.webp'), fit: BoxFit.cover), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Wrap( spacing: 10, crossAxisAlignment: WrapCrossAlignment.center, children: [ FlutterLogo( size: 35, ), Text( 'Flutter Unit', style: TextStyle(fontSize: 24, color: Colors.white, shadows: [ Shadow( color: Colors.black, offset: Offset(1, 1), blurRadius: 3) ]), ), ], ), const SizedBox(height: 15), Text( 'The Unity Of Flutter, The Unity Of Coder.', style: TextStyle(fontSize: 15, color: Colors.white, shadows: [ Shadow(color: color, offset: const Offset(.5, .5), blurRadius: 1) ]), ), const SizedBox(height: 5), Text( 'Flutter的联合,编程者的联合。', style: TextStyle(fontSize: 15, color: Colors.white, shadows: [ Shadow(color: color, offset: const Offset(.5, .5), blurRadius: 1) ]), ), const SizedBox(height: 10), const Row( children: [ Spacer(flex: 5), Text( '—— 张风捷特烈', style: TextStyle(fontSize: 15, color: Colors.white, shadows: [ Shadow( color: Colors.orangeAccent, offset: Offset(.5, .5), blurRadius: 1) ]), ), Spacer(flex: 1), ], ), ], ), ); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_page/widget_list_panel.dart ================================================ import 'package:components/project_ui/project_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'phone_widget_content.dart'; class WidgetListPanel extends StatelessWidget { const WidgetListPanel({Key? key}) : super(key: key); @override Widget build(BuildContext context) { WidgetsState state = context.watch().state; if (state is WidgetsLoaded) { return PhoneWidgetContent(items: state.widgets); } if (state is WidgetsLoading) { return const SliverFillRemaining( child: LoadingShower(), ); } if (state is WidgetsLoadFailed) { return SliverFillRemaining( child: ErrorShower( error: "数据加载异常:\n${state.error}", )); } return const SliverFillRemaining( child: EmptyShower( message: "没数据,哥也没办法\n(≡ _ ≡)/~┴┴", ), ); } } ================================================ FILE: modules/widget_system/widget_module/lib/views/mobile/widget_page/widget_page.dart ================================================ import 'package:components/components.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_module/blocs/blocs.dart'; import 'package:note/note.dart'; import 'widget_list_panel.dart'; import 'package:tolyui_refresh/tolyui_refresh.dart'; class WidgetPage extends StatefulWidget { const WidgetPage({Key? key}) : super(key: key); @override State createState() => _WidgetPageState(); } class _WidgetPageState extends State { final RefreshController _refreshController = RefreshController(initialRefresh: false); @override void dispose() { _refreshController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocListener( listener: _listenStateChange, child: RefreshConfigWrapper( child: TolyRefresh( physics: BouncingScrollPhysics(), controller: _refreshController, onRefresh: _onRefresh, enablePullUp: true, onLoading: _onLoadMore, child: CustomScrollView( // key: PageStorageKey(name), slivers: [ const WidgetListPanel(), ], ), ), ), ); } void _onRefresh() async { context.read().add(EventRefresh()); await context.read().refreshFromNet(); } void _onLoadMore() { context.read().add(EventLoadMore()); } void _listenStateChange(BuildContext context, WidgetsState state) async { if (state is WidgetsLoaded) { if (state.operate == LoadOperate.refresh) { _refreshController.refreshCompleted(); } if (state.operate == LoadOperate.more) { if (state.full) { _refreshController.loadNoData(); await Future.delayed(const Duration(milliseconds: 2000)); _refreshController.resetNoData(); } else { _refreshController.loadComplete(); } } } } } ================================================ FILE: modules/widget_system/widget_module/lib/views/widgets_bloc_provider.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fx_platform_adapter/fx_platform_adapter.dart'; import 'package:widget_ui/widget_ui.dart'; import '../blocs/blocs.dart'; import 'package:widget_repository/widget_repository.dart'; class WidgetsBlocProvider extends StatefulWidget { final Widget child; const WidgetsBlocProvider({super.key, required this.child}); @override State createState() => _WidgetsBlocProviderState(); } class _WidgetsBlocProviderState extends State { late WidgetRepository repository; final CategoryBloc categoryBloc = CategoryBloc(repository: CategoryDbRepository()); @override void initState() { super.initState(); if (kAppEnv.isWeb) { repository = MemoryWidgetRepository(); } else { repository = const WidgetDbRepository(); } } @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider( create: (_) => WidgetsBloc(repository: repository)), BlocProvider(create: (_) => categoryBloc), BlocProvider( create: (_) => LikeWidgetBloc(repository: repository)), BlocProvider( create: (_) => CategoryWidgetBloc(categoryBloc: categoryBloc)), ], child: widget.child, ); } @override void dispose() { categoryBloc.close(); super.dispose(); } } ================================================ FILE: modules/widget_system/widget_module/lib/widget_module.dart ================================================ library widget_module; export 'views/desk_ui/desk_ui.dart'; export 'views/mobile/mobile_ui.dart'; export 'views/widgets_bloc_provider.dart'; export 'event/widget_event.dart'; export 'event/widget_statistics_event.dart'; export 'blocs/action/widget_action.dart'; export 'package:widget_ui/widget_ui.dart' show LikeWidgetBloc; export 'package:widget_repository/widget_repository.dart' show WidgetFilter, WidgetFamily, CategoryModel, WidgetModel, CategoryRepository, CategoryTo, LikeDao,WidgetDao,NodeDao,CategoryDao; ================================================ FILE: modules/widget_system/widget_module/pubspec.yaml ================================================ name: widget_module description: A new Flutter package project. version: 0.0.1 homepage: publish_to: none environment: sdk: ">=3.5.0 <4.0.0" flutter: ">=1.17.0" resolution: workspace dependencies: flutter: sdk: flutter unit_widgets_display: ^0.0.1+2 widget_ui: path: ../widget_ui widget_repository: path: ../widget_repository #dependency_overrides: # intl: 0.19.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: modules/widget_system/widget_module/test/widget_module_test.dart ================================================ // import 'package:flutter_test/flutter_test.dart'; // // import 'package:widget_module/widget_module.dart'; // // void main() { // test('adds one to input values', () { // final calculator = Calculator(); // expect(calculator.addOne(2), 3); // expect(calculator.addOne(-7), -6); // expect(calculator.addOne(0), 1); // }); // } ================================================ FILE: modules/widget_system/widget_repository/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ build/ ================================================ FILE: modules/widget_system/widget_repository/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "2663184aa79047d0a33a14a3b607954f8fdd8730" channel: "stable" project_type: package ================================================ FILE: modules/widget_system/widget_repository/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/widget_system/widget_repository/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/widget_system/widget_repository/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/widget_system/widget_repository/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/widget_system/widget_repository/doc/tables_overview.md ================================================ # 数据表结构总览 ## 核心表 (6张) ### 1. widget - Widget基本信息 ```sql CREATE TABLE widget( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(64) NOT NULL UNIQUE, path TEXT NOT NULL, is_abstract INTEGER NOT NULL DEFAULT 0, is_private INTEGER NOT NULL DEFAULT 0, deprecated INTEGER DEFAULT 0, family INTEGER NOT NULL, lever FLOAT(2) NOT NULL, linkWidget TEXT DEFAULT '' ); ``` ### 2. widget_inheritance - Widget继承关系 ```sql CREATE TABLE widget_inheritance ( id INTEGER PRIMARY KEY AUTOINCREMENT, widget_id INTEGER NOT NULL, parent_name TEXT NOT NULL, inheritance_order INTEGER NOT NULL ); ``` ### 3. widget_fields - Widget字段信息 ```sql CREATE TABLE widget_fields ( id INTEGER PRIMARY KEY AUTOINCREMENT, widget_id INTEGER NOT NULL, field_name TEXT NOT NULL, field_type TEXT NOT NULL, field_desc TEXT, field_desc_zh TEXT, field_order INTEGER NOT NULL, is_required INTEGER NOT NULL DEFAULT 0 ); ``` ### 4. widget_desc - Widget描述信息 ```sql CREATE TABLE widget_desc( id INTEGER PRIMARY KEY AUTOINCREMENT, widget_id INTEGER NOT NULL, name VARCHAR(128) NOT NULL, info TEXT NOT NULL, locale VARCHAR(16) DEFAULT 'zh-cn' ); ``` ### 5. node - 示例代码节点 ```sql CREATE TABLE node( id INTEGER PRIMARY KEY AUTOINCREMENT, widgetId INTEGER NOT NULL, priority INTEGER DEFAULT 0, code TEXT NOT NULL ); ``` ### 6. node_desc - 节点描述信息 ```sql CREATE TABLE node_desc( id INTEGER PRIMARY KEY AUTOINCREMENT, node_id INTEGER NOT NULL, name VARCHAR(128) NOT NULL, subtitle TEXT NOT NULL, locale VARCHAR(16) DEFAULT 'zh-cn' ); ``` ## 扩展表 (2张) ### 7. category - Widget分类 ```sql CREATE TABLE category( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(64) NOT NULL UNIQUE, type INTEGER DEFAULT 0, color VARCHAR(9) DEFAULT '#FF2196F3', info VARCHAR(256) DEFAULT '这里什么都没有...', created DATETIME NOT NULL, updated DATETIME NOT NULL, priority INTEGER DEFAULT 0, image VARCHAR(128) DEFAULT '' ); ``` ### 8. category_widget - 分类Widget关联 ```sql CREATE TABLE category_widget( id INTEGER PRIMARY KEY AUTOINCREMENT, categoryId INTEGER NOT NULL, widgetId INTEGER NOT NULL ); ``` ## 表关系图 ``` widget (1) ←→ (N) widget_inheritance widget (1) ←→ (N) widget_fields widget (1) ←→ (N) widget_desc widget (1) ←→ (N) node node (1) ←→ (N) node_desc widget (N) ←→ (N) category [through category_widget] ``` ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/dao/category_dao.dart ================================================ import 'package:fx_dao/fx_dao.dart'; import '../../../widget_repository.dart'; //""" // CREATE TABLE IF NOT EXISTS category_widget( // id INTEGER PRIMARY KEY AUTOINCREMENT, // name VARCHAR(64) NOT NULL, // color VARCHAR(9) DEFAULT '#FF2196F3', // info VARCHAR(256) DEFAULT '这里什么都没有...', // created DATETIME NOT NULL, // updated DATETIME NOT NULL, // priority INTEGER DEFAULT 0, // image VARCHAR(128) NULL image DEFAULT '' // ); //"""; class CategoryDao extends Dao { @override String get createSql => ''; @override String get name => 'category'; Future insert(CategoryPo category) async { String addSql = //插入数据 "INSERT INTO " "category(id,name,color,info,priority,image,created,updated) " "VALUES (?,?,?,?,?,?,?,?);"; return await database .transaction((tran) async => await tran.rawInsert(addSql, [ category.id, category.name, category.color, category.info, category.priority, category.image, category.created?.toIso8601String(), category.updated.toIso8601String(), ])); } Future update(CategoryPo widget) async { String updateSql = //插入数据 "UPDATE category SET name=? , color=? ,info=?, priority=?,image=?,updated=? " "WHERE id = ?"; return await database .transaction((tran) async => await tran.rawUpdate(updateSql, [ widget.name, widget.color, widget.info, widget.priority, widget.image, widget.updated.toIso8601String(), widget.id, ])); } Future addWidget( int categoryId, int widgetId, ) async { String addSql = "INSERT INTO " "category_widget(widgetId,categoryId) " "VALUES (?,?);"; return await database .transaction((tran) async => await tran.rawInsert(addSql, [ widgetId, categoryId, ])); } Future addWidgets(int categoryId, List widgetIds) async { String addSql = "INSERT INTO " "category_widget(widgetId,categoryId) VALUES "; String args = ''; for (int i = 0; i < widgetIds.length; i++) { args += "(${widgetIds[i]},$categoryId)"; if (i == widgetIds.length - 1) { args += ";"; } else { args += ","; } } addSql += args; return await database .transaction((tran) async => await tran.rawInsert(addSql)); } Future existByName(String name) async { String sql = "SELECT COUNT(name) as count FROM category " "WHERE name = ?"; List> rawData = await database.rawQuery(sql, [name]); if (rawData.isNotEmpty) { return rawData[0]['count'] > 0; } return false; } Future>> queryAll() async { List> data = await database.rawQuery( "SELECT c.id,c.name,c.info,c.color,c.image,c.created,c.updated,c.priority,COUNT(cw.categoryId) as `count`" "FROM category AS c " "LEFT JOIN category_widget AS cw " "ON c.id = cw.categoryId GROUP BY c.id " "ORDER BY priority DESC,created DESC", []); return data; } Future> categoryWidgetIds(int id) async { List> data = await database.rawQuery( "SELECT categoryId FROM `category_widget`" "WHERE widgetId = ?", [id]); return data.toList().map((e) => e["categoryId"]).toList(); } Future deleteCollect(int id) async { await database.execute( "DELETE FROM category_widget " "WHERE categoryId = ?", [id]); return await database.execute( "DELETE FROM category " "WHERE id = ?", [id]); } Future clear() async { await database.execute("DELETE FROM category_widget " "WHERE categoryId >0"); return await database.execute("DELETE FROM category " "WHERE id > 0"); } Future removeWidget(int categoryId, int widgetId) async { String deleteSql = "DELETE FROM " "category_widget WHERE categoryId = ? AND widgetId = ? "; return await database .transaction((tran) async => await tran.rawInsert(deleteSql, [ categoryId, widgetId, ])); } Future existWidgetInCollect(int categoryId, int widgetId) async { String sql = "SELECT COUNT(id) as count FROM category_widget " "WHERE categoryId = ? AND widgetId = ?"; List> rawData = await database.rawQuery(sql, [categoryId, widgetId]); if (rawData.isNotEmpty) { return rawData[0]['count'] > 0; } return false; } Future toggleCollect(int categoryId, int widgetId) async { if (await existWidgetInCollect(categoryId, widgetId)) { await removeWidget(categoryId, widgetId); } else { await addWidget(categoryId, widgetId); } } Future toggleCollectDefault(int widgetId) async { await toggleCollect(1, widgetId); } Future>> loadCollectWidgets(int categoryId, [String locale = 'zh-cn']) async { String querySql = "SELECT w.*, wd.name as localeName, wd.info as info " "FROM widget w " "LEFT JOIN widget_desc wd ON w.id = wd.widget_id AND wd.locale = ? " "WHERE w.id IN (SELECT widgetId FROM category_widget WHERE categoryId = ?) " "ORDER BY w.lever DESC"; return await database.rawQuery(querySql, [locale, categoryId]); } Future> loadCollectWidgetIds(int categoryId) async { String querySql = "SELECT id FROM widget " "WHERE id IN (SELECT widgetId FROM category_widget WHERE categoryId = ?) " "ORDER BY lever DESC"; var data = await database.rawQuery(querySql, [categoryId]); return data.map((e) => e["id"] as int).toList(); } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/dao/like_dao.dart ================================================ import 'package:fx_dao/fx_dao.dart'; class LikeDao extends Dao { @override String get createSql => ''; @override String get name => 'like_widget'; Future> likeWidgetIds() async { String sql = """ SELECT cw.widgetId as widget_id FROM category c JOIN category_widget cw ON c.id = cw.categoryId WHERE c.type = 1; """; var result = await database.rawQuery(sql); var ids = result.map((e) => e['widget_id'] as int).toList(); return ids; } // 如果已喜欢,取消喜欢 // 如果未喜欢,设为喜欢 Future toggleCollect(int widgetId) async { bool liked = await isLiked(widgetId); if (liked) { await unlike(widgetId, check: false); } else { await like(widgetId, check: false); } } Future like(int widgetId, {bool check = true}) async { if (check) { // 如果 liked_widget_bloc ,直接取消,不执行 liked_widget_bloc 操作 bool liked = await isLiked(widgetId); if (liked) return 0; } String sql = """ INSERT INTO category_widget (categoryId, widgetId) SELECT id, ? FROM category WHERE type = 1; """; return await database.rawInsert(sql, [widgetId]); } Future unlike(int widgetId, {bool check = true}) async { if (check) { // 如果未 liked_widget_bloc ,直接取消,不执行 unlike 操作 bool liked = await isLiked(widgetId); if (!liked) return; } String sql = """ DELETE FROM category_widget WHERE categoryId IN (SELECT id FROM category WHERE type = 1) AND widgetId = ?; """; await database.execute(sql, [widgetId]); } // 判断组件是否已 liked Future isLiked(int widgetId) async { String sql = """ SELECT EXISTS( SELECT 1 FROM category c JOIN category_widget cw ON c.id = cw.categoryId WHERE c.type = 1 AND cw.widgetId = ? ) as is_liked; """; var data = await database.rawQuery(sql, [widgetId]); if (data.isNotEmpty) { var result = data[0]; return result['is_liked'] != 0; } return false; } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/dao/node_dao.dart ================================================ import 'package:fx_dao/fx_dao.dart'; import '../po/node_po.dart'; class NodeDao extends Dao { @override String get createSql => ''; @override String get name => 'node'; Future insert(NodePo widget) async { String addSql = "INSERT INTO " "node(widgetId,name,priority,subtitle,code) " "VALUES (?,?,?,?,?);"; return await database.transaction((tran) async => await tran.rawInsert( addSql, [ widget.widgetId, widget.name, widget.priority, widget.subtitle, widget.code ])); } Future>> queryAll() async => database.rawQuery("SELECT * FROM node"); //根据 id 查询组件 node Future>> queryById(int id, {String? locale}) async { String sql = """ SELECT node.priority, node.code, node_desc.name, node_desc.subtitle FROM node INNER JOIN node_desc ON node.id = node_desc.node_id WHERE node_desc.locale = ? AND node.widgetId = ? ORDER BY priority """; return await database.rawQuery(sql, [locale ?? 'zh-cn', id]); } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/dao/widget_dao.dart ================================================ import 'package:fx_dao/fx_dao.dart'; import '../../model/widget_filter.dart'; class WidgetDao extends Dao { @override String get createSql => ''; @override String get name => 'widget'; Future>> queryByIds( List ids, String? locale) async { if (ids.isEmpty) { return []; } String searchSql = """ SELECT widget.id, widget.name, widget.family, widget.linkWidget, widget.lever, widget_desc.name AS nameCN, widget_desc.info FROM widget INNER JOIN widget_desc ON widget.id = widget_desc.widget_id WHERE widget_desc.locale = ? AND widget.id in (${'?,' * (ids.length - 1)}?) ORDER BY lever DESC, widget.id ASC; """; return database.rawQuery(searchSql, [(locale ?? 'zh-cn'), ...ids]); } Future>> search(WidgetFilter arguments) async { // 保证 name 参数为空时,不进行搜索 if (arguments.name.isEmpty) { return []; } // * 表示 name 任意 String name = arguments.name == '*' ? '' : arguments.name; bool hasFamily = arguments.family != null; String familySql = hasFamily ? ' AND family = ?' : ''; List familyArg = hasFamily ? [arguments.family!.index] : []; List starArg = arguments.stars; if (arguments.stars.reduce((a, b) => a + b) == -5) { starArg = [1, 2, 3, 4, 5]; } String searchSql = """ SELECT widget.id, widget.name, widget.family, widget.linkWidget, widget.lever, widget_desc.name AS nameCN, widget_desc.info FROM widget INNER JOIN widget_desc ON widget.id = widget_desc.widget_id WHERE widget_desc.locale = ? AND (widget.name LIKE ? OR widget_desc.info LIKE ? OR widget_desc.name LIKE ?) $familySql AND lever IN(?,?,?,?,?) ORDER BY lever DESC, widget.id ASC LIMIT ? OFFSET ? ; """; return database.rawQuery(searchSql, [ arguments.locale ?? 'zh-cn', "%$name%", "%$name%", "%$name%", ...familyArg, ...starArg, arguments.pageSize, arguments.offset ]); } Future total(WidgetFilter args) async { bool hasFamily = args.family != null; String familySql = hasFamily ? 'family = ?' : ''; List familyArg = hasFamily ? [args.family!.index] : []; String sql = "SELECT count(id) as `count` FROM widget WHERE $familySql"; List> result = await database.rawQuery(sql, familyArg); if (result.isNotEmpty) { return result.first['count'] as int ?? 0; } return 0; } Future?> queryWidgetByName(String name, {String? locale}) async { String querySql = """ SELECT widget.id, widget.name, widget.family, widget.linkWidget, widget.lever, widget_desc.name AS nameCN, widget_desc.info FROM widget INNER JOIN widget_desc ON widget.id = widget_desc.widget_id WHERE widget.name = ? AND widget_desc.locale = ? ; """; List> result = await database.rawQuery(querySql, [name, locale ?? 'zh-cn']); if (result.isNotEmpty) { return result.first; } return null; } Future>> queryWidgetFields(int widgetId) async { String sql = """ SELECT * FROM widget_fields WHERE widget_id = ? ORDER BY is_required DESC, field_order ASC """; return database.rawQuery(sql, [widgetId]); } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/dao/widget_statistics_dao.dart ================================================ import 'package:fx_dao/fx_dao.dart'; class WidgetStatisticsDao extends Dao { @override String get createSql => ''; @override String get name => 'widget_statistics'; /// 获取各Family对应的组件数量 Future> getFamilyCount() async { String sql = ''' SELECT family, COUNT(*) as count FROM widget GROUP BY family '''; List> result = await database.rawQuery(sql); Map familyCount = {}; for (var row in result) { familyCount[row['family'] as int] = row['count'] as int; } return familyCount; } /// 获取总组件数量 Future getTotalWidgets() async { String sql = 'SELECT COUNT(*) as count FROM widget'; List> result = await database.rawQuery(sql); return result.first['count'] as int; } /// 获取总字段数量 Future getTotalFields() async { String sql = 'SELECT COUNT(*) as count FROM widget_fields'; List> result = await database.rawQuery(sql); return result.first['count'] as int; } /// 获取星级分布 Future> getLeverDistribution() async { String sql = ''' SELECT CAST(lever as INTEGER) as lever_int, COUNT(*) as count FROM widget GROUP BY CAST(lever as INTEGER) '''; List> result = await database.rawQuery(sql); Map leverDistribution = {}; for (var row in result) { leverDistribution[row['lever_int'] as int] = row['count'] as int; } return leverDistribution; } /// 获取平均字段数量 Future getAverageFields() async { String sql = ''' SELECT AVG(field_count) as avg_fields FROM ( SELECT widget_id, COUNT(*) as field_count FROM widget_fields GROUP BY widget_id ) '''; List> result = await database.rawQuery(sql); var avgFields = result.first['avg_fields']; return avgFields != null ? (avgFields as num).toDouble() : 0.0; } /// 获取每个组件的属性个数 Future> getWidgetFieldsCount() async { String sql = ''' SELECT widget_id, COUNT(*) as field_count FROM widget_fields GROUP BY widget_id '''; List> result = await database.rawQuery(sql); Map fieldsCount = {}; for (var row in result) { fieldsCount[row['widget_id'] as int] = row['field_count'] as int; } return fieldsCount; } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/database.dart ================================================ export 'po/category_po.dart'; export 'po/node_po.dart'; export 'po/widget_po.dart'; export 'dao/like_dao.dart'; export 'dao/node_dao.dart'; export 'dao/widget_dao.dart'; export 'dao/category_dao.dart'; export 'dao/widget_statistics_dao.dart'; export 'db_impl/category_db_repository.dart'; export 'db_impl/node_db_repository.dart'; export 'db_impl/widget_db_repository.dart'; ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/db_impl/category_db_repository.dart ================================================ import 'dart:async'; import 'dart:convert'; import 'package:storage/storage.dart'; import 'package:widget_repository/widget_repository.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: class CategoryDbRepository implements CategoryRepository { CategoryDao get categoryDao => AppStorage().flutter(); LikeDao get likeDao => AppStorage().flutter(); @override Future addCategory(CategoryPo categoryPo) async { int success = await categoryDao.insert(categoryPo); return success != -1; } @override Future check(int categoryId, int widgetId) async { return await categoryDao.existWidgetInCollect(categoryId, widgetId); } @override Future deleteCategory(int id) async { await categoryDao.deleteCollect(id); } @override Future> loadCategories() async { List> data = await categoryDao.queryAll(); List collects = data.map((e) => CategoryPo.fromJson(e)).toList(); return collects.map(CategoryModel.fromPo).toList(); } @override Future> loadCategoryWidgets( {int categoryId = 0, String locale = 'zh-cn'}) async { List> rawData = await categoryDao.loadCollectWidgets(categoryId, locale); List widgets = rawData.map((e) => WidgetPo.fromJson(e)).toList(); return widgets.map(WidgetModel.fromPo).toList(); } @override Future toggleCategory(int categoryId, int widgetId) async { return await categoryDao.toggleCollect(categoryId, widgetId); } @override Future> getCategoryByWidget(int widgetId) async { return await categoryDao.categoryWidgetIds(widgetId); } @override Future updateCategory(CategoryPo categoryPo) async { int success = await categoryDao.update(categoryPo); return success != -1; } @override Future> loadCategoryData() async { List> data = await categoryDao.queryAll(); Completer> completer = Completer(); List collects = []; if (data.isEmpty) { completer.complete([]); } for (int i = 0; i < data.length; i++) { List ids = await categoryDao.loadCollectWidgetIds(data[i]['id']); collects .add(CategoryTo(widgetIds: ids, model: CategoryPo.fromJson(data[i]))); if (i == data.length - 1) { completer.complete(collects); } } return completer.future; } @override Future syncCategoryByData(String data, String likeData) async { try { await categoryDao.clear(); List dataMap = json.decode(data); for (int i = 0; i < dataMap.length; i++) { CategoryPo po = CategoryPo.fromNetJson(dataMap[i]["model"]); List widgetIds = dataMap[i]["widgetIds"]; await addCategory(po); if (widgetIds.isNotEmpty && po.id != null) { await categoryDao.addWidgets(po.id!, widgetIds); } } List likeWidgets = (json.decode(likeData) as List).map((e) => e).toList(); for (int i = 0; i < likeWidgets.length; i++) { await likeDao.like(likeWidgets[i]); } return true; } catch (e) { print(e); return false; } } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/db_impl/node_db_repository.dart ================================================ import 'package:storage/storage.dart'; import 'package:widget_repository/widget_repository.dart'; class NodeDbRepository implements NodeRepository { const NodeDbRepository(); NodeDao get nodeDao => AppStorage().flutter(); @override Future> loadNode(int widgetId, {String? locale}) async { List> data = await nodeDao.queryById(widgetId, locale: locale); List nodes = []; for (int i = 0; i < data.length; i++) { Map e = data[i]; nodes.add(NodeModel.fromJson(e, i)); } return nodes; } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/db_impl/widget_db_repository.dart ================================================ import 'package:storage/storage.dart'; import 'package:widget_repository/widget_repository.dart'; import '../../model/widget_filter.dart'; import '../../repository/widget_repository.dart'; /// create by 张风捷特烈 on 2020-03-03 /// contact me by email 1981462002@qq.com /// 说明 : Widget数据仓库 class WidgetDbRepository implements WidgetRepository { const WidgetDbRepository(); WidgetDao get widgetDao => AppStorage().flutter(); LikeDao get likeDao => AppStorage().flutter(); @override Future> loadLikeWidgets() async { // return []; List likeIds = await likeDao.likeWidgetIds(); List> data = await widgetDao.queryByIds(likeIds, null); List widgets = data.map((e) => WidgetPo.fromJson(e)).toList(); return widgets.map(WidgetModel.fromPo).toList(); } @override Future> searchWidgets(WidgetFilter args) async { List> data = await widgetDao.search(args); List widgets = data.map((e) => WidgetPo.fromJson(e)).toList(); return widgets.map(WidgetModel.fromPo).toList(); } @override Future> loadWidget(List id,String? locale) async { List> data = await widgetDao.queryByIds(id,locale); List widgets = data.map((e) => WidgetPo.fromJson(e)).toList(); if (widgets.isNotEmpty) return widgets.map(WidgetModel.fromPo).toList(); return []; } @override Future toggleLike( int id, ) { return likeDao.toggleCollect(id); } @override Future collected(int id) async { return await likeDao.like(id); } @override Future total(WidgetFilter args) => widgetDao.total(args); @override Future queryWidgetByName(String? name) async { if (name == null) return null; Map? data = await widgetDao.queryWidgetByName(name); if (data != null) { return WidgetModel.fromPo(WidgetPo.fromJson(data)); } return null; } @override Future> loadWidgetFields(int widgetId) async { List> data = await widgetDao.queryWidgetFields(widgetId); return data.map((e) => WidgetFieldModel.fromJson(e)).toList(); } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/po/category_po.dart ================================================ /// create by 张风捷特烈 on 2020-04-17 /// contact me by email 1981462002@qq.com /// 说明: 收藏夹数据库-数据模型 /// /// """ /// CREATE TABLE IF NOT EXISTS category( /// id INTEGER PRIMARY KEY AUTOINCREMENT, /// name VARCHAR(64) NOT NULL, /// color VARCHAR(9) DEFAULT '#FF2196F3', /// info VARCHAR(256) DEFAULT '这里什么都没有...', /// created DATETIME NOT NULL, /// updated DATETIME NOT NULL, /// priority INTEGER DEFAULT 0, /// image VARCHAR(128) NULL image DEFAULT '' /// );"""; //建表语句 /// class CategoryPo { final int? id; final String name; final String color; final String info; final DateTime? created; final DateTime updated; final String image; final int count; final int priority; const CategoryPo({ this.id, required this.name, this.color = '#FFF2F2F2', this.created, required this.updated, this.count = 0, this.priority = 0, this.info = '这里什么都没有...', this.image = '', }); factory CategoryPo.fromJson(Map map) { return CategoryPo( id: map['id'], name: map['name'], color: map["color"], created: DateTime.parse(map["created"]), image: map["image"], priority: map["priority"], count: map["count"], updated: DateTime.parse(map["updated"]), info: map["info"]); } factory CategoryPo.fromNetJson(Map map) { return CategoryPo( id: map['id'], name: map['name'], color: map["color"], created: DateTime.fromMillisecondsSinceEpoch(map["created"]), image: map["image"], priority: map["priority"], count: map["count"], updated: DateTime.fromMillisecondsSinceEpoch(map["updated"]), info: map["info"]); } Map toJson() => { "id": id, "name": name, "info": info, "created": created?.millisecondsSinceEpoch, "updated": updated.millisecondsSinceEpoch, "image": image, "count": count, "color": color, "priority": priority, }; @override String toString() { return 'CategoryPo{id: $id, name: $name, color: $color, info: $info, created: $created, updated: $updated, image: $image, count: $count, priority: $priority}'; } @override List get props => [id, name, color, created, image, info, updated, priority,count]; } ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/po/node_po.dart ================================================ /// create by 张风捷特烈 on 2020-03-04 /// contact me by email 1981462002@qq.com /// 说明: 详情页节点-数据库-数据模型 /// class NodePo { final int id; final int widgetId; final String name; final int priority; final String subtitle; final String code; const NodePo({ required this.id, required this.widgetId, required this.name, required this.priority, required this.subtitle, required this.code, }); factory NodePo.fromJson(Map map) { return NodePo( id: map['id']??0, name: map['name'], widgetId: map["widgetId"], priority: map["priority"], subtitle: map["subtitle"], code: map["code"]); } @override String toString() { return 'NodePo{id: $id, widgetId: $widgetId, name: $name, priority: $priority, subtitle: $subtitle, code: $code}'; } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/database/po/widget_po.dart ================================================ /// create by 张风捷特烈 on 2020-03-04 /// contact me by email 1981462002@qq.com /// 说明: 组件信息-数据库-数据模型 /// class WidgetPo { final int id; final String name; final String nameCN; final int deprecated; final int family; final double lever; final String info; final String linkWidget; const WidgetPo({ required this.id, required this.name, required this.nameCN, required this.deprecated, required this.family, required this.lever, required this.linkWidget, required this.info, }); factory WidgetPo.fromJson(Map map) { return WidgetPo( id: map['id'], name: map['name'], nameCN: map["nameCN"] ?? map['localeName'] ?? '', family: map["family"] ?? '', deprecated: map["deprecated"] ?? 0, lever: map["lever"].toDouble(), linkWidget: map["linkWidget"] ?? '', info: map["info"] ?? ''); } factory WidgetPo.fromDesc(Map map) { return WidgetPo( id: map['id'], name: map['name'], nameCN: map["localName"], family: map["family"], lever: map["lever"].toDouble(), linkWidget: map["linkIds"].join(','), deprecated: map["deprecated"] ?? 0, info: map["info"]); } Map toJson() { return { "id": id, "name": name, "nameCN": nameCN, "family": family, "deprecated": deprecated, "lever": lever, "linkWidget": linkWidget, "info": info }; } @override String toString() { return 'WidgetPo{id: $id, name: $name, nameCN: $nameCN, deprecated: $deprecated, family: $family, lever: $lever, info: $info}'; } @override List get props => [id, name, nameCN, deprecated, family, linkWidget, lever, info]; } ================================================ FILE: modules/widget_system/widget_repository/lib/src/memory/memory_node_repository.dart ================================================ import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:widget_repository/widget_repository.dart'; import '../repository/node_repository.dart'; /// create by 张风捷特烈 on 2020-03-03 /// contact me by email 1981462002@qq.com /// 说明 : Node 数据仓库 class MemoryNodeRepository implements NodeRepository { List? _nodeCache; MemoryNodeRepository(); Future initData() async { if (_nodeCache != null) { return; } ByteData byteData = await rootBundle.load('assets/data/web/node.json'); String content = utf8.decode(byteData.buffer.asUint8List()); var data = json.decode(content) as List; _nodeCache = data.map((e) => NodePo.fromJson(e)).toList(); } @override Future> loadNode(int widgetId,{String? locale}) async { await initData(); return _nodeCache! .where((element) => element.widgetId == widgetId) .map((e) => NodeModel( name: e.name, subtitle: e.subtitle, code: e.code, priority: e.priority, )) .toList(); } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/memory/memory_widget_repository.dart ================================================ import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:widget_repository/widget_repository.dart'; import '../model/widget_filter.dart'; import '../repository/widget_repository.dart'; /// create by 张风捷特烈 on 2020-03-03 /// contact me by email 1981462002@qq.com /// 说明 : Widget 数据仓库 class MemoryWidgetRepository implements WidgetRepository { List? _widgetCache; List get widgets => _widgetCache!; MemoryWidgetRepository(); Future _initData() async { if (_widgetCache != null) { return; } ByteData byteData = await rootBundle.load('assets/data/web/widget.json'); String content = utf8.decode(byteData.buffer.asUint8List()); var data = json.decode(content) as List; _widgetCache = data.map((e) => WidgetPo.fromJson(e)).toList(); } @override Future> searchWidgets(WidgetFilter args) async { await _initData(); var result = widgets .map(WidgetModel.fromPo) .where((e) => checkSearch(e, args)) .toList(); result.sort((a, b) => b.lever.compareTo(a.lever)); return result; } bool checkSearch(WidgetModel model, WidgetFilter args) { bool nameMatch = model.name.toLowerCase().contains(args.name.toLowerCase()); bool nameCNMatch = model.nameCN.toLowerCase().contains(args.name.toLowerCase()); bool familyMatch = model.family == args.family; return nameMatch || nameCNMatch || familyMatch; } @override Future> loadWidget(List id, String? locale) async { await _initData(); var data = widgets.where((element) => id.contains(element.id)); return data.map(WidgetModel.fromPo).toList(); } @override Future> loadLikeWidgets() async { return []; } @override Future queryWidgetByName(String? name) async { await _initData(); Iterable ret = widgets .map(WidgetModel.fromPo) .where((element) => element.name == name); if (ret.isNotEmpty) { return ret.first; } return null; } @override Future toggleLike(int id) async {} @override Future total(WidgetFilter args) async { return 0; } @override Future collected(int id) async { return 0; } @override Future> loadWidgetFields(int widgetId) { // TODO: implement loadWidgetFields throw UnimplementedError(); } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/model/category_model.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:utils/utils.dart'; import '../database/po/category_po.dart'; // import 'package:intl/intl.dart'; // import 'package:utils/utils.dart'; // // import 'package:storage/storage.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: 收藏夹展示数据模型 class CategoryModel extends Equatable { final int? id; final String name; final String info; final String? createDate; final String? imageCover; final int? count; final Color color; const CategoryModel( {required this.name, required this.id, required this.info, this.createDate, this.imageCover, this.count, required this.color}); bool get canDelete => id != null && id! > 1; static CategoryModel fromPo(CategoryPo po) { return CategoryModel( id: po.id, name: po.name, info: po.info, createDate:po.created!=null? DateFormat('yyyy/MM/dd').format(po.created!):null, imageCover: po.image, count: po.count, color: ColorUtils.parse(po.color), ); } @override List get props => [ id, name, info, createDate, imageCover, count, color, ]; @override String toString() { return 'CategoryModel{id: $id, name: $name, info: $info, createDate: $createDate, imageCover: $imageCover, count: $count, color: $color}'; } } // 收藏集的 To 对象,用与上传/同步 收藏集 class CategoryTo{ final CategoryPo model; final List widgetIds; final List? likesData; CategoryTo({required this.model,required this.widgetIds, this.likesData}); Map toJson() => { "model": model, "widgetIds": widgetIds, "likesData":likesData }; } ================================================ FILE: modules/widget_system/widget_repository/lib/src/model/model.dart ================================================ export 'node_model.dart'; export 'widget_model.dart'; export 'category_model.dart'; export 'widget_filter.dart'; export 'widget_statistics.dart'; export 'widget_field_model.dart'; ================================================ FILE: modules/widget_system/widget_repository/lib/src/model/node_model.dart ================================================ import 'package:equatable/equatable.dart'; /// create by 张风捷特烈 on 2020-03-04 /// contact me by email 1981462002@qq.com /// 说明: 详情页节点-展示-数据模型 /// enum NodeType { display, newPage, description, deprecated, } class NodeModel extends Equatable { final String name; final String subtitle; final String code; final int priority; const NodeModel({ required this.name, required this.subtitle, required this.code, required this.priority, }); @override List get props => [name, subtitle, code, priority]; NodeType type(String widget, int priority) { if (widget == 'PinnedHeaderSliver') { return NodeType.newPage; } if (widget == 'NavigationRail') { return NodeType.newPage; } return NodeType.display; } factory NodeModel.fromJson(Map map, int index) { return NodeModel( name: map['name'], subtitle: map["subtitle"], code: map["code"], priority: index); } @override String toString() { return 'Node{name: $name, subtitle: $subtitle, code: $code}'; } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/model/widget_field_model.dart ================================================ import 'package:equatable/equatable.dart'; /// Widget字段模型 class WidgetFieldModel extends Equatable { final int id; final int widgetId; final String fieldName; final String fieldType; final String? fieldDesc; final String? fieldDescZh; final int fieldOrder; final bool isRequired; const WidgetFieldModel({ required this.id, required this.widgetId, required this.fieldName, required this.fieldType, this.fieldDesc, this.fieldDescZh, required this.fieldOrder, required this.isRequired, }); @override List get props => [ id, widgetId, fieldName, fieldType, fieldDesc, fieldDescZh, fieldOrder, isRequired, ]; factory WidgetFieldModel.fromJson(Map json) { return WidgetFieldModel( id: json['id'], widgetId: json['widget_id'], fieldName: json['field_name'], fieldType: json['field_type'], fieldDesc: json['field_desc'], fieldDescZh: json['field_desc_zh'], fieldOrder: json['field_order'], isRequired: json['is_required'] == 1, ); } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/model/widget_filter.dart ================================================ enum WidgetFamily { stateless, stateful, singleChildRender, multiChildRender, sliver, proxy, other, } class WidgetFilter { final String name; final String? locale; final WidgetFamily? family; final List stars; final int page; final int pageSize; const WidgetFilter({ this.name = '', this.locale = 'zh-cn', this.family, this.stars = const [-1, -1, -1, -1, -1], this.page = 1, this.pageSize = 20, }); int get offset =>pageSize*(page-1); WidgetFilter.family( this.family, { this.name = '*', this.page = 1, this.locale = 'zh-cn', this.pageSize = 20, this.stars = const [1, 2, 3, 4, 5], }); WidgetFilter copyWith({ String? name, WidgetFamily? family, List? stars, int? page, String? locale, }) { return WidgetFilter( name: name ?? this.name, family: family ?? this.family, stars: stars ?? this.stars, page: page ?? this.page, locale: locale ?? this.locale, ); } @override String toString() { return 'WidgetFilter{name: $name, family: $family, stars: $stars, page: $page}'; } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/model/widget_model.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; import '../../widget_repository.dart'; import '../database/po/widget_po.dart'; /// create by 张风捷特烈 on 2020-03-04 /// contact me by email 1981462002@qq.com /// 说明: 组件信息-展示-数据模型 /// const List kTabColors = [ Color(0xff44D1FD), Color(0xffFD4F43), Color(0xffB375FF), Color(0xFF4CAF50), Color(0xFFFF9800), Color(0xFF00F1F1), Color(0xFFDBD83F), ]; class WidgetModel extends Equatable { final int id; final String name; final String nameCN; final WidgetFamily family; final bool deprecated; final bool death; final List links; final double lever; final ImageProvider? image; final String info; String get heroId => 'hero_widget_image_$id'; const WidgetModel({ required this.id, required this.name, required this.nameCN, required this.family, this.deprecated = false, this.death = false, required this.links, // required this.type, required this.lever, this.image, required this.info, }); @override List get props => [id]; Color get color => kTabColors[family.index]; static WidgetModel fromPo(WidgetPo po) { return WidgetModel( id: po.id, name: po.name, nameCN: po.nameCN, family: toFamily(po.family), image: convertImage(po.name), lever: po.lever, deprecated: po.deprecated == 1, death: po.deprecated == -1, info: po.info, links: formatLinkTo(po.linkWidget), ); } static convertImage(String name) { // return image.isEmpty ? null : AssetImage(image); return null; } @override String toString() { return 'WidgetModel{id: $id, name: $name, nameCN: $nameCN, family: $family, deprecated: $deprecated, links: $links, lever: $lever, image: $image, info: $info}'; } static List formatLinkTo(String links) { if (links.isEmpty) { return []; } if (!links.contains(',')) { return [int.parse(links)]; } return links.split(',').map((e) => int.parse(e)).toList(); } static WidgetFamily toFamily(int id) { switch (id) { case 0: return WidgetFamily.stateless; case 1: return WidgetFamily.stateful; case 2: return WidgetFamily.singleChildRender; case 3: return WidgetFamily.multiChildRender; case 4: return WidgetFamily.sliver; case 5: return WidgetFamily.proxy; case 6: return WidgetFamily.other; default: return WidgetFamily.stateless; } } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/model/widget_statistics.dart ================================================ import 'package:equatable/equatable.dart'; import 'widget_filter.dart'; /// Widget统计数据模型 class WidgetStatistics extends Equatable { /// 各Family对应的组件数量 final Map familyCount; /// 总组件数量 final int totalWidgets; /// 总字段数量 final int totalFields; /// 平均字段数量 final double averageFields; /// 星级分布 (1-5星对应的组件数量) final Map leverDistribution; /// 每个组件的属性个数 (widgetId -> 属性个数) final Map widgetFieldsCount; const WidgetStatistics({ required this.familyCount, required this.totalWidgets, required this.totalFields, required this.averageFields, required this.leverDistribution, required this.widgetFieldsCount, }); @override List get props => [ familyCount, totalWidgets, totalFields, averageFields, leverDistribution, widgetFieldsCount, ]; /// 获取指定组件的属性个数 int getWidgetFieldCount(int widgetId) { return widgetFieldsCount[widgetId] ?? 0; } @override String toString() { return 'WidgetStatistics{familyCount: $familyCount, totalWidgets: $totalWidgets, totalFields: $totalFields, averageFields: $averageFields, leverDistribution: $leverDistribution, widgetFieldsCount: ${widgetFieldsCount.length} widgets}'; } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/repository/category_repository.dart ================================================ import '../database/po/category_po.dart'; import '../model/model.dart'; /// create by 张风捷特烈 on 2020-04-21 /// contact me by email 1981462002@qq.com /// 说明: 负责数据的存储和操作接口 abstract class CategoryRepository { //切换一个组件在收藏夹中的状态 Future toggleCategory(int categoryId, int widgetId); // 检查一个组件是否在收藏夹内 Future check(int categoryId,int widgetId); // 获取一个收藏夹中的所有组件 Future> loadCategoryWidgets({int categoryId = 0}); // 获取所有收藏集 Future> loadCategories(); // 获取 所有收藏集 及 收藏集对应的组件 id 列表 Future> loadCategoryData(); // 根据 Category 数据 同步 收藏集 Future syncCategoryByData(String data,String likeData); //添加收藏集 Future addCategory(CategoryPo categoryPo); //更新收藏集 Future updateCategory(CategoryPo categoryPo); //删除收藏集 Future deleteCategory(int id); //查看某个组件在哪些收藏集中 Future> getCategoryByWidget(int widgetId); } ================================================ FILE: modules/widget_system/widget_repository/lib/src/repository/node_repository.dart ================================================ import '../model/model.dart'; abstract class NodeRepository{ Future> loadNode(int widgetId,{String? locale}); } ================================================ FILE: modules/widget_system/widget_repository/lib/src/repository/repository.dart ================================================ export 'category_repository.dart'; export 'node_repository.dart'; export 'widget_repository.dart'; export 'widget_statistics_service.dart'; export 'widget_statistics_provider.dart'; ================================================ FILE: modules/widget_system/widget_repository/lib/src/repository/widget_repository.dart ================================================ import '../model/model.dart'; /// create by 张风捷特烈 on 2020-03-03 /// contact me by email 1981462002@qq.com abstract class WidgetRepository { // Future> loadWidgets(WidgetFamily family); Future> loadWidget(List ids,String? locale); Future queryWidgetByName(String? name); /// 根据 [WidgetFilter] 搜索 [WidgetModel] 列表 Future> searchWidgets(WidgetFilter args); Future toggleLike(int id); Future total(WidgetFilter args); Future> loadLikeWidgets(); Future collected(int id); Future> loadWidgetFields(int widgetId); } ================================================ FILE: modules/widget_system/widget_repository/lib/src/repository/widget_statistics_provider.dart ================================================ import 'package:flutter/foundation.dart'; import '../../widget_repository.dart'; import '../model/widget_statistics.dart'; import 'widget_statistics_service.dart'; /// 全局统计数据提供器 class WidgetStatisticsProvider extends ChangeNotifier { static final WidgetStatisticsProvider _instance = WidgetStatisticsProvider._internal(); factory WidgetStatisticsProvider() => _instance; WidgetStatisticsProvider._internal(); WidgetStatistics? _statistics; bool _isLoaded = false; WidgetStatistics? get statistics => _statistics; bool get isLoaded => _isLoaded; /// 加载统计数据 Future loadStatistics(WidgetStatisticsDao dao) async { if (_isLoaded) return; final stopwatch = Stopwatch()..start(); try { _statistics = await getStatistics(dao); _isLoaded = true; stopwatch.stop(); debugPrint('统计数据加载耗时: ${stopwatch.elapsedMilliseconds} ms'); notifyListeners(); } catch (e) { stopwatch.stop(); debugPrint('加载统计数据失败: $e (耗时: ${stopwatch.elapsedMilliseconds} ms)'); } } /// 获取Widget统计数据 Future getStatistics(WidgetStatisticsDao dao) async { final results = await Future.wait([ dao.getFamilyCount(), dao.getTotalWidgets(), dao.getTotalFields(), dao.getAverageFields(), dao.getLeverDistribution(), dao.getWidgetFieldsCount(), ]); final rawFamilyCount = results[0] as Map; final totalWidgets = results[1] as int; final totalFields = results[2] as int; final averageFields = results[3] as double; final leverDistribution = results[4] as Map; final widgetFieldsCount = results[5] as Map; final familyCount = {}; for (var entry in rawFamilyCount.entries) { final family = _intToWidgetFamily(entry.key); familyCount[family] = entry.value; } return WidgetStatistics( familyCount: familyCount, totalWidgets: totalWidgets, totalFields: totalFields, averageFields: averageFields, leverDistribution: leverDistribution, widgetFieldsCount: widgetFieldsCount, ); } WidgetFamily _intToWidgetFamily(int index) { switch (index) { case 0: return WidgetFamily.stateless; case 1: return WidgetFamily.stateful; case 2: return WidgetFamily.singleChildRender; case 3: return WidgetFamily.multiChildRender; case 4: return WidgetFamily.sliver; case 5: return WidgetFamily.proxy; case 6: return WidgetFamily.other; default: return WidgetFamily.stateless; } } } ================================================ FILE: modules/widget_system/widget_repository/lib/src/repository/widget_statistics_service.dart ================================================ import '../model/widget_statistics.dart'; import '../database/dao/widget_statistics_dao.dart'; import '../model/widget_filter.dart'; /// Widget统计服务 class WidgetStatisticsService { final WidgetStatisticsDao dao; const WidgetStatisticsService({required this.dao}); /// 获取Widget统计数据 Future getStatistics() async { final results = await Future.wait([ dao.getFamilyCount(), dao.getTotalWidgets(), dao.getTotalFields(), dao.getAverageFields(), dao.getLeverDistribution(), dao.getWidgetFieldsCount(), ]); final rawFamilyCount = results[0] as Map; final totalWidgets = results[1] as int; final totalFields = results[2] as int; final averageFields = results[3] as double; final leverDistribution = results[4] as Map; final widgetFieldsCount = results[5] as Map; final familyCount = {}; for (var entry in rawFamilyCount.entries) { final family = _intToWidgetFamily(entry.key); familyCount[family] = entry.value; } return WidgetStatistics( familyCount: familyCount, totalWidgets: totalWidgets, totalFields: totalFields, averageFields: averageFields, leverDistribution: leverDistribution, widgetFieldsCount: widgetFieldsCount, ); } WidgetFamily _intToWidgetFamily(int index) { switch (index) { case 0: return WidgetFamily.stateless; case 1: return WidgetFamily.stateful; case 2: return WidgetFamily.singleChildRender; case 3: return WidgetFamily.multiChildRender; case 4: return WidgetFamily.sliver; case 5: return WidgetFamily.proxy; case 6: return WidgetFamily.other; default: return WidgetFamily.stateless; } } } ================================================ FILE: modules/widget_system/widget_repository/lib/widget_repository.dart ================================================ library widget_repository; export 'src/repository/repository.dart'; export 'src/memory/memory_node_repository.dart'; export 'src/memory/memory_widget_repository.dart'; export 'src/model/model.dart'; export 'src/database/database.dart'; ================================================ FILE: modules/widget_system/widget_repository/pubspec.yaml ================================================ name: widget_repository description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ^3.5.0 flutter: ">=1.17.0" dependencies: flutter: sdk: flutter equatable: ^2.0.5 # 相等辅助 intl: ^0.19.0 # 相等辅助 fx_dao: 0.0.3+4 # 数据库 storage: path: ../../basic_system/storage utils: path: ../../basic_system/utils dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^4.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/to/asset-from-package # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/to/font-from-package ================================================ FILE: modules/widget_system/widget_repository/test/widget_repository_test.dart ================================================ ================================================ FILE: modules/widget_system/widget_ui/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies build/ ================================================ FILE: modules/widget_system/widget_ui/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "c519ee916eaeb88923e67befb89c0f1dabfa83e6" channel: "stable" project_type: package ================================================ FILE: modules/widget_system/widget_ui/CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: modules/widget_system/widget_ui/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: modules/widget_system/widget_ui/README.md ================================================ TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. ## Features TODO: List what your package can do. Maybe include images, gifs, or videos. ## Getting started TODO: List prerequisites and provide or point to information on how to start using the package. ## Usage TODO: Include short and useful examples for package users. Add longer examples to `/example` folder. ```dart const like = 'sample'; ``` ## Additional information TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more. ================================================ FILE: modules/widget_system/widget_ui/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: modules/widget_system/widget_ui/lib/src/bloc/bloc.dart ================================================ export 'liked_widget_bloc.dart'; ================================================ FILE: modules/widget_system/widget_ui/lib/src/bloc/liked_widget_bloc.dart ================================================ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_repository/widget_repository.dart'; /// create by 张风捷特烈 on 2020-04-07 /// contact me by email 1981462002@qq.com /// 说明: class LikeWidgetBloc extends Cubit> { final WidgetRepository repository; LikeWidgetBloc({required this.repository}) : super(const []); Future loadLikeData() async { List widgets = await repository.loadLikeWidgets(); emit(widgets); } Future toggle(int widgetId) async { await repository.toggleLike(widgetId); return loadLikeData(); } } ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/field/filed.dart ================================================ export 'widget_fields_dialog.dart'; export 'widget_fields_page.dart'; ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/field/widget_fields_dialog.dart ================================================ import 'package:flutter/material.dart'; import 'package:widget_repository/widget_repository.dart'; class WidgetFieldsDialog extends StatefulWidget { final int widgetId; final String widgetName; const WidgetFieldsDialog({ super.key, required this.widgetId, required this.widgetName, }); @override State createState() => _WidgetFieldsDialogState(); } class _WidgetFieldsDialogState extends State { List? _fields; bool _isLoading = true; @override void initState() { super.initState(); _loadFields(); } Future _loadFields() async { try { final repository = const WidgetDbRepository(); final fields = await repository.loadWidgetFields(widget.widgetId); // SQL已经按必需属性排序,不需要再次排序 setState(() { _isLoading = false; _fields = fields; }); } catch (e) { setState(() { _isLoading = false; _fields = []; }); } } @override Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Container( width: 500, height: 600, padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(context), const Divider(), const SizedBox(height: 16), Expanded(child: _buildContent()), ], ), ), ); } Widget _buildHeader(BuildContext context) { return Row( children: [ Icon(Icons.widgets, color: Theme.of(context).primaryColor), const SizedBox(width: 8), Text( '${widget.widgetName} 属性', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const Spacer(), IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close), ), ], ); } Widget _buildContent() { if (_isLoading) return const Center(child: CircularProgressIndicator()); if (_fields!.isEmpty) return const Center(child: Text('暂无属性信息')); return ListView.builder( itemCount: _fields!.length, itemBuilder: (context, index) => _buildFieldItem(_fields![index]), ); } Widget _buildFieldItem(WidgetFieldModel field) { return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.withOpacity(0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ RichText( text: TextSpan( children: [ TextSpan( text: '${field.fieldName} : ', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.black), ), TextSpan( text: field.fieldType, style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).primaryColor), ), ], ), ), // if (field.isRequired) _buildRequiredBadge(), ], ), if (field.fieldDescZh != null) ...[ const SizedBox(height: 4), Text( field.fieldDescZh!, style: const TextStyle(fontSize: 11, color: Colors.grey), ), ], ], ), ); } } ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/field/widget_fields_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:widget_repository/widget_repository.dart'; class WidgetFieldsPage extends StatefulWidget { final int widgetId; final String widgetName; const WidgetFieldsPage({ super.key, required this.widgetId, required this.widgetName, }); @override State createState() => _WidgetFieldsPageState(); } class _WidgetFieldsPageState extends State { List? _fields; bool _isLoading = true; @override void initState() { super.initState(); _loadFields(); } Future _loadFields() async { try { final repository = const WidgetDbRepository(); final fields = await repository.loadWidgetFields(widget.widgetId); // SQL已经按必需属性排序,不需要再次排序 setState(() { _isLoading = false; _fields = fields; }); } catch (e) { setState(() { _isLoading = false; _fields = []; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( bottom: PreferredSize( preferredSize: Size(0, 10), child: Container( height: 10, color: Color(0xfff3f4f6), )), title: Text('${widget.widgetName} 属性'), centerTitle: true, ), body: _buildContent(), ); } Widget _buildContent() { if (_isLoading) return const Center(child: CircularProgressIndicator()); if (_fields!.isEmpty) return const Center(child: Text('暂无属性信息')); bool isZh = Localizations.localeOf(context).languageCode == 'zh'; return ListView.separated( separatorBuilder: (_, __) => Divider(), itemCount: _fields!.length, itemBuilder: (context, index) => _buildFieldItem(_fields![index], isZh), ); } Widget _buildFieldItem(WidgetFieldModel field, bool isZh) { Color color = Theme.of(context).primaryColor; return Container( color: Colors.white, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 4, height: 20, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 12), Expanded( child: RichText( text: TextSpan( children: [ TextSpan( text: field.fieldName, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, color: Colors.black87, ), ), ], ), ), ), // if (field.isRequired) _buildRequiredBadge(), ], ), const SizedBox(height: 2), Padding( padding: const EdgeInsets.only(left: 16), child: Text( field.fieldType, style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 14, fontWeight: FontWeight.w600, ), ), ), if (field.fieldDescZh != null) ...[ const SizedBox(height: 8), Text( isZh ? field.fieldDescZh! : '${field.fieldDesc}', style: const TextStyle( fontSize: 13, color: Colors.grey, height: 1.4, ), ), ], ], ), ), ); } } ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/node_tiled/node_tiled.dart ================================================ // TODO Implement this library. ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/view.dart ================================================ export 'widget_tiled/widget_tiled.dart'; export 'node_tiled/node_tiled.dart'; export 'field/filed.dart'; ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/widget_tiled/widget_detail_logo.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_star/star.dart'; import 'package:flutter_star/star_score.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:fx_env/fx_env.dart'; import 'package:widget_ui/widget_ui.dart'; class WidgetDetailLogo extends StatelessWidget { final Color background; final String widgetName; final WidgetModel model; const WidgetDetailLogo({ super.key, required this.background, required this.widgetName, required this.model, }); @override Widget build(BuildContext context) { if (!kApp.isDesktop) { return Padding( padding: const EdgeInsets.only(right: 8.0, top: 0), child: WidgetLogo( background: background, widgetName: widgetName, widgetId: model.id, lever: model.lever, inDetail: true, ), ); } return Stack( children: [ Container( width: 240, height: 160, alignment: Alignment.center, decoration: BoxDecoration( color: background, gradient: LinearGradient( transform: const GradientRotation(270 * 180 / pi), colors: [ background.withValues(alpha: 0.9), background.withValues(alpha: 0.5) ]), borderRadius: BorderRadius.circular(6), ), child: SvgPicture.asset( 'assets/images/widgets/${widgetLogo(widgetName)}', width: 120, ), ), Positioned( top: 6, right: 0, child: StarScore( score: model.lever, star: Star(size: 15, fillColor: Colors.white), ), ), Positioned( bottom: 2, left: 6, child: Text( "#${model.id}", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 14, color: Colors.white70, ), )), _buildFieldCountBadge(context), ], ); } Widget _buildFieldCountBadge(BuildContext context) { final provider = WidgetStatisticsProvider(); final stats = provider.statistics; if (stats == null) return const SizedBox.shrink(); final fieldCount = stats.getWidgetFieldCount(model.id); if (fieldCount == 0) return const SizedBox.shrink(); return Positioned( right: 0, bottom: 0, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _showFieldsDialog(context), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 4), child: Text( '属性:$fieldCount 个', style: const TextStyle( fontSize: 14, color: Colors.white, decoration: TextDecoration.underline, decorationColor: Colors.white, ), ), ), ), ); } void _showFieldsDialog(BuildContext context) { if (kApp.isMobile) { Navigator.push( context, MaterialPageRoute( builder: (context) => WidgetFieldsPage( widgetId: model.id, widgetName: widgetName, ), ), ); } else { showDialog( context: context, builder: (context) => WidgetFieldsDialog( widgetId: model.id, widgetName: widgetName, ), ); } } } ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/widget_tiled/widget_fields_dialog.dart ================================================ import 'package:flutter/material.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:storage/storage.dart'; class WidgetFieldsDialog extends StatefulWidget { final int widgetId; final String widgetName; const WidgetFieldsDialog({ super.key, required this.widgetId, required this.widgetName, }); @override State createState() => _WidgetFieldsDialogState(); } class _WidgetFieldsDialogState extends State { List? _fields; bool _isLoading = true; @override void initState() { super.initState(); _loadFields(); } Future _loadFields() async { try { final repository = const WidgetDbRepository(); final fields = await repository.loadWidgetFields(widget.widgetId); // SQL已经按必需属性排序,不需要再次排序 setState(() { _isLoading = false; _fields = fields; }); } catch (e) { setState(() { _isLoading = false; _fields = []; }); } } @override Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Container( width: 500, height: 600, padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(context), const Divider(), const SizedBox(height: 16), Expanded(child: _buildContent()), ], ), ), ); } Widget _buildHeader(BuildContext context) { return Row( children: [ Icon(Icons.widgets, color: Theme.of(context).primaryColor), const SizedBox(width: 8), Text( '${widget.widgetName} 属性', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const Spacer(), IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close), ), ], ); } Widget _buildContent() { if (_isLoading) return const Center(child: CircularProgressIndicator()); if (_fields!.isEmpty) return const Center(child: Text('暂无属性信息')); return ListView.builder( itemCount: _fields!.length, itemBuilder: (context, index) => _buildFieldItem(_fields![index]), ); } Widget _buildFieldItem(WidgetFieldModel field) { return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.withOpacity(0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ RichText( text: TextSpan( children: [ TextSpan( text: '${field.fieldName} : ', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.black), ), TextSpan( text: field.fieldType, style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).primaryColor), ), ], ), ), // if (field.isRequired) _buildRequiredBadge(), ], ), if (field.fieldDescZh != null) ...[ const SizedBox(height: 4), Text( field.fieldDescZh!, style: const TextStyle(fontSize: 11, color: Colors.grey), ), ], ], ), ); } Widget _buildRequiredBadge() { return Container( margin: const EdgeInsets.only(left: 8), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1), decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(4), ), child: const Text( '必需', style: TextStyle(color: Colors.white, fontSize: 10), ), ); } } ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/widget_tiled/widget_fields_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:storage/storage.dart'; class WidgetFieldsPage extends StatefulWidget { final int widgetId; final String widgetName; const WidgetFieldsPage({ super.key, required this.widgetId, required this.widgetName, }); @override State createState() => _WidgetFieldsPageState(); } class _WidgetFieldsPageState extends State { List? _fields; bool _isLoading = true; @override void initState() { super.initState(); _loadFields(); } Future _loadFields() async { try { final repository = const WidgetDbRepository(); final fields = await repository.loadWidgetFields(widget.widgetId); // SQL已经按必需属性排序,不需要再次排序 setState(() { _isLoading = false; _fields = fields; }); } catch (e) { setState(() { _isLoading = false; _fields = []; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( bottom: PreferredSize( preferredSize: Size(0, 10), child: Container( height: 10, color: Color(0xfff3f4f6), )), title: Text('${widget.widgetName} 属性'), centerTitle: true, ), body: _buildContent(), ); } Widget _buildContent() { if (_isLoading) return const Center(child: CircularProgressIndicator()); if (_fields!.isEmpty) return const Center(child: Text('暂无属性信息')); return ListView.separated( separatorBuilder: (_, __) => Divider(), itemCount: _fields!.length, itemBuilder: (context, index) => _buildFieldItem(_fields![index]), ); } Widget _buildFieldItem(WidgetFieldModel field) { return Container( color: Colors.white, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 4, height: 20, decoration: BoxDecoration( color: field.isRequired ? Colors.red : Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 12), Expanded( child: RichText( text: TextSpan( children: [ TextSpan( text: field.fieldName, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, color: Colors.black87, ), ), ], ), ), ), // if (field.isRequired) _buildRequiredBadge(), ], ), const SizedBox(height: 2), Padding( padding: const EdgeInsets.only(left: 16), child: Text( field.fieldType, style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 14, fontWeight: FontWeight.w600, ), ), ), if (field.fieldDescZh != null) ...[ const SizedBox(height: 8), Text( field.fieldDescZh!, style: const TextStyle( fontSize: 13, color: Colors.grey, height: 1.4, ), ), ], ], ), ), ); } Widget _buildRequiredBadge() { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(12), ), child: const Text( '必需', style: TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w500, ), ), ); } } ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/widget_tiled/widget_id_view.dart ================================================ import 'package:flutter/material.dart'; class WidgetIdView extends StatelessWidget { final int id; const WidgetIdView({super.key, required this.id}); @override Widget build(BuildContext context) { return Text( "#$id", maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 10, color: Colors.white70, ), ); } } ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/widget_tiled/widget_item.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_star/star.dart'; import 'package:flutter_star/star_score.dart'; import 'package:tolyui_message/tolyui_message.dart'; import 'package:tolyui_text/tolyui_text.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:wrapper/wrapper.dart'; import '../../../widget_ui.dart'; sealed class WidgetAction {} class JumpWidgetDetail extends WidgetAction { final int? widgetId; final String? widgetName; final WidgetModel? model; JumpWidgetDetail({ this.widgetId, this.model, this.widgetName, }); } class ToggleLikeWidget extends WidgetAction { final int widgetId; ToggleLikeWidget(this.widgetId); } /// 组价主页单体的样式 class WidgetItem extends StatelessWidget { final WidgetModel model; final String? searchArgs; final ValueChanged onWidget; const WidgetItem({ super.key, required this.model, this.searchArgs, required this.onWidget, }); @override Widget build(BuildContext context) { ThemeData theme = Theme.of(context); ListTileThemeData data = theme.listTileTheme; Color? tileColor = data.tileColor; Color? textColor = data.textColor; bool isDark = theme.brightness == Brightness.dark; textColor = isDark ? textColor : const Color(0xff2F3032); Color color = theme.primaryColor; EdgeInsetsGeometry padding = const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ); return Stack( children: [ InkWell( borderRadius: BorderRadius.circular(6), onTap: () => onWidget.call(JumpWidgetDetail(model: model)), child: Ink( decoration: BoxDecoration( color: tileColor, borderRadius: BorderRadius.circular(6), boxShadow: [ BoxShadow( color: color.withValues(alpha: 0.1), blurRadius: 2, ) ], ), child: Row( children: [ GestureDetector( onLongPress: () => onWidget.call(ToggleLikeWidget(model.id)), child: Hero( tag: model.heroId, child: WidgetLogo( lever: model.lever, background: color, widgetName: model.name, widgetId: model.id)), ), Expanded( child: Padding( padding: padding, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ listTitle(textColor), _buildContent(textColor), _buildFoot(isDark) ], ), ), ), ], ), ), ), LikeTag(model: model, onWidget: onWidget), ], ); } Widget listTitle(Color? textColor) { TextStyle style = TextStyle( fontSize: 14, color: textColor, fontWeight: FontWeight.bold, ); return GestureDetector( child: HighlightText.withArg( model.name, arg: searchArgs, caseSensitive: false, highlightStyle: style.copyWith(color: Colors.red), maxLines: 1, overflow: TextOverflow.ellipsis, style: style, ), onLongPress: () async { await Clipboard.setData(ClipboardData(text: model.name)); $message.success(message: '名称复制成功!'); }, ); } Widget _buildContent(Color? textColor) { TextStyle style = TextStyle(fontSize: 13, color: textColor); return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: HighlightText.withArg( model.info, arg: searchArgs, caseSensitive: false, highlightStyle: style.copyWith(color: Colors.red), maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 13, color: textColor), ), ); } Widget _buildFoot(bool isDark) { TextStyle style = const TextStyle(fontSize: 12, height: 1, color: Color(0xff86909c)); return Row( children: [ Container( width: 4, height: 4, margin: const EdgeInsets.only(right: 6), decoration: const BoxDecoration( color: Color(0xff86909c), shape: BoxShape.circle), ), Expanded( child: HighlightText.withArg( model.nameCN, arg: searchArgs, caseSensitive: false, highlightStyle: style.copyWith(color: Colors.red), maxLines: 1, overflow: TextOverflow.ellipsis, style: style, ), ), Wrapper.just( radius: 4, color: isDark ? const Color(0xff292A2D) : const Color(0xffF3F3F5), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Text( kWidgetFamilyLabelMap[model.family]!, style: TextStyle( color: isDark ? const Color(0xffCCCCCC) : const Color(0xff878D96), height: 1, fontSize: 10, shadows: [ Shadow( color: isDark ? Colors.black : Colors.white, blurRadius: 2, offset: const Offset(1, 1)) ]), ), ), ], ); } } Map get kWidgetFamilyLabelMap => { WidgetFamily.stateless: "Stateless", WidgetFamily.stateful: "Stateful", WidgetFamily.singleChildRender: "SingleChild", WidgetFamily.multiChildRender: "MultiChild", WidgetFamily.sliver: "Sliver", WidgetFamily.proxy: "Proxy", WidgetFamily.other: "Other", }; ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/widget_tiled/widget_like_tag.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:widget_ui/widget_ui.dart'; class LikeTag extends StatelessWidget { final WidgetModel model; final ValueChanged onWidget; const LikeTag({ super.key, required this.model, required this.onWidget, }); @override Widget build(BuildContext context) { bool show = context.select((LikeWidgetBloc bloc) => bloc.state.contains(model)); if (show) { return GestureDetector( onTap: () => onWidget(ToggleLikeWidget(model.id)), child: Container( width: 24, height: 24, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.3), borderRadius: BorderRadius.only( topLeft: Radius.circular(6), bottomRight: Radius.circular(8), )), child: Icon( Icons.star, color: Colors.white, size: 16, ), ), ); } return const SizedBox(); } } ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/widget_tiled/widget_logo.dart ================================================ import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_star/star.dart'; import 'package:flutter_star/star_score.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:widget_repository/widget_repository.dart'; import 'package:fx_env/fx_env.dart'; import 'package:widget_ui/widget_ui.dart'; class WidgetLogo extends StatelessWidget { final Color background; final String widgetName; final int? widgetId; final double lever; final bool inDetail; const WidgetLogo({ super.key, required this.background, required this.widgetName, this.widgetId, required this.lever, this.inDetail = false, }); @override Widget build(BuildContext context) { return Stack( children: [ Container( width: 110, height: 110, alignment: Alignment.center, decoration: BoxDecoration( color: background, gradient: LinearGradient( transform: const GradientRotation(270 * 180 / pi), colors: [ background.withValues(alpha: 0.9), background.withValues(alpha: 0.5) ]), borderRadius: inDetail ? BorderRadius.circular(6) : const BorderRadius.only( topLeft: Radius.circular(6), bottomLeft: Radius.circular(6)), ), child: SvgPicture.asset( 'assets/images/widgets/${widgetLogo(widgetName)}', width: 80, ), ), Positioned( bottom: 4, left: 6, child: WidgetIdView( id: widgetId ?? 0, )), Positioned( top: 6, right: -4, child: StarScore( score: lever, star: Star(size: 10, fillColor: Colors.white), ), ), if (widgetId != null) _buildFieldCountBadge(context), ], ); } Widget _buildFieldCountBadge(BuildContext context) { final provider = WidgetStatisticsProvider(); final stats = provider.statistics; if (stats == null) return const SizedBox.shrink(); final fieldCount = stats.getWidgetFieldCount(widgetId!); if (fieldCount == 0) return const SizedBox.shrink(); return Positioned( right: 0, bottom: 0, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _showFieldsDialog(context), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 4), child: Text( '属性:$fieldCount', style: const TextStyle( fontSize: 10, color: Colors.white, decoration: TextDecoration.underline, decorationColor: Colors.white, ), ), ), ), ); } void _showFieldsDialog(BuildContext context) { if (kApp.isMobile) { Navigator.push( context, MaterialPageRoute( builder: (context) => WidgetFieldsPage( widgetId: widgetId!, widgetName: widgetName, ), ), ); } else { showDialog( context: context, builder: (context) => WidgetFieldsDialog( widgetId: widgetId!, widgetName: widgetName, ), ); } } } String widgetLogo(String widgetName) { return switch (widgetName) { 'Container' => 'Container.svg', 'Text' => 'Text.svg', 'GestureDetector' => 'GestureDetector.svg', 'CircleAvatar' => 'CircleAvatar.svg', 'Card' => 'Card.svg', 'ListView' => 'ListView.svg', 'GridView' => 'GridView.svg', 'SingleChildScrollView' => 'SingleChildScrollView.svg', 'PageView' => 'PageView.svg', 'InputChip' => 'InputChip.svg', 'Chip' => 'Chip.svg', 'FilterChip' => 'FilterChip.svg', 'MaterialButton' => 'MaterialButton.svg', 'FlutterLogo' => 'FlutterLogo.svg', 'RichText' => 'RichText.svg', 'FloatingActionButton' => 'FloatingActionButton.svg', 'Banner' => 'Banner.svg', 'Icon' => 'Icon.svg', _ => 'Widget.svg', }; } class GlassSquare extends StatelessWidget { final Color color; // 主颜色 final double size; // 边长 final Widget? child; // 子组件 const GlassSquare({ super.key, required this.color, this.size = 300, this.child, }); @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(20), child: Stack( alignment: Alignment.center, children: [ // 背景渐变层 Container( width: size, height: size, decoration: BoxDecoration( gradient: LinearGradient( colors: [ color, Color.lerp(color, Colors.white, 0.4)!, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.4), blurRadius: 25, offset: const Offset(0, 8), ), ], ), ), // 毛玻璃效果层 BackdropFilter( filter: ImageFilter.blur(sigmaX: 18, sigmaY: 18), child: Container( width: size, height: size, decoration: BoxDecoration( border: Border.all(color: Colors.white.withOpacity(0.2)), color: Colors.white.withOpacity(0.05), ), ), ), // 高光层 Container( width: size, height: size, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: RadialGradient( colors: [ Colors.white.withOpacity(0.45), Colors.transparent, ], radius: 0.6, center: const Alignment(-0.6, -0.6), ), ), ), // 柔光层 Container( width: size, height: size, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color.fromRGBO(255, 255, 255, 0.15), Color.fromRGBO(0, 0, 0, 0.25), ], ), ), ), // 子组件 if (child != null) child!, ], ), ); } } ================================================ FILE: modules/widget_system/widget_ui/lib/src/view/widget_tiled/widget_tiled.dart ================================================ export 'widget_item.dart'; export 'widget_logo.dart'; export 'widget_id_view.dart'; export 'widget_like_tag.dart'; export 'widget_detail_logo.dart'; ================================================ FILE: modules/widget_system/widget_ui/lib/widget_ui.dart ================================================ library; export "src/view/view.dart"; export 'src/bloc/bloc.dart'; ================================================ FILE: modules/widget_system/widget_ui/pubspec.yaml ================================================ name: widget_ui description: "A new Flutter package project." version: 0.0.1 homepage: environment: sdk: ^3.6.1 flutter: ">=1.17.0" dependencies: flutter: sdk: flutter flutter_bloc: ^8.1.6 # 状态管理 equatable: ^2.0.5 # 相等辅助 tolyui_message: ^0.2.6+1 flutter_star: ^1.0.2 # 星星组件 wrapper: ^1.0.2 # 气泡包裹 tolyui_text: ^0.0.1+4 fx_platform_adapter: 0.0.3 # 平台适配器 flutter_svg: ^2.0.17 # svg 展示 widget_repository: path: ../widget_repository dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/to/asset-from-package # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/to/font-from-package ================================================ FILE: modules/widget_system/widget_ui/test/widget_ui_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:widget_ui/widget_ui.dart'; void main() {} ================================================ FILE: pubspec.yaml ================================================ name: flutter_unit description: All Platform Flutter Experience App. publish_to: none version: 3.4.1+2009 author: 张风捷特烈 <1981462002@qq.com> homepage: https://juejin.cn/user/149189281194766/posts environment: sdk: ">=3.5.0 <4.0.0" workspace: - modules/basic_system/app - modules/basic_system/authentication - modules/basic_system/components - modules/basic_system/l10n - modules/basic_system/storage - modules/basic_system/toly_ui - modules/basic_system/utils - modules/knowledge_system/algorithm - modules/knowledge_system/artifact - modules/knowledge_system/awesome - modules/knowledge_system/layout - modules/painting_system/draw_system - modules/tools_system/treasure_tools - modules/widget_system/widget_module - modules/knowledge_system/note dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter cupertino_icons: ^1.0.4 # 路由与状态管理 go_router: ^14.2.0 # 路由管理 flutter_bloc: ^9.1.1 # 状态管理 ## fx 架构 fx_platform_adapter: 0.0.3 # 平台适配器 fx_go_router_ext: 0.0.6+1 # 路由 fx_dao: 0.0.3+4 fx_dio: 0.0.4+3 fx_boot_starter: 0.1.1 # app 启动器 fx_trace: 0.0.5+5 # 异常追踪/监听 # 数据与持久化 dio: ^5.4.3+1 # 网络请求 shared_preferences: ^2.5.3 # xml 固化 jwt_decoder: ^2.0.1 # jwt 解析 path_provider: ^2.1.5 # 路径 fx_updater: path: modules/basic_system/fx_updater # 平台功能 # connectivity_plus: ^6.1.4 # 网络状态 url_launcher: ^6.3.1 # url archive: ^4.0.6 # 解压 file_picker: ^10.1.9 # 文件选择器 share_plus: ^10.1.4 # 文字分享 package_info_plus: 8.1.4 # 视图展示 tolyui: 0.0.4+10 # tolyui tolyui_refresh: 0.0.1+1 # 下拉刷新 dash_painter: ^1.0.2 # 虚线 flutter_star: ^1.0.2 # 星星组件 flutter_spinkit: ^5.2.0 # loading toggle_rotate: ^1.0.1 # 点击旋转 wrapper: ^1.0.2 # 气泡包裹 webview_flutter: ^4.2.4 # webview flutter_markdown: ^0.7.2+1 # markdown flutter_svg: ^2.0.17 # svg 展示 # 逻辑处理 image: ^4.0.17 # 图像处理 equatable: ^2.0.5 # 相等辅助 pkg_player: path: modules/tools_system/pkg_player uuid: ^4.5.1 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^4.0.0 dependency_overrides: web: ^1.0.0 intl: 0.20.2 fx_dio: 0.0.4+3 flutter_bloc: ^9.1.1 flutter: generate: true uses-material-design: true assets: - assets/images/ - assets/data/ # - assets/data/web/ - assets/images/head_icon/ - assets/images/widgets/ - assets/flutter.db - assets/article.db - assets/version.json fonts: # 配置字体,可配置多个,支持ttf和otf,ttc等字体资源 - family: TolyIcon fonts: - asset: assets/iconfont/toly_icon.ttf - family: IndieFlower #字体名 fonts: - asset: assets/fonts/IndieFlower-Regular.ttf - family: BalooBhai2 #字体名 fonts: - asset: assets/fonts/BalooBhai2-Regular.ttf - family: Inconsolata #字体名 fonts: - asset: assets/fonts/Inconsolata-Regular.ttf - family: Neucha #字体名 fonts: - asset: assets/fonts/Neucha-Regular.ttf - family: ComicNeue #字体名 fonts: - asset: assets/fonts/ComicNeue-Regular.ttf - family: CHOPS fonts: - asset: assets/fonts/CHOPS.ttf toly: icon: src_zip: '' assets_dir: 'assets/iconfont' file_dist: 'packages/app/lib/app/res/toly_icon.dart' ================================================ FILE: test/app_update_test.dart ================================================ import 'package:app/app.dart'; import 'package:fx_dio/fx_dio.dart'; import 'package:fx_updater/fx_updater.dart'; void main() async { FxDio() .register(const ScienceHost(), repInterceptor: ScienceRepInterceptor()); UpgradeApi api = UnitUpgradeApi(); ApiRet info = await api.fetch(1, 'zh'); print(info.data); } ================================================ FILE: test/size.dart ================================================ import 'dart:io'; import 'package:path/path.dart' as p; void main() async{ Directory directory=Directory(r'D:\Projects\Flutter\Github\FlutterUnit\build_tools\output'); List files = directory.listSync(); Map map = {}; for(FileSystemEntity file in files){ if(file is File){ map[p.basename(file.path)] = file.statSync().size; } } print(map); } ================================================ FILE: test/widget_test.dart ================================================ //// This is a basic Flutter widget test. //// //// To perform an interaction with a widget in your test, use the WidgetTester //// utility that Flutter provides. For example, you can send tap and scroll //// gestures. You can also use WidgetTester to find child widgets in the widget //// tree, read text, and verify that the values of widget properties are correct. // //import 'package:flutter/material.dart'; //import 'package:flutter_test/flutter_test.dart'; // //import 'package:flutter_unit/main.dart'; // //void main() { // testWidgets('Counter increments smoke test', (WidgetTester tester) async { // // Build our app and trigger a frame. // await tester.pumpWidget(MyApp()); // // // Verify that our counter starts at 0. // expect(find.text('0'), findsOneWidget); // expect(find.text('1'), findsNothing); // // // Tap the '+' icon and trigger a frame. // await tester.tap(find.byIcon(Icons.add)); // await tester.pump(); // // // Verify that our counter has incremented. // expect(find.text('0'), findsNothing); // expect(find.text('1'), findsOneWidget); // // }); //} ================================================ FILE: web/index.html ================================================ flutter_unit
Flutter Unit Loading...
================================================ FILE: web/manifest.json ================================================ { "name": "flutter_unit", "short_name": "flutter_unit", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" }, { "src": "icons/Icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { "src": "icons/Icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ] } ================================================ FILE: web/splash.js ================================================ window.addEventListener('load', function(ev) { // Download main.dart.js _flutter.loader.load({ {{flutter_js}} {{flutter_build_config}} serviceWorker: { serviceWorkerVersion: {{flutter_service_worker_version}}, }, onEntrypointLoaded: function(engineInitializer) { engineInitializer.initializeEngine().then(function(appRunner) { document.getElementById("app_splash")?.remove(); appRunner.runApp(); }); } }); }) ================================================ FILE: windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: windows/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.14) project(flutter_unit LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "flutter_unit") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() # Define settings for the Profile build mode. set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build; see runner/CMakeLists.txt. add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) # add libs dll to release file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/libs/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/runner/Release) ================================================ FILE: windows/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # Set fallback configurations for older versions of the flutter tool. if (NOT DEFINED FLUTTER_TARGET_PLATFORM) set(FLUTTER_TARGET_PLATFORM "windows-x64") endif() # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" "flutter_texture_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: windows/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // // clang-format off #include "generated_plugin_registrant.h" #include #include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); } ================================================ FILE: windows/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void RegisterPlugins(flutter::PluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST permission_handler_windows screen_retriever_windows share_plus url_launcher_windows window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) # Define the application target. To change its name, change BINARY_NAME in the # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add preprocessor definitions for the build version. target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") # Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") # Add dependency libraries and include directories. Add any application-specific # dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else #define VERSION_AS_NUMBER 1,0,0,0 #endif #if defined(FLUTTER_VERSION) #define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.toly1994" "\0" VALUE "FileDescription", "flutter_unit" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "flutter_unit" "\0" VALUE "LegalCopyright", "Copyright (C) 2022 com.toly1994. All rights reserved." "\0" VALUE "OriginalFilename", "flutter_unit.exe" "\0" VALUE "ProductName", "flutter_unit" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: windows/runner/flutter_window.cpp ================================================ #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: windows/runner/flutter_window.h ================================================ #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow hosting a Flutter view running |project|. explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: windows/runner/main.cpp ================================================ #include #include #include #include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"flutter_unit", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: windows/runner/utils.cpp ================================================ #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE *unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr); std::string utf8_string; if (target_length == 0 || target_length > utf8_string.max_size()) { return utf8_string; } utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } ================================================ FILE: windows/runner/utils.h ================================================ #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ #include #include // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); #endif // RUNNER_UTILS_H_ ================================================ FILE: windows/runner/win32_window.cpp ================================================ #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( // window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, window_class, title.c_str(), WS_OVERLAPPEDWINDOW, // do not add WS_VISIBLE since the window will be shown later Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: windows/runner/win32_window.h ================================================ #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_