├── .gitignore ├── .metadata ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── ScreenShots ├── 1.PNG ├── 2.PNG ├── 3.PNG ├── 4.PNG ├── 5.PNG ├── 6.PNG ├── 7.PNG ├── 8.PNG ├── 9.PNG └── 框架结构图.drawio ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── getx_study │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── html │ ├── index.html │ └── webToApp.html └── images │ ├── ic_eye.png │ ├── ic_head.jpeg │ ├── launchImage.png │ ├── placeholder.png │ ├── saber.jpg │ ├── saber_logo.jpg │ ├── season_ali_pay.jpg │ ├── upgrade.png │ ├── welcome_1.png │ └── welcome_2.jpg ├── clean_flutter_projects.sh ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── PrivacyInfo.xcprivacy ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-1024.png │ │ │ ├── icon-20-ipad.png │ │ │ ├── icon-20@2x-ipad.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-29-ipad.png │ │ │ ├── icon-29.png │ │ │ ├── icon-29@2x-ipad.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-40.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-40@3x.png │ │ │ ├── icon-60@2x.png │ │ │ ├── icon-60@3x.png │ │ │ ├── icon-76.png │ │ │ ├── icon-76@2x.png │ │ │ └── icon-83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── ios_2024-04-11.txt └── update_privacy_info.py ├── lib ├── account_manager │ └── account_manager.dart ├── app_service │ ├── account_service.dart │ └── theme_service.dart ├── base │ ├── base_controller.dart │ ├── base_refresh_controller.dart │ ├── base_request_controller.dart │ ├── box.dart │ ├── class_name.dart │ ├── get_bind_widget.dart │ ├── get_cupertino_controller.dart │ ├── interface.dart │ ├── pure_get_cupertino_app.dart │ ├── reload_data_mixin.dart │ ├── resign_first_responder.dart │ └── resign_first_view.dart ├── entity │ ├── account_info_entity.dart │ ├── article_info_entity.dart │ ├── banner_entity.dart │ ├── base_entity.dart │ ├── coin_rank_entity.dart │ ├── common_response.dart │ ├── common_response.g.dart │ ├── hot_key_entity.dart │ ├── i_entity.dart │ ├── json_convert_extension.dart │ ├── my_coin_entity.dart │ ├── my_coin_history_entity.dart │ ├── page_entity.dart │ ├── pageable.dart │ └── tab_entity.dart ├── enum │ ├── collect_action_type.dart │ ├── main_tag_type.dart │ ├── my.dart │ ├── response_status.dart │ ├── scroll_view_action_type.dart │ ├── tag_type.dart │ └── theme_type.dart ├── example_app │ ├── example_routes.dart │ ├── get_x_app.dart │ ├── h5_js_channel_app.dart │ ├── rx_dart_app.dart │ ├── state_mixin_example_page.dart │ └── stream_app.dart ├── extension │ ├── future_extension.dart │ ├── get_route_extension.dart │ ├── string_extension.dart │ └── theme_data_extension.dart ├── generated │ ├── assets.dart │ └── json │ │ ├── account_info_entity.g.dart │ │ ├── article_info_entity.g.dart │ │ ├── banner_entity.g.dart │ │ ├── base │ │ ├── json_convert_content.dart │ │ └── json_field.dart │ │ ├── coin_rank_entity.g.dart │ │ ├── hot_key_entity.g.dart │ │ ├── my_coin_entity.g.dart │ │ ├── my_coin_history_entity.g.dart │ │ └── tab_entity.g.dart ├── http_client │ ├── login_client.dart │ ├── login_client.g.dart │ ├── request_client.dart │ └── request_client.g.dart ├── http_util │ ├── api.dart │ ├── http_status.dart │ ├── http_util.dart │ ├── network_activity_plugin.dart │ ├── plugins.dart │ └── request.dart ├── i18n │ └── localized_strings.dart ├── logger │ └── logger.dart ├── main.dart ├── my_app.dart ├── pages │ ├── coin_rank │ │ ├── bindings │ │ │ └── coin_rank_binding.dart │ │ ├── controller │ │ │ └── coin_rank_controller.dart │ │ ├── repository │ │ │ └── coin_rank_repository.dart │ │ └── view │ │ │ └── coin_rank_page.dart │ ├── common │ │ ├── countdown_circle.dart │ │ ├── empty_view.dart │ │ ├── error_view.dart │ │ ├── floating_widget.dart │ │ ├── info_cell.dart │ │ ├── keep_alive_wrapper.dart │ │ ├── loading_view.dart │ │ ├── marquee_label.dart │ │ ├── my_list_view.dart │ │ ├── refresh_header_footer.dart │ │ ├── shimmer.dart │ │ ├── status_view.dart │ │ └── unknown_page.dart │ ├── home │ │ ├── binding │ │ │ ├── home_binding.dart │ │ │ ├── hot_key_binding.dart │ │ │ └── search_result_binding.dart │ │ ├── controller │ │ │ ├── home_controller.dart │ │ │ ├── hot_key_controller.dart │ │ │ └── search_result_controller.dart │ │ ├── repository │ │ │ ├── home_repository.dart │ │ │ ├── hot_key_repository.dart │ │ │ └── search_result_repository.dart │ │ └── view │ │ │ ├── home_page.dart │ │ │ ├── hot_key_page.dart │ │ │ ├── search_result_page.dart │ │ │ └── search_text_field.dart │ ├── launch │ │ ├── splash_page.dart │ │ └── welcome_page.dart │ ├── main │ │ ├── bindings │ │ │ └── main_binding.dart │ │ ├── controller │ │ │ └── main_controller.dart │ │ └── view │ │ │ └── main_page.dart │ ├── my │ │ ├── binding │ │ │ ├── login_binding.dart │ │ │ ├── my_binding.dart │ │ │ ├── my_coin_history_binding.dart │ │ │ ├── my_collect_binding.dart │ │ │ └── register_binding.dart │ │ ├── controller │ │ │ ├── get_user_info_mixin.dart │ │ │ ├── login_controller.dart │ │ │ ├── my_coin_history_controller.dart │ │ │ ├── my_collect_controller.dart │ │ │ ├── my_controller.dart │ │ │ └── register_controller.dart │ │ ├── repository │ │ │ ├── my_coin_history_repository.dart │ │ │ ├── my_collect_repository.dart │ │ │ └── my_repository.dart │ │ └── view │ │ │ ├── login_page.dart │ │ │ ├── my_coin_history_page.dart │ │ │ ├── my_collect_page.dart │ │ │ ├── my_page.dart │ │ │ ├── register_page.dart │ │ │ └── theme_setting_page.dart │ ├── tree │ │ ├── bindings │ │ │ └── tabs_binding.dart │ │ ├── controller │ │ │ ├── tab_list_controller.dart │ │ │ └── tabs_controller.dart │ │ ├── repository │ │ │ ├── tab_list_repository.dart │ │ │ └── tabs_repository.dart │ │ └── view │ │ │ ├── tab_list_page.dart │ │ │ ├── tabs_page.dart │ │ │ ├── tree_cell.dart │ │ │ └── tree_page.dart │ └── web │ │ ├── binding │ │ └── web_binding.dart │ │ ├── controller │ │ └── web_controller.dart │ │ ├── repository │ │ └── web_repository.dart │ │ └── view │ │ └── web_page.dart ├── resource │ └── constant.dart └── routes │ ├── getx_router_observer.dart │ ├── history_router_observer.dart │ ├── middleware │ ├── login_middleware.dart │ └── web_middleware.dart │ └── routes.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 ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app_icon_1024.png │ │ ├── app_icon_128.png │ │ ├── app_icon_16.png │ │ ├── app_icon_256.png │ │ ├── app_icon_32.png │ │ ├── app_icon_512.png │ │ └── app_icon_64.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json ├── windows ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h └── 说明.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .packages 33 | .pub-cache/ 34 | .pub/ 35 | /build/ 36 | 37 | # Web related 38 | 39 | # Symbolication related 40 | app.*.symbols 41 | 42 | # Obfuscation related 43 | app.*.map.json 44 | 45 | # Android Studio will place build artifacts here 46 | /android/app/debug 47 | /android/app/profile 48 | /android/app/release 49 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "300451adae589accbece3490f4396f10bdf15e6e" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e 17 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e 18 | - platform: ios 19 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e 20 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "getx_study", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "getx_study (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "getx_study (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "todo-tree.tree.disableCompactFolders": true, 3 | "editor.formatOnSave": true, 4 | "dart.previewFlutterUiGuides": true, 5 | "editor.inlineSuggest.showToolbar": "always" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 seasonZhu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ScreenShots/1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ScreenShots/1.PNG -------------------------------------------------------------------------------- /ScreenShots/2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ScreenShots/2.PNG -------------------------------------------------------------------------------- /ScreenShots/3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ScreenShots/3.PNG -------------------------------------------------------------------------------- /ScreenShots/4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ScreenShots/4.PNG -------------------------------------------------------------------------------- /ScreenShots/5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ScreenShots/5.PNG -------------------------------------------------------------------------------- /ScreenShots/6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ScreenShots/6.PNG -------------------------------------------------------------------------------- /ScreenShots/7.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ScreenShots/7.PNG -------------------------------------------------------------------------------- /ScreenShots/8.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ScreenShots/8.PNG -------------------------------------------------------------------------------- /ScreenShots/9.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ScreenShots/9.PNG -------------------------------------------------------------------------------- /ScreenShots/框架结构图.drawio: -------------------------------------------------------------------------------- 1 | 7VvRkpowFP2aPG6HBEF4XF27fejOOGun2/YtlShpgTgxrrpf30QCAmFH16LQuk8mN3CBk3PvPQkI7GG8ued4ET6wgEQAWcEG2HcAIQgtV/4oyza19F2UGuacBvqgvWFCX4g2Wtq6ogFZlg4UjEWCLsrGKUsSMhUlG+acrcuHzVhUvuoCz4lhmExxZFqfaCDC1Oqh/t7+idB5mF0Zun46EuPsYP0kyxAHbF0w2SNgDzljIm3FmyGJFHgZLul5H18ZzW+Mk0Qcc8LTli3oS0gfkpvNvfXpy+fHux832sszjlb6gb9SspZO1M+QJYKzKCJcP4HYZrBwtkoCojxbwB6sQyrIZIGnanQtiSBtoYgj2YOyad5pdlnCBdkUTPrO7wmLieBbeYgetXsaRU0jZOv+ej8pMEM6LEyIq21Y82Ceu95DJRsarTcgh2qR0wHQMbTyfmto2QZaY86eZWh3j1o9p22wegZYD2yLOweU4zgtA+UbQBkgkSS4VWVA9hKWkDIoZQTJhopvqv3B0b3vhZG7TbGzzToyQ24LJ6nud+1919mftutl5706HUu24lNymBwC8zkRhyOOBKX6Zk5uYfKcmrnLbJxEWNDnclWsm1B9hTGj8sly7uRBVQ2yzEX63PqsYhWrOHKsiiOr4igFxnC041f+2KdTLtMlV8W5bnHJ9v0PTpkEPec0NtW4ujSfTAX2//MJdZxPtt0Yn5B/YT7V61Lpa6xWOm1riL7bNR0PTWl6T8S3Dq19qpi1r+ahqVAfyYItqWDae5fwal/QQ6cGL0mvGRWdQ6t9VQ/dKyyJGUUO6vos9jpSPL1+Q8LeRy0L+/41sq5jZIKNKfsaV5cmlHeNhOrWUtFkwcnS3nR1cWlvbndNBBbti3rf6ZpAReaqekCTgCbz1sFyKgUzZ1F7YJlLxiMSVYCX4Q42WIZI2cdYCMKTnQVZtrQupdr9nb9BQ69nt2JuyzPdgewGi7mtkOrOlt0OirTMY0fSoOsd0FbHJsG+XYn0qto7cwpE5mL98lTNi+rbaJdRHL6B4qdTNX9ZfnA9gd6pehaqmnsk71T9u6yaCaB3qjZM1ZrtKbYSZNk5sQS91sXSNW5OHV1MMiZ1NEJt1FCEov6FI9TcnHrcTMQq2IJRHwwGwLfByAX+CHg9MHKA5wHPVY0BBB5SDX8ABiODqTIMRV5HhixifM/ZGY2iiqlA4xlLhP5aT1Y63deO1biKcTrF0W1E56pkxTQI1OAAa8NUspDwZtKEXVmAwrodf6uGZ72zpQlz80e9VXqfsjw0YXkzJc/ihSnrwTNNGeYLSh+wcBgTwc948utxHN+YRfA2itmM8kY2WAxkavA7+ssr171gGawF61+vgpUCddRktPU1gludxdO/Rsijrvm6VUsTs2zdUdZ6OFVfebYfTv/6q4LuhpO5bX9yONW4aiycZHf/T4D08P3/KezRHw== -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | defaultConfig { 30 | minSdkVersion 21 31 | } 32 | compileSdkVersion 33//flutter.compileSdkVersion 33 | ndkVersion flutter.ndkVersion 34 | 35 | compileOptions { 36 | sourceCompatibility JavaVersion.VERSION_1_8 37 | targetCompatibility JavaVersion.VERSION_1_8 38 | } 39 | 40 | kotlinOptions { 41 | jvmTarget = '1.8' 42 | } 43 | 44 | sourceSets { 45 | main.java.srcDirs += 'src/main/kotlin' 46 | } 47 | 48 | defaultConfig { 49 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 50 | applicationId "com.example.getx_study" 51 | // You can update the following values to match your application needs. 52 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 53 | minSdkVersion flutter.minSdkVersion 54 | targetSdkVersion flutter.targetSdkVersion 55 | versionCode flutterVersionCode.toInteger() 56 | versionName flutterVersionName 57 | } 58 | 59 | buildTypes { 60 | release { 61 | // TODO: Add your own signing config for the release build. 62 | // Signing with the debug keys for now, so `flutter run --release` works. 63 | signingConfig signingConfigs.debug 64 | } 65 | } 66 | } 67 | 68 | flutter { 69 | source '../..' 70 | } 71 | 72 | dependencies { 73 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 74 | } 75 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/getx_study/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.getx_study 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.9.0' 3 | repositories { 4 | //google() 5 | //mavenCentral() 6 | maven { url 'https://maven.aliyun.com/repository/google' } 7 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 8 | maven { url 'https://maven.aliyun.com/nexus/content/groups/public' } 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:7.1.2' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | //google() 20 | //mavenCentral() 21 | maven { url 'https://maven.aliyun.com/repository/google' } 22 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 23 | maven { url 'https://maven.aliyun.com/nexus/content/groups/public' } 24 | } 25 | } 26 | 27 | rootProject.buildDir = '../build' 28 | subprojects { 29 | project.buildDir = "${rootProject.buildDir}/${project.name}" 30 | } 31 | subprojects { 32 | project.evaluationDependsOn(':app') 33 | } 34 | 35 | tasks.register("clean", Delete) { 36 | delete rootProject.buildDir 37 | } 38 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/html/webToApp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 11 | 12 | 38 | -------------------------------------------------------------------------------- /assets/images/ic_eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/assets/images/ic_eye.png -------------------------------------------------------------------------------- /assets/images/ic_head.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/assets/images/ic_head.jpeg -------------------------------------------------------------------------------- /assets/images/launchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/assets/images/launchImage.png -------------------------------------------------------------------------------- /assets/images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/assets/images/placeholder.png -------------------------------------------------------------------------------- /assets/images/saber.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/assets/images/saber.jpg -------------------------------------------------------------------------------- /assets/images/saber_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/assets/images/saber_logo.jpg -------------------------------------------------------------------------------- /assets/images/season_ali_pay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/assets/images/season_ali_pay.jpg -------------------------------------------------------------------------------- /assets/images/upgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/assets/images/upgrade.png -------------------------------------------------------------------------------- /assets/images/welcome_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/assets/images/welcome_1.png -------------------------------------------------------------------------------- /assets/images/welcome_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/assets/images/welcome_2.jpg -------------------------------------------------------------------------------- /clean_flutter_projects.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 获取脚本所在的目录 4 | START_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | 6 | # 遍历目录寻找pubspec.yaml文件,并执行flutter clean 7 | find "$START_DIR" -name 'pubspec.yaml' | while read -r file; do 8 | # 获取Flutter项目的目录路径 9 | project_dir=$(dirname "$file") 10 | echo "Found Flutter project at $project_dir" 11 | # 进入项目目录 12 | cd "$project_dir" || exit 13 | # 执行flutter clean 14 | flutter clean 15 | # 返回到脚本开始的目录,以继续搜索 16 | cd "$START_DIR" || exit 17 | done 18 | 19 | echo "All Flutter projects cleaned." 20 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '15.6' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | 6 | #source 'https://github.com/CocoaPods/Specs.git' 7 | 8 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 9 | 10 | project 'Runner', { 11 | 'Debug' => :debug, 12 | 'Profile' => :release, 13 | 'Release' => :release, 14 | } 15 | 16 | def flutter_root 17 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 18 | unless File.exist?(generated_xcode_build_settings_path) 19 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 20 | end 21 | 22 | File.foreach(generated_xcode_build_settings_path) do |line| 23 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 24 | return matches[1].strip if matches 25 | end 26 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 27 | end 28 | 29 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 30 | 31 | flutter_ios_podfile_setup 32 | 33 | target 'Runner' do 34 | use_frameworks! 35 | use_modular_headers! 36 | 37 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ios/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | NSPrivacyAccessedAPITypes -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | private let channelName = "com.getStudy.app/popIsEnable" 7 | 8 | override func application( 9 | _ application: UIApplication, 10 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 11 | ) -> Bool { 12 | GeneratedPluginRegistrant.register(with: self) 13 | 14 | guard let controller = window?.rootViewController as? FlutterViewController else { 15 | fatalError("rootViewController is not type FlutterViewController") 16 | } 17 | let channel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.binaryMessenger) 18 | channel.setMethodCallHandler { [weak self] (call, result) in 19 | if call.method == "sendMessage" { 20 | self?.handleMessage(call.arguments) 21 | result("Message received") 22 | } else { 23 | result(FlutterMethodNotImplemented) 24 | } 25 | } 26 | 27 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 28 | } 29 | 30 | private func handleMessage(_ message: Any?) { 31 | print(message) 32 | guard let dict = message as? [String: Bool], 33 | let canGoBack = dict["canGoBack"] else { 34 | return 35 | } 36 | 37 | guard let controller = window?.rootViewController as? FlutterViewController else { 38 | return 39 | } 40 | 41 | /// 这里其实有很本质的问题,那就是navigationController根本不存在,所以这个方法调用没有意义 42 | controller.navigationController?.interactivePopGestureRecognizer?.isEnabled = !canGoBack 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | wandroid 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | getx_study 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationLandscapeRight 40 | UIInterfaceOrientationPortrait 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | UIApplicationSupportsIndirectInputEvents 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/ios_2024-04-11.txt: -------------------------------------------------------------------------------- 1 | Found API Categories: 2 | 3 | Found Dependencies: 4 | 5 | - webview_flutter_wkwebview 6 | - https://raw.githubusercontent.com/flutter/packages/main/packages/webview_flutter/webview_flutter_wkwebview/ios/Resources/PrivacyInfo.xcprivacy 7 | 8 | - FMDB 9 | - https://raw.githubusercontent.com/ccgus/fmdb/master/privacy/PrivacyInfo.xcprivacy 10 | 11 | - device_info_plus 12 | - https://raw.githubusercontent.com/fluttercommunity/plus_plugins/9e187803d395bf1d8cbe74a0494ef28989451dde/packages/device_info_plus/device_info_plus/ios/PrivacyInfo.xcprivacy 13 | 14 | - sqflite 15 | - https://raw.githubusercontent.com/tekartik/sqflite/master/sqflite/darwin/Resources/PrivacyInfo.xcprivacy 16 | 17 | - url_launcher_ios 18 | - https://raw.githubusercontent.com/flutter/packages/main/packages/url_launcher/url_launcher_ios/ios/Resources/PrivacyInfo.xcprivacy 19 | 20 | - image_picker_ios 21 | - https://raw.githubusercontent.com/flutter/packages/main/packages/image_picker/image_picker_ios/ios/Resources/PrivacyInfo.xcprivacy 22 | 23 | - Flutter 24 | - https://raw.githubusercontent.com/flutter/engine/a565cea256c7bafeaa0c26c2f1b0d66a52692d02/shell/platform/darwin/ios/framework/PrivacyInfo.xcprivacy#L9-L12 25 | -------------------------------------------------------------------------------- /lib/app_service/theme_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:getx_study/app_service/account_service.dart'; 5 | import 'package:getx_study/base/get_cupertino_controller.dart'; 6 | import 'package:getx_study/enum/theme_type.dart'; 7 | 8 | class ThemeService extends GetxService { 9 | static ThemeService get find => Get.find(); 10 | 11 | final rxCurrentThemeType = ThemeType.light.obs; 12 | 13 | CupertinoThemeData get themeData { 14 | return rxCurrentThemeType.value.theme; 15 | } 16 | 17 | Color get indicatorColor { 18 | if (rxCurrentThemeType.value == ThemeType.light) { 19 | return Colors.blue; 20 | } else { 21 | return Colors.white; 22 | } 23 | } 24 | 25 | Color get labelColor { 26 | if (rxCurrentThemeType.value == ThemeType.light) { 27 | return Colors.blue; 28 | } else { 29 | return Colors.white; 30 | } 31 | } 32 | 33 | Color get unselectedLabelColor { 34 | if (rxCurrentThemeType.value == ThemeType.light) { 35 | return Colors.lightBlue; 36 | } else { 37 | return Colors.white60; 38 | } 39 | } 40 | 41 | void switchTheme(ThemeType type) async { 42 | final currentThemeType = await AccountService.find.getThemeSetting(); 43 | if (currentThemeType == type) { 44 | return; 45 | } 46 | 47 | rxCurrentThemeType.value = type; 48 | saveThemeType(type); 49 | restartApp(); 50 | } 51 | 52 | void saveThemeType(ThemeType type) { 53 | // 保存 主题设置 54 | AccountService.find.saveThemeSetting(type); 55 | } 56 | 57 | Future getThemeType() async { 58 | // 获取主题设置 59 | final type = await AccountService.find.getThemeSetting(); 60 | rxCurrentThemeType.value = type; 61 | return type; 62 | } 63 | 64 | // 重启应用的方法 65 | Future restartApp() async { 66 | Get.find().restartApp(); 67 | } 68 | 69 | /// 通过GetCupertinoController更换主题颜色的思路 70 | void changeTheme(ThemeType type) async { 71 | final currentThemeType = await AccountService.find.getThemeSetting(); 72 | if (currentThemeType == type) { 73 | return; 74 | } 75 | 76 | rxCurrentThemeType.value = type; 77 | Get.find().setCupertinoTheme(type.theme); 78 | AccountService.find.saveThemeSetting(type); 79 | Get.find().restartApp(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/base/base_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'package:getx_study/app_service/account_service.dart'; 4 | import 'package:getx_study/base/interface.dart'; 5 | import 'package:getx_study/enum/response_status.dart'; 6 | import 'package:getx_study/routes/routes.dart'; 7 | 8 | abstract class BaseController extends GetxController 9 | implements IRetry, IEmptyTap { 10 | ResponseStatus status = ResponseStatus.loading; 11 | } 12 | 13 | /// 要不要with是个问题 14 | mixin ResponseStatusMixin on GetxController { 15 | ResponseStatus status = ResponseStatus.loading; 16 | } 17 | 18 | /// 目前这个玩安卓版本的设计是如果用户是游客,就直接屏蔽功能 19 | /// 这里考虑的是如果所有功能都是开放的,那么点击事件最后都会传到Controller层,那么控制器层就需要用一个方法做统一拦截 20 | /// 这里考虑用的mixin的方式,这样的好处的是,有些页面根本就不需要检查是否登录,也就减少了继承的成本 21 | /// 但是用mixin的问题是,很多页面又都需要写with 22 | /// 先with然后再extends? 23 | /// 本质上GetMiddleware的中间件已经做了这个事情,详细看LoginMiddleware,但是如何去拦截点击事情还没想好 24 | mixin CheckIsLoginMixin on BaseController { 25 | bool checkIsLogin() { 26 | if (!AccountService.find.isLogin) { 27 | Get.toNamed(Routes.login); 28 | } 29 | 30 | return AccountService.find.isLogin; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/base/base_request_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import "package:getx_study/base/base_controller.dart"; 4 | import 'package:getx_study/base/interface.dart'; 5 | import 'package:getx_study/entity/base_entity.dart'; 6 | 7 | abstract class BaseRequestController extends BaseController { 8 | late R request; 9 | 10 | BaseEntity? response; 11 | 12 | T? data; 13 | 14 | @override 15 | void onInit() async { 16 | super.onInit(); 17 | request = Get.find(); 18 | } 19 | 20 | Future aRequest({ 21 | Map? parameters, 22 | }) async {} 23 | 24 | @override 25 | void retry() { 26 | aRequest(); 27 | } 28 | 29 | @override 30 | void emptyTap() { 31 | aRequest(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/base/box.dart: -------------------------------------------------------------------------------- 1 | class Box { 2 | T value; 3 | 4 | Box(this.value); 5 | } 6 | 7 | extension Extension on T { 8 | Box get box => Box(this); 9 | } -------------------------------------------------------------------------------- /lib/base/class_name.dart: -------------------------------------------------------------------------------- 1 | 2 | String className(Object object) => (object).toString(); 3 | 4 | String typeName(Type type) => (type).toString(); -------------------------------------------------------------------------------- /lib/base/get_cupertino_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | /// 在创建GetCupertinoApp会默认创建GetMaterialController,但是GetMaterialController里面没有涉及到CupertinoThemeData的设置 5 | /// 但是又无法对GetMaterialController新增属性,只能整个继承了然后再mixin 6 | class GetCupertinoController extends GetMaterialController with UpdateCupertinoThemeData {} 7 | 8 | mixin UpdateCupertinoThemeData on GetMaterialController { 9 | CupertinoThemeData? cupertinoTheme; 10 | 11 | CupertinoThemeData? darkCupertinoTheme; 12 | 13 | void setCupertinoTheme(CupertinoThemeData value) { 14 | if (darkCupertinoTheme == null) { 15 | cupertinoTheme = value; 16 | } else { 17 | if (value.brightness == Brightness.light) { 18 | cupertinoTheme = value; 19 | } else { 20 | darkCupertinoTheme = value; 21 | } 22 | } 23 | update(); 24 | } 25 | } -------------------------------------------------------------------------------- /lib/base/interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | abstract class IRepository {} 4 | 5 | /// 重试机制 6 | abstract class IRetry { 7 | /// 这里写不写方法的实现好像并不影响代码编译与逻辑 8 | void retry(); 9 | } 10 | 11 | /// 点击空白机制 12 | abstract class IEmptyTap { 13 | /// 这里写不写方法的实现好像并不影响代码编译与逻辑 14 | void emptyTap(); 15 | } 16 | 17 | /// 跳转Web的模型基类 18 | abstract class IWebLoadInfo { 19 | int? id; 20 | int? originId; 21 | String? title; 22 | String? link; 23 | } 24 | 25 | /// 以下代码没有使用,是思考 26 | 27 | abstract class IRequestController extends GetxController {} 28 | 29 | /// 危险,不要定义这个类 30 | /// typedef GetPage = GetView; 31 | 32 | /// 下面这个类在使用上没有意义 33 | /* 34 | abstract class IClassName { 35 | static String? className; 36 | 37 | /// 协议的类方法必须要进行实现,否则就会报错 38 | // static String? Some(); 39 | 40 | // String some(); 41 | } 42 | */ 43 | 44 | -------------------------------------------------------------------------------- /lib/base/reload_data_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | mixin ReloadDataMixin on State { 4 | 5 | void reloadData() { 6 | if (mounted) setState(() {}); 7 | } 8 | } -------------------------------------------------------------------------------- /lib/base/resign_first_responder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | abstract class ResignFirstResponder { 5 | ResignFirstResponder._(); 6 | 7 | /// 此方法需要获取当前Widget的上下文,而且用起来也不够安全 8 | static of(BuildContext context) { 9 | FocusScope.of(context).requestFocus(FocusNode()); 10 | } 11 | 12 | /// 推荐下面的方法 13 | static unfocus() { 14 | FocusManager.instance.primaryFocus?.unfocus(); 15 | } 16 | 17 | static hideKeyboard() { 18 | SystemChannels.textInput.invokeMethod("TextInput.hide"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/base/resign_first_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'resign_first_responder.dart'; 4 | 5 | class ResignFirstView extends StatelessWidget { 6 | const ResignFirstView({Key? key, required this.child}) : super(key: key); 7 | 8 | final Widget child; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return GestureDetector( 13 | onTap: () { 14 | ResignFirstResponder.unfocus(); 15 | }, 16 | child: child, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/entity/account_info_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_field.dart'; 2 | import 'package:getx_study/generated/json/account_info_entity.g.dart'; 3 | import 'dart:convert'; 4 | 5 | @JsonSerializable() 6 | class AccountInfoEntity { 7 | 8 | bool? admin; 9 | List? chapterTops; 10 | List? collectIds; 11 | String? email; 12 | String? icon; 13 | int? id; 14 | String? nickname; 15 | String? password; 16 | String? publicName; 17 | String? token; 18 | int? type; 19 | String? username; 20 | 21 | AccountInfoEntity(); 22 | 23 | factory AccountInfoEntity.fromJson(Map json) => $AccountInfoEntityFromJson(json); 24 | 25 | Map toJson() => $AccountInfoEntityToJson(this); 26 | 27 | @override 28 | String toString() { 29 | return jsonEncode(this); 30 | } 31 | } -------------------------------------------------------------------------------- /lib/entity/article_info_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/generated/json/base/json_field.dart'; 3 | import 'package:getx_study/generated/json/article_info_entity.g.dart'; 4 | import 'dart:convert'; 5 | 6 | @JsonSerializable() 7 | class ArticleInfoEntity { 8 | int? curPage; 9 | List? datas; 10 | int? offset; 11 | bool? over; 12 | int? pageCount; 13 | int? size; 14 | int? total; 15 | 16 | ArticleInfoEntity(); 17 | 18 | factory ArticleInfoEntity.fromJson(Map json) => 19 | $ArticleInfoEntityFromJson(json); 20 | 21 | Map toJson() => $ArticleInfoEntityToJson(this); 22 | 23 | @override 24 | String toString() { 25 | return jsonEncode(this); 26 | } 27 | } 28 | 29 | @JsonSerializable() 30 | class ArticleInfoDatas implements IWebLoadInfo { 31 | String? apkLink; 32 | int? audit; 33 | String? author; 34 | bool? canEdit; 35 | int? chapterId; 36 | String? chapterName; 37 | bool? collect; 38 | int? courseId; 39 | String? desc; 40 | String? descMd; 41 | String? envelopePic; 42 | bool? fresh; 43 | @override 44 | int? id; 45 | @override 46 | String? link; 47 | String? niceDate; 48 | String? niceShareDate; 49 | String? origin; 50 | String? prefix; 51 | String? projectLink; 52 | int? publishTime; 53 | int? selfVisible; 54 | int? shareDate; 55 | String? shareUser; 56 | int? superChapterId; 57 | String? superChapterName; 58 | List? tags; 59 | @override 60 | String? title; 61 | int? type; 62 | int? userId; 63 | int? visible; 64 | int? zan; 65 | @override 66 | int? originId; 67 | 68 | ArticleInfoDatas(); 69 | 70 | factory ArticleInfoDatas.fromJson(Map json) => 71 | $ArticleInfoDatasFromJson(json); 72 | 73 | Map toJson() => $ArticleInfoDatasToJson(this); 74 | 75 | @override 76 | String toString() { 77 | return jsonEncode(this); 78 | } 79 | } 80 | 81 | @JsonSerializable() 82 | class ArticleInfoDatasTags { 83 | String? name; 84 | String? url; 85 | 86 | ArticleInfoDatasTags(); 87 | 88 | factory ArticleInfoDatasTags.fromJson(Map json) => 89 | $ArticleInfoDatasTagsFromJson(json); 90 | 91 | Map toJson() => $ArticleInfoDatasTagsToJson(this); 92 | 93 | @override 94 | String toString() { 95 | return jsonEncode(this); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/entity/banner_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/generated/json/base/json_field.dart'; 3 | import 'package:getx_study/generated/json/banner_entity.g.dart'; 4 | import 'dart:convert'; 5 | 6 | @JsonSerializable() 7 | class BannerEntity implements IWebLoadInfo { 8 | String? desc; 9 | @override 10 | int? id; 11 | String? imagePath; 12 | double? isVisible; 13 | double? order; 14 | @override 15 | String? title; 16 | double? type; 17 | String? url; 18 | @override 19 | String? link; 20 | @override 21 | int? originId; 22 | 23 | BannerEntity(); 24 | 25 | factory BannerEntity.fromJson(Map json) => 26 | $BannerEntityFromJson(json); 27 | 28 | Map toJson() => $BannerEntityToJson(this); 29 | 30 | @override 31 | String toString() { 32 | return jsonEncode(this); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/entity/base_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/entity/i_entity.dart'; 2 | import 'package:getx_study/entity/page_entity.dart'; 3 | import 'package:getx_study/enum/response_status.dart'; 4 | 5 | import 'package:getx_study/resource/constant.dart'; 6 | 7 | class BaseEntity extends IEntity { 8 | BaseEntity(this.errorCode, this.errorMsg, this.data); 9 | 10 | BaseEntity.fromJson(Map json) { 11 | errorCode = json[Constant.errorCode] as int?; 12 | errorMsg = json[Constant.errorMsg] as String?; 13 | if (json.containsKey(Constant.data)) { 14 | data = generateOBJ(json[Constant.data] as Object?); 15 | } 16 | } 17 | 18 | int? errorCode; 19 | String? errorMsg; 20 | T? data; 21 | 22 | bool get isSuccess => errorCode == 0; 23 | 24 | ResponseStatus get responseStatus => _responseStatus; 25 | 26 | ResponseStatus get _responseStatus { 27 | if (errorCode == null) { 28 | return ResponseStatus.loading; 29 | } else if (errorCode == 0) { 30 | if (data is List) { 31 | final listData = data as List; 32 | if (listData.isNotEmpty) { 33 | return ResponseStatus.successHasContent; 34 | } else { 35 | return ResponseStatus.successNoData; 36 | } 37 | } else if (data is PageEntity ) { 38 | final pageEntity = data as PageEntity; 39 | final dataSource = pageEntity.dataSource as List; 40 | if (dataSource.isNotEmpty) { 41 | return ResponseStatus.successHasContent; 42 | } else { 43 | return ResponseStatus.successNoData; 44 | } 45 | } else { 46 | if (data != null) { 47 | return ResponseStatus.successHasContent; 48 | } else { 49 | return ResponseStatus.successNoData; 50 | } 51 | } 52 | } else { 53 | return ResponseStatus.fail; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/entity/coin_rank_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_field.dart'; 2 | import 'package:getx_study/generated/json/coin_rank_entity.g.dart'; 3 | import 'dart:convert'; 4 | 5 | @JsonSerializable() 6 | class CoinRankEntity { 7 | 8 | int? curPage; 9 | List? datas; 10 | int? offset; 11 | bool? over; 12 | int? pageCount; 13 | int? size; 14 | int? total; 15 | 16 | CoinRankEntity(); 17 | 18 | factory CoinRankEntity.fromJson(Map json) => $CoinRankEntityFromJson(json); 19 | 20 | Map toJson() => $CoinRankEntityToJson(this); 21 | 22 | @override 23 | String toString() { 24 | return jsonEncode(this); 25 | } 26 | } 27 | 28 | @JsonSerializable() 29 | class CoinRankDatas { 30 | 31 | int? coinCount; 32 | int? level; 33 | int? rank; 34 | int? userId; 35 | String? username; 36 | 37 | CoinRankDatas(); 38 | 39 | factory CoinRankDatas.fromJson(Map json) => $CoinRankDatasFromJson(json); 40 | 41 | Map toJson() => $CoinRankDatasToJson(this); 42 | 43 | @override 44 | String toString() { 45 | return jsonEncode(this); 46 | } 47 | } -------------------------------------------------------------------------------- /lib/entity/common_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part'common_response.g.dart'; // 生成的文件名 4 | 5 | @JsonSerializable() 6 | class CommonResponse { 7 | final String name; 8 | final int age; 9 | final bool isStudent; 10 | 11 | CommonResponse({required this.name, required this.age, required this.isStudent}); 12 | 13 | factory CommonResponse.fromJson(Map json) => _$CommonResponseFromJson(json); 14 | Map toJson() => _$CommonResponseToJson(this); 15 | } -------------------------------------------------------------------------------- /lib/entity/common_response.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'common_response.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | CommonResponse _$CommonResponseFromJson(Map json) => 10 | CommonResponse( 11 | name: json['name'] as String, 12 | age: (json['age'] as num).toInt(), 13 | isStudent: json['isStudent'] as bool, 14 | ); 15 | 16 | Map _$CommonResponseToJson(CommonResponse instance) => 17 | { 18 | 'name': instance.name, 19 | 'age': instance.age, 20 | 'isStudent': instance.isStudent, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/entity/hot_key_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_field.dart'; 2 | import 'package:getx_study/generated/json/hot_key_entity.g.dart'; 3 | import 'dart:convert'; 4 | 5 | @JsonSerializable() 6 | class HotKeyEntity { 7 | 8 | double? id; 9 | String? link; 10 | String? name; 11 | double? order; 12 | double? visible; 13 | 14 | HotKeyEntity(); 15 | 16 | factory HotKeyEntity.fromJson(Map json) => $HotKeyEntityFromJson(json); 17 | 18 | Map toJson() => $HotKeyEntityToJson(this); 19 | 20 | @override 21 | String toString() { 22 | return jsonEncode(this); 23 | } 24 | } -------------------------------------------------------------------------------- /lib/entity/i_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_convert_content.dart'; 2 | 3 | abstract class IEntity { 4 | T? generateOBJ(Object? json) { 5 | if (T.toString() == 'String') { 6 | return json.toString() as T; 7 | } else if (T.toString() == 'Map') { 8 | return json as T; 9 | } else { 10 | /// List类型数据由fromJsonAsT判断处理 11 | return JsonConvert.fromJsonAsT(json); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /lib/entity/json_convert_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_convert_content.dart'; 2 | 3 | import 'package:getx_study/entity/article_info_entity.dart'; 4 | import 'package:getx_study/entity/coin_rank_entity.dart'; 5 | import 'package:getx_study/entity/my_coin_history_entity.dart'; 6 | import 'package:getx_study/entity/page_entity.dart'; 7 | 8 | extension MoreGenerics on JsonConvert { 9 | /// 解决泛型里面包含泛型的转换问题 10 | static final Map genericsFuncMap = { 11 | "PageEntity>": PageEntity>.fromJson, 12 | "PageEntity>": PageEntity>.fromJson, 13 | "PageEntity>": PageEntity>.fromJson, 14 | }; 15 | } -------------------------------------------------------------------------------- /lib/entity/my_coin_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_field.dart'; 2 | import 'package:getx_study/generated/json/my_coin_entity.g.dart'; 3 | import 'dart:convert'; 4 | 5 | @JsonSerializable() 6 | class MyCoinEntity { 7 | int? coinCount; 8 | int? level; 9 | String? nickname; 10 | String? rank; 11 | int? userId; 12 | String? username; 13 | 14 | MyCoinEntity(); 15 | 16 | factory MyCoinEntity.fromJson(Map json) => 17 | $MyCoinEntityFromJson(json); 18 | 19 | Map toJson() => $MyCoinEntityToJson(this); 20 | 21 | @override 22 | String toString() { 23 | return jsonEncode(this); 24 | } 25 | 26 | /// Dart的可选类型没有解包运算符https://cloud.tencent.com/developer/ask/sof/1357979/answer/1869854 27 | String get userInfo { 28 | if (rank != null && level != null && coinCount != null) { 29 | return "排名: $rank! 等级: $level 积分: $coinCount"; 30 | } else { 31 | return "排名: -- 等级: -- 积分: --"; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/entity/my_coin_history_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_field.dart'; 2 | import 'package:getx_study/generated/json/my_coin_history_entity.g.dart'; 3 | import 'dart:convert'; 4 | 5 | @JsonSerializable() 6 | class MyCoinHistoryEntity { 7 | 8 | int? curPage; 9 | List? datas; 10 | int? offset; 11 | bool? over; 12 | int? pageCount; 13 | int? size; 14 | int? total; 15 | 16 | MyCoinHistoryEntity(); 17 | 18 | factory MyCoinHistoryEntity.fromJson(Map json) => $MyCoinHistoryEntityFromJson(json); 19 | 20 | Map toJson() => $MyCoinHistoryEntityToJson(this); 21 | 22 | @override 23 | String toString() { 24 | return jsonEncode(this); 25 | } 26 | } 27 | 28 | @JsonSerializable() 29 | class MyCoinHistoryDatas { 30 | 31 | int? coinCount; 32 | int? date; 33 | String? desc; 34 | int? id; 35 | String? reason; 36 | int? type; 37 | int? userId; 38 | String? userName; 39 | 40 | MyCoinHistoryDatas(); 41 | 42 | factory MyCoinHistoryDatas.fromJson(Map json) => $MyCoinHistoryDatasFromJson(json); 43 | 44 | Map toJson() => $MyCoinHistoryDatasToJson(this); 45 | 46 | @override 47 | String toString() { 48 | return jsonEncode(this); 49 | } 50 | } -------------------------------------------------------------------------------- /lib/entity/page_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/entity/i_entity.dart'; 2 | import 'package:getx_study/resource/constant.dart'; 3 | 4 | /// 有关与BaseEntity>这种泛型解析我还没有找个非常好的方案,目前还是保证单层解析 5 | class PageEntity extends IEntity { 6 | int? curPage; 7 | T? dataSource; 8 | int? offset; 9 | bool? over; 10 | int? pageCount; 11 | int? size; 12 | int? total; 13 | 14 | PageEntity.fromJson(Map json) { 15 | curPage = json[Constant.curPage] as int?; 16 | offset = json[Constant.offset] as int?; 17 | over = json[Constant.over] as bool?; 18 | pageCount = json[Constant.pageCount] as int?; 19 | size = json[Constant.size] as int?; 20 | total = json[Constant.total] as int?; 21 | if (json.containsKey(Constant.datas)) { 22 | dataSource = generateOBJ(json[Constant.datas] as Object); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/entity/pageable.dart: -------------------------------------------------------------------------------- 1 | // abstract class Pageable { 2 | // int? curPage; 3 | // T? datas; 4 | // int? offset; 5 | // bool? over; 6 | // int? pageCount; 7 | // int? size; 8 | // int? total; 9 | // } 10 | 11 | abstract class Pageable { 12 | int? curPage; 13 | int? offset; 14 | bool? over; 15 | int? pageCount; 16 | int? size; 17 | int? total; 18 | } -------------------------------------------------------------------------------- /lib/entity/tab_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_field.dart'; 2 | import 'package:getx_study/generated/json/tab_entity.g.dart'; 3 | import 'dart:convert'; 4 | 5 | @JsonSerializable() 6 | class TabEntity { 7 | 8 | List? children; 9 | int? courseId; 10 | int? id; 11 | String? name; 12 | int? order; 13 | int? parentChapterId; 14 | bool? userControlSetTop; 15 | int? visible; 16 | 17 | TabEntity(); 18 | 19 | factory TabEntity.fromJson(Map json) => $TabEntityFromJson(json); 20 | 21 | Map toJson() => $TabEntityToJson(this); 22 | 23 | @override 24 | String toString() { 25 | return jsonEncode(this); 26 | } 27 | } -------------------------------------------------------------------------------- /lib/enum/collect_action_type.dart: -------------------------------------------------------------------------------- 1 | enum CollectActionType { 2 | collect, 3 | unCollect; 4 | } 5 | -------------------------------------------------------------------------------- /lib/enum/main_tag_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:getx_study/enum/tag_type.dart'; 3 | 4 | import 'package:getx_study/pages/home/view/home_page.dart'; 5 | import 'package:getx_study/pages/my/view/my_page.dart'; 6 | import 'package:getx_study/pages/tree/view/tabs_page.dart'; 7 | import 'package:getx_study/pages/tree/view/tree_page.dart'; 8 | 9 | enum MainTagType { 10 | home, 11 | project, 12 | publicNumber, 13 | tree, 14 | my; 15 | } 16 | 17 | extension MainTagTypeExt on MainTagType { 18 | IconData get icon { 19 | switch (this) { 20 | case MainTagType.home: 21 | return Icons.home; 22 | case MainTagType.project: 23 | return Icons.web; 24 | case MainTagType.publicNumber: 25 | return Icons.public; 26 | case MainTagType.tree: 27 | return Icons.list; 28 | case MainTagType.my: 29 | return Icons.person; 30 | } 31 | } 32 | 33 | String get title { 34 | switch (this) { 35 | case MainTagType.home: 36 | return "首页"; 37 | case MainTagType.project: 38 | return "项目"; 39 | case MainTagType.publicNumber: 40 | return "公众号"; 41 | case MainTagType.tree: 42 | return "体系"; 43 | case MainTagType.my: 44 | return "我的"; 45 | } 46 | } 47 | 48 | Widget get page { 49 | switch (this) { 50 | case MainTagType.home: 51 | return const HomePage(); 52 | case MainTagType.project: 53 | return const TabsPage(type: TagType.project); 54 | case MainTagType.publicNumber: 55 | return const TabsPage(type: TagType.publicNumber); 56 | case MainTagType.tree: 57 | return const TreePage(); 58 | case MainTagType.my: 59 | return const MyPage(); 60 | } 61 | } 62 | 63 | static List get items { 64 | return MainTagType.values 65 | .map( 66 | (type) => BottomNavigationBarItem( 67 | icon: Icon(type.icon), 68 | label: type.title, 69 | ), 70 | ) 71 | .toList(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/enum/response_status.dart: -------------------------------------------------------------------------------- 1 | enum SuccessStatus { 2 | hasContent(2), 3 | empty(3); 4 | 5 | final int value; 6 | const SuccessStatus(this.value); 7 | 8 | @override 9 | String toString() => 'The $name value is $value'; 10 | } 11 | 12 | enum ResponseStatus { 13 | loading(0), 14 | fail(1), 15 | successHasContent(2), 16 | successNoData(3); 17 | 18 | final int value; 19 | const ResponseStatus(this.value); 20 | 21 | @override 22 | String toString() => 'The $name value is $value'; 23 | } 24 | 25 | /// 这是Dart新特性的例子 26 | enum Water { 27 | frozen(0), 28 | lukewarm(40), 29 | boiling(100); 30 | 31 | final int temperature; 32 | const Water(this.temperature); 33 | 34 | @override 35 | String toString() => 'The $name water is $temperature'; 36 | } 37 | 38 | /// 有关于枚举里面加泛型 39 | enum Season { 40 | spring("好"), 41 | summer(true), 42 | autumn(100), 43 | winter([]); 44 | 45 | final T value; 46 | const Season(this.value); 47 | } 48 | 49 | final season = Season.spring.value; 50 | -------------------------------------------------------------------------------- /lib/enum/scroll_view_action_type.dart: -------------------------------------------------------------------------------- 1 | /// 上下拉行为类型 2 | enum ScrollViewActionType { 3 | refresh, 4 | loadMore; 5 | } 6 | -------------------------------------------------------------------------------- /lib/enum/tag_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/http_util/api.dart'; 2 | 3 | enum TagType { 4 | project, 5 | publicNumber, 6 | tree; 7 | } 8 | 9 | extension Ext on TagType { 10 | String get title { 11 | switch (this) { 12 | case TagType.project: 13 | return "项目"; 14 | case TagType.publicNumber: 15 | return "公众号"; 16 | case TagType.tree: 17 | return "体系"; 18 | } 19 | } 20 | 21 | int get pageNum { 22 | switch (this) { 23 | case TagType.project: 24 | return 1; 25 | case TagType.publicNumber: 26 | return 1; 27 | case TagType.tree: 28 | return 0; 29 | } 30 | } 31 | 32 | String get tabApi { 33 | switch (this) { 34 | case TagType.project: 35 | return Api.getProjectClassify; 36 | case TagType.publicNumber: 37 | return Api.getPublicNumber; 38 | case TagType.tree: 39 | return Api.getTree; 40 | } 41 | } 42 | 43 | String get detailApi { 44 | switch (this) { 45 | case TagType.project: 46 | return Api.getProjectClassifyList; 47 | case TagType.publicNumber: 48 | return Api.getPublicNumberList; 49 | case TagType.tree: 50 | return Api.getTreeDetailList; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/example_app/example_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:getx_study/example_app/get_x_app.dart'; 3 | 4 | abstract class Routes { 5 | Routes._(); 6 | 7 | static const getXExamplePage = "/getXExamplePage"; 8 | 9 | static const getxRxExamplePage = "/getxRxExamplePage"; 10 | 11 | ///页面合集 12 | static final routePage = [ 13 | 14 | GetPage( 15 | name: getXExamplePage, 16 | page: () => const GetXExamplePage(), 17 | binding: GetXExampleBindings(), 18 | ), 19 | GetPage( 20 | name: getxRxExamplePage, 21 | page: () => GetxRxExamplePage(), 22 | binding: GetxRxExampleBindings(), 23 | ), 24 | ]; 25 | } -------------------------------------------------------------------------------- /lib/example_app/rx_dart_app.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:rxdart/rxdart.dart'; 6 | 7 | class RxDartApp extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | home: RxDartExamplePage(), 12 | ); 13 | } 14 | } 15 | 16 | class RxDartExamplePage extends StatelessWidget { 17 | RxDartExamplePage({Key? key}) : super(key: key); 18 | 19 | final _viewModel = RxDartExampleViewModel(); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: AppBar( 25 | title: const Text("RxDart编写计数器"), 26 | ), 27 | body: Center( 28 | child: Column( 29 | mainAxisAlignment: MainAxisAlignment.center, 30 | children: [ 31 | const Text( 32 | 'You have pushed the button this many times:', 33 | ), 34 | StreamBuilder( 35 | stream: _viewModel.stream, 36 | builder: (context, snapshot) { 37 | return Text( 38 | snapshot.data.toString(), 39 | //_viewModel.stream.value.toString(), 40 | style: Theme.of(context).textTheme.headlineMedium, 41 | ); 42 | }, 43 | ), 44 | ], 45 | ), 46 | ), 47 | floatingActionButton: FloatingActionButton( 48 | onPressed: _viewModel.increment, 49 | tooltip: 'Increment', 50 | child: const Icon(Icons.add), 51 | ), 52 | ); 53 | } 54 | } 55 | 56 | class RxDartExampleViewModel { 57 | final _subject = BehaviorSubject.seeded(0); 58 | 59 | ValueStream get stream => _subject.stream; 60 | 61 | void increment() { 62 | _subject.add(_subject.value + 1); 63 | } 64 | 65 | void dispose() { 66 | _subject.close(); 67 | } 68 | } 69 | 70 | /// 尝试使用了PublishSubject,但是感觉和BehaviorSubject差不多 71 | class RxDartExampleViewModel2 { 72 | final _subject = PublishSubject(); 73 | 74 | var _count = 0; 75 | 76 | Stream get stream => _subject.stream; 77 | 78 | StreamSink get sink => _subject.sink; 79 | 80 | RxDartExampleViewModel2() { 81 | _subject.startWith(_count); 82 | } 83 | 84 | void increment() { 85 | ++_count; 86 | _subject.add(_count); 87 | } 88 | 89 | void dispose() { 90 | _subject.close(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/example_app/stream_app.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class StreamApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: StreamExamplePage(), 10 | ); 11 | } 12 | } 13 | 14 | class StreamExamplePage extends StatelessWidget { 15 | StreamExamplePage({Key? key}) : super(key: key); 16 | 17 | final _viewModel = StreamExampleViewModel(); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | appBar: AppBar( 23 | title: const Text("RxDart编写计数器"), 24 | ), 25 | body: Center( 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: [ 29 | const Text( 30 | 'You have pushed the button this many times:', 31 | ), 32 | StreamBuilder( 33 | initialData: 0, 34 | stream: _viewModel.stream, 35 | builder: (context, snapshot) { 36 | return Text( 37 | snapshot.data.toString(), 38 | //_viewModel.stream.value.toString(), 39 | style: Theme.of(context).textTheme.headlineMedium, 40 | ); 41 | }, 42 | ), 43 | ], 44 | ), 45 | ), 46 | floatingActionButton: FloatingActionButton( 47 | onPressed: _viewModel.increment, 48 | tooltip: 'Increment', 49 | child: const Icon(Icons.add), 50 | ), 51 | ); 52 | } 53 | } 54 | 55 | class StreamExampleViewModel { 56 | final _controller = StreamController(); 57 | 58 | var _count = 0; 59 | 60 | Stream get stream => _controller.stream; 61 | 62 | void increment() { 63 | ++_count; 64 | _controller.sink.add(_count); 65 | } 66 | 67 | void dispose() { 68 | _controller.close(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/extension/future_extension.dart: -------------------------------------------------------------------------------- 1 | /* 2 | import 'dart:async'; 3 | 4 | /// https://juejin.cn/post/7372503361361068082 5 | /// This requires the 'records' language feature to be enabled. 6 | /// Try updating your pubspec.yaml to set the minimum SDK constraint to 3.0.0 or higher, and running 'pub get'. 7 | extension FutureZipX on Future { 8 | Future<(T, T2)> zipWith(Future future2) async { 9 | late T result1; 10 | late T2 result2; 11 | await Future.wait([ 12 | then((it) => result1 = it), 13 | future2.then((it) => result2 = it) 14 | ]); 15 | return (result1, result2); 16 | } 17 | } 18 | 19 | final (name, year, married) = await ( 20 | Future.value("andrew"), 21 | Future.value(1984), 22 | Future.value(false), 23 | ).wait; 24 | 25 | final (name, year) = await Future.value("andrew") 26 | .zipWith(Future.value(1984)); 27 | */ -------------------------------------------------------------------------------- /lib/extension/get_route_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | import 'package:getx_study/routes/history_router_observer.dart'; 6 | 7 | /// https://juejin.cn/post/7283315133140107304 8 | extension GetRouteExtension on GetInterface { 9 | ///路由历史 10 | List> get history => historyRouterObserver.history; 11 | 12 | ///是否已打开该页面 13 | bool containName(String name) { 14 | return getRouteByName(name) != null; 15 | } 16 | 17 | ///通过name获取route,从栈顶开始查找 18 | Route? getRouteByName(String name) { 19 | var index = 20 | history.lastIndexWhere((element) => element.settings.name == name); 21 | if (index != -1) { 22 | return history[index]; 23 | } 24 | return null; 25 | } 26 | 27 | ///通过name获取route 28 | List getRoutesByName(String name) { 29 | return history.where((element) => element.settings.name == name).toList(); 30 | } 31 | 32 | ///移除指定的页面,一次 33 | void removeName(String name) { 34 | var route = getRouteByName(name); 35 | if (route != null) { 36 | if (history.last == route) { 37 | //移除当前页面,直接返回 38 | Get.back(); 39 | } else { 40 | Get.removeRoute(route); 41 | } 42 | } 43 | } 44 | 45 | ///移除所有指定的页面 46 | void removeAllName(String name) { 47 | var routes = getRoutesByName(name); 48 | for (var route in routes) { 49 | Get.removeRoute(route); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/extension/string_extension.dart: -------------------------------------------------------------------------------- 1 | // 用于String替换掉Html元素的分类 2 | extension Extension on String { 3 | String get replaceHtmlElement { 4 | return replaceAll(RegExp("(]*>)|()"), "") 5 | .replaceAll(RegExp("\n{2,}"), "\n") 6 | .replaceAll(RegExp("s{2,}"), " ") 7 | .replaceAll("–", "–") 8 | .replaceAll("—", "—") 9 | .replaceAll("‘", "‘") 10 | .replaceAll("’", "’") 11 | .replaceAll("‚", "‚") 12 | .replaceAll("“", "“") 13 | .replaceAll("”", "”") 14 | .replaceAll("„", "„") 15 | .replaceAll("‰", "‰") 16 | .replaceAll("‹", "‹") 17 | .replaceAll("›", "›") 18 | .replaceAll("€", "€") 19 | .replaceAll("

", "") 20 | .replaceAll("

", "") 21 | .replaceAll("
", "\n") 22 | .replaceAll("
", "\n") 23 | .replaceAll("<", "<") 24 | .replaceAll(">", ">") 25 | .replaceAll(" ", " ") 26 | .replaceAll("&", "&") 27 | .replaceAll(""", "\"") 28 | .replaceAll("¥", "¥"); 29 | } 30 | } -------------------------------------------------------------------------------- /lib/generated/assets.dart: -------------------------------------------------------------------------------- 1 | ///This file is automatically generated. DO NOT EDIT, all your changes would be lost. 2 | class Assets { 3 | Assets._(); 4 | 5 | static const String assetsHtmlIndex = 'assets/html/index.html'; 6 | static const String assetsImagesIcEye = 'assets/images/ic_eye.png'; 7 | static const String assetsImagesIcHead = 'assets/images/ic_head.jpeg'; 8 | static const String assetsImagesLaunchImage = 'assets/images/launchImage.png'; 9 | static const String assetsImagesPlaceholder = 'assets/images/placeholder.png'; 10 | static const String assetsImagesSaber = 'assets/images/saber.jpg'; 11 | static const String assetsImagesSaberLogo = 'assets/images/saber_logo.jpg'; 12 | static const String assetsImagesSeasonAliPay = 'assets/images/season_ali_pay.jpg'; 13 | static const String assetsImagesUpgrade = 'assets/images/upgrade.png'; 14 | static const String assetsImagesWelcome1 = 'assets/images/welcome_1.png'; 15 | static const String assetsImagesWelcome2 = 'assets/images/welcome_2.jpg'; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /lib/generated/json/banner_entity.g.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_convert_content.dart'; 2 | import 'package:getx_study/entity/banner_entity.dart'; 3 | 4 | BannerEntity $BannerEntityFromJson(Map json) { 5 | final BannerEntity bannerEntity = BannerEntity(); 6 | final String? desc = jsonConvert.convert(json['desc']); 7 | if (desc != null) { 8 | bannerEntity.desc = desc; 9 | } 10 | final int? id = jsonConvert.convert(json['id']); 11 | if (id != null) { 12 | bannerEntity.id = id; 13 | } 14 | final String? imagePath = jsonConvert.convert(json['imagePath']); 15 | if (imagePath != null) { 16 | bannerEntity.imagePath = imagePath; 17 | } 18 | final double? isVisible = jsonConvert.convert(json['isVisible']); 19 | if (isVisible != null) { 20 | bannerEntity.isVisible = isVisible; 21 | } 22 | final double? order = jsonConvert.convert(json['order']); 23 | if (order != null) { 24 | bannerEntity.order = order; 25 | } 26 | final String? title = jsonConvert.convert(json['title']); 27 | if (title != null) { 28 | bannerEntity.title = title; 29 | } 30 | final double? type = jsonConvert.convert(json['type']); 31 | if (type != null) { 32 | bannerEntity.type = type; 33 | } 34 | final String? url = jsonConvert.convert(json['url']); 35 | if (url != null) { 36 | bannerEntity.url = url; 37 | bannerEntity.link = url; 38 | } 39 | return bannerEntity; 40 | } 41 | 42 | Map $BannerEntityToJson(BannerEntity entity) { 43 | final Map data = {}; 44 | data['desc'] = entity.desc; 45 | data['id'] = entity.id; 46 | data['imagePath'] = entity.imagePath; 47 | data['isVisible'] = entity.isVisible; 48 | data['order'] = entity.order; 49 | data['title'] = entity.title; 50 | data['type'] = entity.type; 51 | data['url'] = entity.url; 52 | data['link'] = entity.url; 53 | return data; 54 | } 55 | -------------------------------------------------------------------------------- /lib/generated/json/base/json_field.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | // ignore_for_file: camel_case_types 3 | // ignore_for_file: prefer_single_quotes 4 | 5 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 6 | 7 | class JsonSerializable{ 8 | const JsonSerializable(); 9 | } 10 | 11 | class JSONField { 12 | //Specify the parse field name 13 | final String? name; 14 | 15 | //Whether to participate in toJson 16 | final bool? serialize; 17 | 18 | //Whether to participate in fromMap 19 | final bool? deserialize; 20 | 21 | const JSONField({this.name, this.serialize, this.deserialize}); 22 | } 23 | -------------------------------------------------------------------------------- /lib/generated/json/hot_key_entity.g.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_convert_content.dart'; 2 | import 'package:getx_study/entity/hot_key_entity.dart'; 3 | 4 | HotKeyEntity $HotKeyEntityFromJson(Map json) { 5 | final HotKeyEntity hotKeyEntity = HotKeyEntity(); 6 | final double? id = jsonConvert.convert(json['id']); 7 | if (id != null) { 8 | hotKeyEntity.id = id; 9 | } 10 | final String? link = jsonConvert.convert(json['link']); 11 | if (link != null) { 12 | hotKeyEntity.link = link; 13 | } 14 | final String? name = jsonConvert.convert(json['name']); 15 | if (name != null) { 16 | hotKeyEntity.name = name; 17 | } 18 | final double? order = jsonConvert.convert(json['order']); 19 | if (order != null) { 20 | hotKeyEntity.order = order; 21 | } 22 | final double? visible = jsonConvert.convert(json['visible']); 23 | if (visible != null) { 24 | hotKeyEntity.visible = visible; 25 | } 26 | return hotKeyEntity; 27 | } 28 | 29 | Map $HotKeyEntityToJson(HotKeyEntity entity) { 30 | final Map data = {}; 31 | data['id'] = entity.id; 32 | data['link'] = entity.link; 33 | data['name'] = entity.name; 34 | data['order'] = entity.order; 35 | data['visible'] = entity.visible; 36 | return data; 37 | } -------------------------------------------------------------------------------- /lib/generated/json/my_coin_entity.g.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_convert_content.dart'; 2 | import 'package:getx_study/entity/my_coin_entity.dart'; 3 | 4 | MyCoinEntity $MyCoinEntityFromJson(Map json) { 5 | final MyCoinEntity myCoinEntity = MyCoinEntity(); 6 | final int? coinCount = jsonConvert.convert(json['coinCount']); 7 | if (coinCount != null) { 8 | myCoinEntity.coinCount = coinCount; 9 | } 10 | final int? level = jsonConvert.convert(json['level']); 11 | if (level != null) { 12 | myCoinEntity.level = level; 13 | } 14 | final String? nickname = jsonConvert.convert(json['nickname']); 15 | if (nickname != null) { 16 | myCoinEntity.nickname = nickname; 17 | } 18 | final String? rank = jsonConvert.convert(json['rank']); 19 | if (rank != null) { 20 | myCoinEntity.rank = rank; 21 | } 22 | final int? userId = jsonConvert.convert(json['userId']); 23 | if (userId != null) { 24 | myCoinEntity.userId = userId; 25 | } 26 | final String? username = jsonConvert.convert(json['username']); 27 | if (username != null) { 28 | myCoinEntity.username = username; 29 | } 30 | return myCoinEntity; 31 | } 32 | 33 | Map $MyCoinEntityToJson(MyCoinEntity entity) { 34 | final Map data = {}; 35 | data['coinCount'] = entity.coinCount; 36 | data['level'] = entity.level; 37 | data['nickname'] = entity.nickname; 38 | data['rank'] = entity.rank; 39 | data['userId'] = entity.userId; 40 | data['username'] = entity.username; 41 | return data; 42 | } -------------------------------------------------------------------------------- /lib/generated/json/tab_entity.g.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/generated/json/base/json_convert_content.dart'; 2 | import 'package:getx_study/entity/tab_entity.dart'; 3 | 4 | TabEntity $TabEntityFromJson(Map json) { 5 | final TabEntity tabEntity = TabEntity(); 6 | final List? children = jsonConvert.convertListNotNull(json['children']); 7 | if (children != null) { 8 | tabEntity.children = children; 9 | } 10 | final int? courseId = jsonConvert.convert(json['courseId']); 11 | if (courseId != null) { 12 | tabEntity.courseId = courseId; 13 | } 14 | final int? id = jsonConvert.convert(json['id']); 15 | if (id != null) { 16 | tabEntity.id = id; 17 | } 18 | final String? name = jsonConvert.convert(json['name']); 19 | if (name != null) { 20 | tabEntity.name = name; 21 | } 22 | final int? order = jsonConvert.convert(json['order']); 23 | if (order != null) { 24 | tabEntity.order = order; 25 | } 26 | final int? parentChapterId = jsonConvert.convert(json['parentChapterId']); 27 | if (parentChapterId != null) { 28 | tabEntity.parentChapterId = parentChapterId; 29 | } 30 | final bool? userControlSetTop = jsonConvert.convert(json['userControlSetTop']); 31 | if (userControlSetTop != null) { 32 | tabEntity.userControlSetTop = userControlSetTop; 33 | } 34 | final int? visible = jsonConvert.convert(json['visible']); 35 | if (visible != null) { 36 | tabEntity.visible = visible; 37 | } 38 | return tabEntity; 39 | } 40 | 41 | Map $TabEntityToJson(TabEntity entity) { 42 | final Map data = {}; 43 | data['children'] = entity.children?.map((v) => v.toJson()).toList(); 44 | data['courseId'] = entity.courseId; 45 | data['id'] = entity.id; 46 | data['name'] = entity.name; 47 | data['order'] = entity.order; 48 | data['parentChapterId'] = entity.parentChapterId; 49 | data['userControlSetTop'] = entity.userControlSetTop; 50 | data['visible'] = entity.visible; 51 | return data; 52 | } -------------------------------------------------------------------------------- /lib/http_client/login_client.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:retrofit/retrofit.dart'; 3 | 4 | import 'package:getx_study/entity/base_entity.dart'; 5 | import 'package:getx_study/entity/banner_entity.dart'; 6 | 7 | part 'login_client.g.dart'; 8 | 9 | const timeout = Duration(seconds: 60); 10 | 11 | final _dio = Dio( 12 | BaseOptions( 13 | baseUrl: "https://www.wanandroid.com/", 14 | connectTimeout: timeout, 15 | receiveTimeout: timeout, 16 | headers: {"name": "season"}, 17 | ), 18 | ); 19 | 20 | /// 通过不同的client来区分不同的业务,一般情况下对于非登录和登录,使用不同的client来进行网络请求, 21 | /// 另外,只有创建这个文件,然后把`part 'login_client.g.dart';`标注好,硬着头皮跑脚本就完事了 22 | /// 回想一下,其实只需要细分不同_dio,就可以差异化不同的业务以及登录与非登录状态 23 | final loginClient = LoginClient(_dio); 24 | 25 | @RestApi( 26 | // 请求域名 27 | baseUrl: 'https://www.wanandroid.com/', 28 | // 数据解析方式,默认为json 29 | parser: Parser.JsonSerializable, 30 | ) 31 | 32 | abstract class LoginClient { 33 | // 标准的构建方式 34 | // dio: 传入发起网络请求的对象 35 | // baseUrl: 请求域名,优先级高于注解 36 | factory LoginClient(Dio dio, {String baseUrl}) = _LoginClient; 37 | 38 | @GET("banner/json") 39 | Future>> getBanner(); 40 | } 41 | -------------------------------------------------------------------------------- /lib/http_client/login_client.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'login_client.dart'; 4 | 5 | // ************************************************************************** 6 | // RetrofitGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers 10 | 11 | class _LoginClient implements LoginClient { 12 | _LoginClient( 13 | this._dio, { 14 | this.baseUrl, 15 | }) { 16 | baseUrl ??= 'https://www.wanandroid.com/'; 17 | } 18 | 19 | final Dio _dio; 20 | 21 | String? baseUrl; 22 | 23 | @override 24 | Future>> getBanner() async { 25 | const _extra = {}; 26 | final queryParameters = {}; 27 | final _headers = {}; 28 | final Map? _data = null; 29 | final _result = await _dio.fetch>( 30 | _setStreamType>>(Options( 31 | method: 'GET', 32 | headers: _headers, 33 | extra: _extra, 34 | ) 35 | .compose( 36 | _dio.options, 37 | 'banner/json', 38 | queryParameters: queryParameters, 39 | data: _data, 40 | ) 41 | .copyWith( 42 | baseUrl: _combineBaseUrls( 43 | _dio.options.baseUrl, 44 | baseUrl, 45 | )))); 46 | final value = BaseEntity>.fromJson(_result.data!); 47 | return value; 48 | } 49 | 50 | RequestOptions _setStreamType(RequestOptions requestOptions) { 51 | if (T != dynamic && 52 | !(requestOptions.responseType == ResponseType.bytes || 53 | requestOptions.responseType == ResponseType.stream)) { 54 | if (T == String) { 55 | requestOptions.responseType = ResponseType.plain; 56 | } else { 57 | requestOptions.responseType = ResponseType.json; 58 | } 59 | } 60 | return requestOptions; 61 | } 62 | 63 | String _combineBaseUrls( 64 | String dioBaseUrl, 65 | String? baseUrl, 66 | ) { 67 | if (baseUrl == null || baseUrl.trim().isEmpty) { 68 | return dioBaseUrl; 69 | } 70 | 71 | final url = Uri.parse(baseUrl); 72 | 73 | if (url.isAbsolute) { 74 | return url.toString(); 75 | } 76 | 77 | return Uri.parse(dioBaseUrl).resolveUri(url).toString(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/http_util/api.dart: -------------------------------------------------------------------------------- 1 | abstract class Api { 2 | 3 | /// 初始化方法私有化 4 | Api._(); 5 | 6 | // baseUrl 7 | static const String baseUrl = 'https://www.wanandroid.com/'; 8 | 9 | // 首页banner 10 | static const String getBanner = 'banner/json'; 11 | 12 | // 首页文章列表 13 | static const String getArticleList = 'article/list/'; 14 | 15 | // 首页置顶文章列表 16 | static const String getTopArticleList = 'article/top/json'; 17 | 18 | // 首页搜索热词 19 | static const String getSearchHotKey = 'hotkey/json'; 20 | 21 | //搜索 https://www.wanandroid.com/article/query/0/json 22 | static const String postQueryKey = 'article/query/'; 23 | 24 | // 项目分类 25 | static const String getProjectClassify = 'project/tree/json'; 26 | 27 | // 项目分类列表 https://www.wanandroid.com/project/list/0/json 28 | static const String getProjectClassifyList = 'project/list/'; 29 | 30 | // 公众号 31 | static const String getPublicNumber = 'wxarticle/chapters/json'; 32 | 33 | // 公众号文章列表 34 | static const String getPublicNumberList = 'wxarticle/list/'; 35 | 36 | // 登录 37 | static const String postLogin = 'user/login'; 38 | 39 | // 注册 40 | static const String postRegister = 'user/register'; 41 | 42 | // 登录退出 43 | static const String getLogout = 'user/logout/json'; 44 | 45 | // 收藏站内文章 lg/collect/1165/json 46 | static const String postCollectArticle = 'lg/collect/'; 47 | 48 | // 取消收藏站内文章 lg/uncollect_originId/1165/json 49 | static const String postUnCollectArticle = 'lg/uncollect_originId/'; 50 | 51 | // 收藏文章列表 52 | static const String getCollectArticleList = 'lg/collect/list/'; 53 | 54 | // 积分排行榜 lg/coin/list/1/json 55 | static const String getRankingList = 'coin/rank/'; 56 | 57 | // 个人积分获取列表 58 | static const String getCoinList = 'lg/coin/list/'; 59 | 60 | // 个人积分 61 | static const String getUserCoinInfo = 'lg/coin/userinfo/json'; 62 | 63 | // 体系 64 | static const String getTree = "tree/json"; 65 | 66 | // 体系详细 article/list/0/json?cid=1 其实和getArticleList接口一样 67 | static const String getTreeDetailList = "article/list/"; 68 | } -------------------------------------------------------------------------------- /lib/http_util/network_activity_plugin.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | /// 如果想要监听请求过程,用Dio自带的InterceptorsWrapper就可以了 4 | 5 | enum NetworkActivityChangeType { 6 | began, 7 | ended; 8 | } 9 | 10 | typedef NetworkActivityCallback = void Function( 11 | NetworkActivityChangeType change, RequestOptions options); 12 | 13 | class NetworkActivityPlugin extends Interceptor { 14 | late NetworkActivityCallback networkActivityCallback; 15 | 16 | NetworkActivityPlugin({ 17 | required this.networkActivityCallback, 18 | }); 19 | 20 | @override 21 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 22 | networkActivityCallback(NetworkActivityChangeType.began, options); 23 | super.onRequest(options, handler); 24 | } 25 | 26 | @override 27 | void onError(DioException err, ErrorInterceptorHandler handler) { 28 | networkActivityCallback( 29 | NetworkActivityChangeType.ended, err.requestOptions); 30 | super.onError(err, handler); 31 | } 32 | 33 | @override 34 | void onResponse(Response response, ResponseInterceptorHandler handler) { 35 | networkActivityCallback( 36 | NetworkActivityChangeType.ended, response.requestOptions); 37 | super.onResponse(response, handler); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/http_util/request.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'http_util.dart'; 4 | 5 | import 'package:getx_study/entity/base_entity.dart'; 6 | 7 | extension Request on HttpUtils { 8 | /// Get请求直接转模型 9 | static Future> get( 10 | {required String api, Map params = const {}}) async { 12 | final data = await HttpUtils.get(api: api, params: params); 13 | final model = BaseEntity.fromJson(data); 14 | return model; 15 | } 16 | 17 | /// Post请求 18 | static Future> post( 19 | {required String api, Map params = const {}}) async { 21 | final data = await HttpUtils.post(api: api, params: params); 22 | final model = BaseEntity.fromJson(data); 23 | return model; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/i18n/localized_strings.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class LocalizedStrings extends Translations { 4 | @override Map> get keys => { 5 | "zh_CN": { 6 | "today": "今天", 7 | "profile": "设置", 8 | "theme_setting": "主题设置", 9 | "light": "浅色", 10 | "dark": "深色", 11 | "blue": "蓝色", 12 | "green": "绿色", 13 | "red": "红色", 14 | }, 15 | "en": { 16 | "today": "Today", 17 | "profile": "Profile", 18 | "theme_setting": "Theme Setting", 19 | "light": "Light", 20 | "dark": "Dark", 21 | "blue": "Blue", 22 | "green": "Green", 23 | "red": "Red", 24 | }, 25 | "fr": { 26 | "today": "Aujourd’hui", 27 | "profile": "Pprofil", 28 | "theme_setting": "Paramètres du thème", 29 | "light": "Lumière", 30 | "dark": "Sombre", 31 | "blue": "Bleu", 32 | "green": "Vert", 33 | "red": "Rouge", 34 | } 35 | }; 36 | } -------------------------------------------------------------------------------- /lib/logger/logger.dart: -------------------------------------------------------------------------------- 1 | import 'package:logger/logger.dart'; 2 | 3 | var logger = Logger( 4 | printer: PrettyPrinter(), 5 | ); 6 | 7 | var loggerNoStack = Logger( 8 | printer: PrettyPrinter(methodCount: 0), 9 | ); -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | 5 | import 'package:get/get.dart'; 6 | 7 | import 'package:getx_study/my_app.dart'; 8 | import 'package:getx_study/app_service/account_service.dart'; 9 | import 'package:getx_study/example_app/stream_app.dart'; 10 | import 'package:getx_study/example_app/get_x_app.dart'; 11 | import 'package:getx_study/example_app/rx_dart_app.dart'; 12 | import 'package:getx_study/example_app/h5_js_channel_app.dart'; 13 | import 'package:package_info_plus/package_info_plus.dart'; 14 | import 'package:getx_study/app_service/theme_service.dart'; 15 | 16 | void main() => run(); 17 | 18 | run() async { 19 | WidgetsFlutterBinding.ensureInitialized(); 20 | 21 | SystemChrome.setPreferredOrientations([ 22 | DeviceOrientation.portraitUp, 23 | DeviceOrientation.portraitDown, 24 | ]); 25 | 26 | //cherrilog(); 27 | 28 | /// 把初始化服务放到runApp之前 29 | final accountService = Get.put(AccountService()); 30 | 31 | final themeService = Get.put(ThemeService()); 32 | 33 | final isFirst = await accountService.getIsFirstLaunch(); 34 | 35 | await themeService.getThemeType(); 36 | 37 | /// 玩安卓App的进这个 38 | runApp(MyApp(isFirst: isFirst)); 39 | 40 | /// 使用StreamController与StreamBuilder构建页面的进这个 41 | //runApp(StreamApp()); 42 | 43 | /// 使用RxDart与StreamBuilder构建页面的进这个 44 | //runApp(RxDartApp()); 45 | 46 | /// 使用GetX构建页面的进这个 47 | //runApp(GetXApp()); 48 | 49 | /// Flutter与JS通信的进这个 50 | //runApp(H5JSChannelApp()); 51 | 52 | if (Platform.isAndroid) { 53 | // 以下两行 设置android状态栏为透明的沉浸。写在组件渲染之后,是为了在渲染后进行set赋值,覆盖状态栏,写在渲染之前MaterialApp组件会覆盖掉这个值。 54 | const systemUiOverlayStyle = 55 | SystemUiOverlayStyle(statusBarColor: Colors.transparent); 56 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 57 | } 58 | } -------------------------------------------------------------------------------- /lib/my_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 5 | 6 | import 'package:get/get.dart'; 7 | import 'package:getx_study/extension/theme_data_extension.dart'; 8 | import 'package:getx_study/logger/logger.dart'; 9 | import 'package:getx_study/routes/getx_router_observer.dart'; 10 | import 'package:getx_study/routes/history_router_observer.dart'; 11 | import 'package:getx_study/routes/routes.dart'; 12 | import 'package:getx_study/app_service/theme_service.dart'; 13 | import 'package:getx_study/i18n/localized_strings.dart'; 14 | 15 | class MyApp extends StatelessWidget { 16 | final bool isFirst; 17 | 18 | const MyApp({Key? key, required this.isFirst}) : super(key: key); 19 | 20 | // This widget is the root of your application. 21 | @override 22 | Widget build(BuildContext context) { 23 | return Obx(() { 24 | return GetCupertinoApp( 25 | title: 'GetX Study', 26 | navigatorObservers: [getXRouterObserver, historyRouterObserver], 27 | unknownRoute: Routes.unknownPage, 28 | 29 | /// 通过使用initialRoute来保证绑定的操作 30 | initialRoute: isFirst ? Routes.welcome : Routes.splash, 31 | getPages: Routes.routePage, 32 | onGenerateRoute: (settings) { 33 | logger.d(settings.name); 34 | return null; 35 | }, 36 | 37 | /// 使用toast 38 | builder: EasyLoading.init(), 39 | theme: ThemeService.find.themeData, //_getCupertinoCurrentTheme(), 40 | translations: LocalizedStrings(), 41 | locale: Get.deviceLocale, //不加这个就不知道当前要用什么locale下的语言, i18n就会失败 42 | fallbackLocale: const Locale("en", "US"), 43 | ); 44 | }); 45 | } 46 | 47 | /// App运行过程中,如果在iOS的设置中更改了亮度模式,还是无法实时进行更改,只能下次运行的时候才能体现变化,体验不好 48 | ThemeData _getMaterialCurrentTheme() { 49 | return WidgetsBinding 50 | .instance.platformDispatcher.platformBrightness.themeData; 51 | //return View.of(context).platformDispatcher.platformBrightness.themeData; 52 | //return SchedulerBinding.instance.window.platformBrightness.themeData; 53 | } 54 | 55 | CupertinoThemeData _getCupertinoCurrentTheme() { 56 | return const CupertinoThemeData( 57 | primaryColor: Colors.blue, 58 | barBackgroundColor: Colors.white, 59 | brightness: Brightness.light); 60 | } 61 | 62 | /// 悼念模式 63 | /// 将这个包裹在GetCupertinoApp外层即可,具体颜色一把使用grey 64 | Widget _changeColorModel({required Color color, required Widget widget}) { 65 | return ColorFiltered( 66 | colorFilter: ColorFilter.mode(color, BlendMode.color), child: widget); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/pages/coin_rank/bindings/coin_rank_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/class_name.dart'; 2 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:getx_study/pages/coin_rank/controller/coin_rank_controller.dart'; 5 | import 'package:getx_study/pages/coin_rank/repository/coin_rank_repository.dart'; 6 | 7 | class CoinRankBinding extends Bindings { 8 | @override 9 | void dependencies() { 10 | Get.lazyPut( 11 | () => CoinRankRepository(), 12 | ); 13 | 14 | /// 需要通过tag来进行区分,避免RefreshController反复使用导致的内存泄露与崩溃 15 | Get.lazyPut( 16 | tag: className(CoinRankController), 17 | () => RefreshController(initialRefresh: true), 18 | ); 19 | Get.lazyPut( 20 | tag: className(CoinRankController), 21 | () => 1, 22 | ); 23 | Get.lazyPut( 24 | () => CoinRankController(), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/pages/coin_rank/controller/coin_rank_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'package:getx_study/base/base_refresh_controller.dart'; 4 | import 'package:getx_study/enum/response_status.dart'; 5 | import 'package:getx_study/entity/coin_rank_entity.dart'; 6 | import 'package:getx_study/base/class_name.dart'; 7 | import 'package:getx_study/pages/coin_rank/repository/coin_rank_repository.dart'; 8 | import 'package:getx_study/enum/scroll_view_action_type.dart'; 9 | 10 | class CoinRankController 11 | extends BaseRefreshController { 12 | @override 13 | void onInit() { 14 | super.onInit(); 15 | initPage = Get.find(tag: className(CoinRankController)); 16 | page = initPage; 17 | refreshController = Get.find(tag: className(CoinRankController)); 18 | } 19 | 20 | @override 21 | Future onRefresh() async { 22 | page = initPage; 23 | await aRequest(type: ScrollViewActionType.refresh); 24 | } 25 | 26 | @override 27 | Future onLoadMore() async { 28 | page = page + 1; 29 | await aRequest(type: ScrollViewActionType.loadMore); 30 | } 31 | 32 | @override 33 | Future aRequest({ 34 | required ScrollViewActionType type, 35 | Map? parameters, 36 | }) async { 37 | response = await request.getCoinRankList(page).catchError((error) { 38 | return processError(type: type, error: error); 39 | }); 40 | 41 | /// 正常场景 42 | status = response?.responseStatus ?? ResponseStatus.loading; 43 | 44 | final models = response?.data?.dataSource ?? []; 45 | 46 | switch (type) { 47 | case ScrollViewActionType.refresh: 48 | dataSource.clear(); 49 | dataSource.addAll(models); 50 | break; 51 | case ScrollViewActionType.loadMore: 52 | dataSource.addAll(models); 53 | break; 54 | } 55 | 56 | refreshControllerStatusUpdate(type); 57 | 58 | update(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/pages/coin_rank/repository/coin_rank_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/entity/base_entity.dart'; 3 | import 'package:getx_study/entity/coin_rank_entity.dart'; 4 | import 'package:getx_study/entity/page_entity.dart'; 5 | import 'package:getx_study/http_util/request.dart' as http; 6 | import 'package:getx_study/http_util/api.dart'; 7 | 8 | class CoinRankRepository extends IRepository { 9 | Future>>> getCoinRankList( 10 | int page) => 11 | http.Request.get(api: "${Api.getRankingList}${page.toString()}/json"); 12 | } 13 | -------------------------------------------------------------------------------- /lib/pages/coin_rank/view/coin_rank_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:getx_study/logger/logger.dart'; 4 | import 'package:getx_study/pages/common/refresh_header_footer.dart'; 5 | import 'package:getx_study/pages/common/status_view.dart'; 6 | 7 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 8 | import 'package:get/get.dart'; 9 | import 'package:getx_study/pages/coin_rank/controller/coin_rank_controller.dart'; 10 | 11 | class CoinRankPage extends GetView { 12 | const CoinRankPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return CupertinoPageScaffold( 17 | navigationBar: const CupertinoNavigationBar( 18 | middle: Text("积分排名"), 19 | ), 20 | child: StatusView( 21 | contentBuilder: (controller) { 22 | return SmartRefresher( 23 | enablePullUp: true, 24 | header: const RefreshHeader(), 25 | footer: const RefreshFooter(), 26 | controller: controller.refreshController, 27 | onRefresh: controller.onRefresh, 28 | onLoading: controller.onLoadMore, 29 | child: ListView.builder( 30 | shrinkWrap: true, 31 | itemCount: controller.dataSource.length, 32 | itemBuilder: (BuildContext context, int index) { 33 | final model = controller.dataSource[index]; 34 | 35 | return ListTile( 36 | leading: Text(model.rank.toString()), 37 | title: Text(model.username.toString()), 38 | trailing: Text('积分:${model.level.toString()}'), 39 | onTap: () => logger.d(model.username), 40 | ); 41 | }, 42 | ), 43 | ); 44 | }, 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/pages/common/empty_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class EmptyView extends StatelessWidget { 4 | final VoidCallback? emptyTap; 5 | 6 | const EmptyView({Key? key, this.emptyTap}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Padding( 11 | padding: const EdgeInsets.all(10.0), 12 | child: Center( 13 | child: Column( 14 | mainAxisAlignment: MainAxisAlignment.center, 15 | children: [ 16 | GestureDetector( 17 | onTap: () { 18 | if (emptyTap != null) { 19 | emptyTap!(); 20 | } 21 | }, 22 | child: const Text( 23 | '暂无数据', 24 | style: TextStyle(fontSize: 20), 25 | ), 26 | ), 27 | ], 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/pages/common/error_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ErrorView extends StatelessWidget { 4 | final VoidCallback? retryAction; 5 | 6 | const ErrorView({Key? key, this.retryAction}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Padding( 11 | padding: const EdgeInsets.all(10.0), 12 | child: Center( 13 | child: Column( 14 | mainAxisAlignment: MainAxisAlignment.center, 15 | children: [ 16 | const Text( 17 | '哎呀,出错了...', 18 | style: TextStyle(fontSize: 20), 19 | ), 20 | const SizedBox( 21 | height: 44, 22 | ), 23 | SizedBox( 24 | height: 44, 25 | width: double.infinity, 26 | child: Padding( 27 | padding: const EdgeInsets.symmetric(horizontal: 20), 28 | child: TextButton( 29 | style: ButtonStyle( 30 | backgroundColor: MaterialStateProperty.all(Colors.blue), 31 | ), 32 | onPressed: () { 33 | if (retryAction != null) { 34 | retryAction!(); 35 | } 36 | }, 37 | child: const Text( 38 | "重试", 39 | style: TextStyle(color: Colors.white), 40 | ), 41 | ), 42 | ), 43 | ) 44 | ], 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/pages/common/keep_alive_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class KeepAliveWrapper extends StatefulWidget { 4 | const KeepAliveWrapper({ 5 | Key? key, 6 | this.keepAlive = true, 7 | required this.child, 8 | }) : super(key: key); 9 | final bool keepAlive; 10 | final Widget child; 11 | 12 | @override 13 | State createState() => _KeepAliveWrapperState(); 14 | } 15 | 16 | class _KeepAliveWrapperState extends State 17 | with AutomaticKeepAliveClientMixin { 18 | @override 19 | Widget build(BuildContext context) { 20 | super.build(context); 21 | return widget.child; 22 | } 23 | 24 | @override 25 | void didUpdateWidget(covariant KeepAliveWrapper oldWidget) { 26 | if(oldWidget.keepAlive != widget.keepAlive) { 27 | // keepAlive 状态需要更新,实现在 AutomaticKeepAliveClientMixin 中 28 | updateKeepAlive(); 29 | } 30 | super.didUpdateWidget(oldWidget); 31 | } 32 | 33 | @override 34 | bool get wantKeepAlive => widget.keepAlive; 35 | } 36 | 37 | /** 38 | 封装了一个 KeepAliveWrapper 组件, 39 | 如果哪个列表项需要缓存,只需要使用 KeepAliveWrapper 包裹一下它即可。 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | var children = []; 44 | for (int i = 0; i < 10++i) { 45 | //只需要用 KeepAliveWrapper 包装一下即可 46 | children.add(KeepAliveWrapper(child:Page( text: '$i')); 47 | } 48 | return PageView(children: children); 49 | } 50 | */ -------------------------------------------------------------------------------- /lib/pages/common/loading_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class LoadingView extends StatelessWidget { 4 | final String? _message; 5 | 6 | const LoadingView({Key? key, String? message}) : 7 | _message = message, 8 | super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Padding( 13 | padding: const EdgeInsets.all(10.0), 14 | child: Center( 15 | child: Column( 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | children: [ 18 | const CupertinoActivityIndicator(radius: 20), 19 | const SizedBox( 20 | height: 10.0, 21 | ), 22 | Text(_message ?? '正在加载...'), 23 | ], 24 | ), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/pages/common/my_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /* 4 | 比 Flutter ListView 更灵活的布局方式 5 | 链接:https://juejin.cn/post/7184955986224873531 6 | */ 7 | class MyListView extends ScrollView { 8 | const MyListView( 9 | {Key? key, 10 | this.banner, 11 | required this.itemBuilder, 12 | required this.itemCount, 13 | this.itemExtent, 14 | Key? center}) 15 | : super(key: key, center: center); 16 | 17 | final Widget? banner; 18 | final IndexedWidgetBuilder itemBuilder; 19 | final double? itemExtent; 20 | final int itemCount; 21 | 22 | @override 23 | List buildSlivers(BuildContext context) { 24 | List list = []; 25 | if (banner != null) { 26 | list.add(SliverToBoxAdapter(child: banner!)); 27 | } 28 | if (center == null) { 29 | if (itemExtent == null) { 30 | list.add( 31 | SliverList( 32 | delegate: 33 | SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), 34 | ), 35 | ); 36 | } else { 37 | list.add( 38 | SliverFixedExtentList( 39 | delegate: 40 | SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), 41 | itemExtent: itemExtent!, 42 | ), 43 | ); 44 | } 45 | } else { 46 | for (var i = 0; i < itemCount; i++) { 47 | list.add( 48 | SliverToBoxAdapter( 49 | key: ValueKey(i), 50 | child: itemBuilder(context, i), 51 | ), 52 | ); 53 | } 54 | } 55 | return list; 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /lib/pages/common/refresh_header_footer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 4 | 5 | class RefreshHeader extends StatelessWidget { 6 | const RefreshHeader({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return const WaterDropHeader( 11 | complete: Center( 12 | child: Text("下拉刷新完成"), 13 | ), 14 | ); 15 | } 16 | } 17 | 18 | class RefreshFooter extends StatelessWidget { 19 | const RefreshFooter({Key? key}) : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return CustomFooter( 24 | builder: (context, mode) { 25 | return Center(child: mode?.statusBody); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | extension on LoadStatus { 32 | Widget get statusBody { 33 | switch (this) { 34 | case LoadStatus.idle: 35 | return const Text("上拉加载"); 36 | case LoadStatus.canLoading: 37 | return const Text("松手,加载更多!"); 38 | case LoadStatus.loading: 39 | return const CupertinoActivityIndicator(); 40 | case LoadStatus.noMore: 41 | return const Text("没有更多数据了!"); 42 | case LoadStatus.failed: 43 | return Container(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/pages/common/shimmer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // Most Basic Shimmer 4 | class Shimmer extends StatefulWidget { 5 | const Shimmer({ 6 | super.key, 7 | this.width, 8 | this.height, 9 | this.minOpacity = 0.015, 10 | this.maxOpacity = 0.15, 11 | this.borderRadius = const BorderRadius.all(Radius.circular(4)), 12 | this.child, 13 | }); 14 | 15 | final double? width; 16 | final double? height; 17 | final double minOpacity; 18 | final double maxOpacity; 19 | final BorderRadius? borderRadius; 20 | final Widget? child; 21 | 22 | @override 23 | State createState() => _ShimmerState(); 24 | } 25 | 26 | class _ShimmerState extends State with SingleTickerProviderStateMixin { 27 | late final AnimationController controller; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | controller = AnimationController( 33 | vsync: this, 34 | duration: const Duration(seconds: 1), 35 | lowerBound: widget.minOpacity, 36 | upperBound: widget.maxOpacity, 37 | )..repeat(reverse: true); 38 | } 39 | 40 | @override 41 | void dispose() { 42 | controller.dispose(); 43 | super.dispose(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return RepaintBoundary( 49 | child: FadeTransition( 50 | opacity: controller, 51 | child: Container( 52 | width: widget.width, 53 | height: widget.height, 54 | decoration: BoxDecoration( 55 | color: Colors.black, 56 | borderRadius: widget.borderRadius, 57 | ), 58 | child: widget.child, 59 | ), 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/pages/common/unknown_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class UnknownPage extends StatelessWidget { 4 | const UnknownPage({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return const CupertinoPageScaffold( 9 | navigationBar: CupertinoNavigationBar( 10 | middle: Text("未知页面"), 11 | ), 12 | child: Center( 13 | child: Text( 14 | "未知页面", 15 | style: TextStyle(fontSize: 20), 16 | ), 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/pages/home/binding/home_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:getx_study/base/class_name.dart'; 3 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 4 | 5 | import 'package:getx_study/pages/home/controller/home_controller.dart'; 6 | import 'package:getx_study/pages/home/repository/home_repository.dart'; 7 | 8 | class HomeBinding extends Bindings { 9 | @override 10 | void dependencies() { 11 | Get.lazyPut( 12 | () => HomeRepository(), 13 | ); 14 | 15 | /// 需要通过tag来进行区分,避免RefreshController反复使用导致的内存泄露与崩溃 16 | /// 其实后来想想,其实把page与RefreshController在这里进行put,直接在onInit里面初始化反而逻辑更加简单 17 | Get.lazyPut( 18 | tag: className(HomeController), 19 | () => RefreshController(initialRefresh: true), 20 | ); 21 | Get.lazyPut( 22 | tag: className(HomeController), 23 | () => 1, 24 | ); 25 | Get.lazyPut( 26 | () => HomeController(), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/pages/home/binding/hot_key_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'package:getx_study/pages/home/repository/hot_key_repository.dart'; 4 | import 'package:getx_study/pages/home/controller/hot_key_controller.dart'; 5 | 6 | class HotKeyBinding extends Bindings { 7 | @override 8 | void dependencies() { 9 | Get.lazyPut( 10 | () => HotKeyRepository(), 11 | ); 12 | Get.lazyPut( 13 | () => HotKeyController(), 14 | ); 15 | } 16 | } -------------------------------------------------------------------------------- /lib/pages/home/binding/search_result_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/class_name.dart'; 2 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import 'package:getx_study/pages/home/controller/search_result_controller.dart'; 6 | import 'package:getx_study/pages/home/repository/search_result_repository.dart'; 7 | 8 | class SearchResultBinding extends Bindings { 9 | @override 10 | void dependencies() { 11 | Get.lazyPut( 12 | () => SearchResultRepository(), 13 | ); 14 | Get.lazyPut( 15 | tag: className(SearchResultController), 16 | () => RefreshController(initialRefresh: true), 17 | ); 18 | Get.lazyPut( 19 | tag: className(SearchResultController), 20 | () => 1, 21 | ); 22 | Get.lazyPut( 23 | () => SearchResultController(), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/pages/home/controller/hot_key_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/base_request_controller.dart'; 2 | import 'package:getx_study/enum/response_status.dart'; 3 | import 'package:getx_study/entity/hot_key_entity.dart'; 4 | import 'package:getx_study/pages/home/repository/hot_key_repository.dart'; 5 | 6 | class HotKeyController 7 | extends BaseRequestController> { 8 | @override 9 | void onInit() async { 10 | super.onInit(); 11 | aRequest(); 12 | } 13 | 14 | @override 15 | Future aRequest({Map? parameters}) async { 16 | response = await request.getHotKey().catchError((error) { 17 | status = ResponseStatus.fail; 18 | update(); 19 | return error; 20 | }); 21 | data = response?.data ?? []; 22 | status = response?.responseStatus ?? ResponseStatus.loading; 23 | update(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/pages/home/controller/search_result_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'package:getx_study/base/base_refresh_controller.dart'; 4 | import 'package:getx_study/enum/response_status.dart'; 5 | import 'package:getx_study/entity/article_info_entity.dart'; 6 | import 'package:getx_study/base/class_name.dart'; 7 | import 'package:getx_study/pages/home/repository/search_result_repository.dart'; 8 | import 'package:getx_study/enum/scroll_view_action_type.dart'; 9 | 10 | class SearchResultController 11 | extends BaseRefreshController { 12 | @override 13 | void onInit() { 14 | super.onInit(); 15 | initPage = Get.find(tag: className(SearchResultController)); 16 | page = initPage; 17 | refreshController = Get.find(tag: className(SearchResultController)); 18 | } 19 | 20 | @override 21 | Future onRefresh() async { 22 | page = initPage; 23 | await aRequest(type: ScrollViewActionType.refresh); 24 | } 25 | 26 | @override 27 | Future onLoadMore() async { 28 | page = page + 1; 29 | await aRequest(type: ScrollViewActionType.loadMore); 30 | } 31 | 32 | @override 33 | Future aRequest({ 34 | required ScrollViewActionType type, 35 | Map? parameters, 36 | }) async { 37 | String keyword = Get.arguments; 38 | 39 | response = await request.searchKeyword(page: page, keyword: keyword).catchError((error) { 40 | return processError(type: type, error: error); 41 | }); 42 | status = response?.responseStatus ?? ResponseStatus.loading; 43 | 44 | final models = response?.data?.dataSource ?? []; 45 | 46 | switch (type) { 47 | case ScrollViewActionType.refresh: 48 | dataSource.clear(); 49 | dataSource.addAll(models); 50 | break; 51 | case ScrollViewActionType.loadMore: 52 | dataSource.addAll(models); 53 | break; 54 | } 55 | 56 | refreshControllerStatusUpdate(type); 57 | 58 | update(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/pages/home/repository/home_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/entity/article_info_entity.dart'; 3 | import 'package:getx_study/entity/base_entity.dart'; 4 | import 'package:getx_study/entity/banner_entity.dart'; 5 | import 'package:getx_study/entity/page_entity.dart'; 6 | import 'package:getx_study/http_util/request.dart' as http; 7 | import 'package:getx_study/http_util/api.dart'; 8 | import 'package:getx_study/http_client/request_client.dart'; 9 | 10 | class HomeRepository extends IRepository { 11 | Future>> getBanner() => 12 | http.Request.get(api: Api.getBanner); 13 | 14 | Future>> getTopArticleList() => 15 | http.Request.get(api: Api.getTopArticleList); 16 | 17 | Future>>> getArticleList( 18 | {required int page}) => 19 | http.Request.get(api: "${Api.getArticleList}${page.toString()}/json"); 20 | } 21 | 22 | /* 瞬间觉得自己写的HomeRepository不香了 23 | class HomeRepository extends IRepository { 24 | Future>> getBanner() => 25 | requestClient.getBanner(); 26 | 27 | Future>> getTopArticleList() => 28 | requestClient.getTopArticleList(); 29 | 30 | Future>>> getArticleList( 31 | {required int page}) => 32 | requestClient.getArticleList(page); 33 | } 34 | */ -------------------------------------------------------------------------------- /lib/pages/home/repository/hot_key_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/entity/base_entity.dart'; 3 | import 'package:getx_study/entity/hot_key_entity.dart'; 4 | import 'package:getx_study/http_util/request.dart' as http; 5 | import 'package:getx_study/http_util/api.dart'; 6 | 7 | class HotKeyRepository extends IRepository { 8 | Future>> getHotKey() => 9 | http.Request.get(api: Api.getSearchHotKey); 10 | } 11 | -------------------------------------------------------------------------------- /lib/pages/home/repository/search_result_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/entity/base_entity.dart'; 3 | import 'package:getx_study/entity/article_info_entity.dart'; 4 | import 'package:getx_study/entity/page_entity.dart'; 5 | import 'package:getx_study/http_util/request.dart' as http; 6 | import 'package:getx_study/http_util/api.dart'; 7 | 8 | class SearchResultRepository extends IRepository { 9 | Future>>> searchKeyword( 10 | {required int page, required String keyword}) async { 11 | final params = {}; 12 | params["k"] = keyword; 13 | return await http.Request.post( 14 | api: "${Api.postQueryKey}${page.toString()}/json", params: params); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/pages/home/view/search_result_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | import 'package:getx_study/pages/common/info_cell.dart'; 4 | import 'package:getx_study/pages/common/refresh_header_footer.dart'; 5 | import 'package:getx_study/pages/common/status_view.dart'; 6 | import 'package:getx_study/pages/home/controller/search_result_controller.dart'; 7 | import 'package:getx_study/routes/routes.dart'; 8 | 9 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 10 | import 'package:get/get.dart'; 11 | 12 | class SearchResultPage extends GetView { 13 | const SearchResultPage({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | String title = Get.arguments; 18 | return CupertinoPageScaffold( 19 | navigationBar: CupertinoNavigationBar( 20 | middle: Text(title), 21 | ), 22 | child: StatusView( 23 | contentBuilder: (_) { 24 | return SmartRefresher( 25 | enablePullUp: true, 26 | header: const RefreshHeader(), 27 | footer: const RefreshFooter(), 28 | controller: controller.refreshController, 29 | onRefresh: controller.onRefresh, 30 | onLoading: controller.onLoadMore, 31 | child: ListView.builder( 32 | shrinkWrap: true, 33 | itemCount: controller.dataSource.length, 34 | itemBuilder: (BuildContext context, int index) { 35 | final model = controller.dataSource[index]; 36 | return InfoCell( 37 | model: model, 38 | callback: (_) => Get.toNamed(Routes.web, arguments: model), 39 | ); 40 | }, 41 | ), 42 | ); 43 | }, 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/pages/launch/splash_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | import 'package:getx_study/generated/assets.dart'; 5 | 6 | import 'package:getx_study/routes/routes.dart'; 7 | import 'package:getx_study/pages/common/countdown_circle.dart'; 8 | 9 | /// 模拟的一个广告页面 10 | class SplashPage extends StatelessWidget { 11 | 12 | const SplashPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Stack( 17 | alignment: AlignmentDirectional.bottomEnd, 18 | children: [ 19 | Container( 20 | decoration: const BoxDecoration( 21 | //设置背景图片 22 | image: DecorationImage( 23 | image: AssetImage(Assets.assetsImagesLaunchImage), 24 | fit: BoxFit.cover, 25 | ), 26 | ), 27 | ), 28 | Positioned( 29 | right: 20, 30 | top: 88, 31 | child: CountdownCircle(finished: (byUserClick) { 32 | Get.offAllNamed(Routes.main); 33 | },), 34 | ), 35 | ], 36 | ); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /lib/pages/launch/welcome_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | import 'package:getx_study/app_service/account_service.dart'; 6 | import 'package:getx_study/generated/assets.dart'; 7 | import 'package:getx_study/routes/routes.dart'; 8 | 9 | class WelcomePage extends StatelessWidget { 10 | const WelcomePage({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | body: PageView( 16 | physics: const ClampingScrollPhysics(), 17 | children: [ 18 | _welcomeView(Assets.assetsImagesWelcome1, false), 19 | _welcomeView(Assets.assetsImagesWelcome2, true), 20 | ], 21 | ), 22 | ); 23 | } 24 | 25 | Widget _welcomeView(String imageName, bool visible) { 26 | return Stack( 27 | alignment: AlignmentDirectional.bottomEnd, 28 | children: [ 29 | Container( 30 | decoration: BoxDecoration( 31 | //设置背景图片 32 | image: DecorationImage( 33 | image: AssetImage(imageName), 34 | fit: BoxFit.cover, 35 | ), 36 | ), 37 | ), 38 | Visibility( 39 | visible: visible, 40 | child: Positioned( 41 | left: 20, 42 | right: 20, 43 | bottom: 44, 44 | child: TextButton( 45 | style: ButtonStyle( 46 | backgroundColor: MaterialStateProperty.all(Colors.grey), 47 | foregroundColor: MaterialStateProperty.all(Colors.white), 48 | shape: MaterialStateProperty.all( 49 | const RoundedRectangleBorder( 50 | side: BorderSide.none, 51 | borderRadius: BorderRadius.all( 52 | Radius.circular(20), 53 | ), 54 | ), 55 | ), 56 | ), 57 | child: const Text("点击进入"), 58 | onPressed: () { 59 | AccountService.find.saveNotFirstLaunch(); 60 | Get.offAllNamed(Routes.main); 61 | }, 62 | ), 63 | ), 64 | ) 65 | ], 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/pages/main/bindings/main_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'package:getx_study/pages/main/controller/main_controller.dart'; 4 | 5 | class MainBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => MainController(), 10 | ); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/pages/main/controller/main_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:getx_study/logger/logger.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | class MainController extends GetxController { 6 | var selectedIndex = 0; 7 | 8 | void onItemTapped(int index) { 9 | selectedIndex = index; 10 | update(); 11 | 12 | //getFindTest(); 13 | } 14 | 15 | void getFindTest() { 16 | /// Get.putAsyn使用的时候要稍微注意,避免先find后put 17 | final prefs = Get.find(); 18 | int? count = prefs.getInt('counter'); 19 | logger.d(count); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/pages/main/view/main_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:get/get.dart'; 5 | import 'package:get/get_state_manager/get_state_manager.dart'; 6 | import 'package:getx_study/app_service/theme_service.dart'; 7 | import 'package:getx_study/enum/main_tag_type.dart'; 8 | import 'package:getx_study/pages/main/controller/main_controller.dart'; 9 | import 'package:getx_study/enum/theme_type.dart'; 10 | 11 | class MainPage extends GetView { 12 | const MainPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return GetBuilder( 17 | builder: ((controller) { 18 | return CupertinoTabScaffold( 19 | tabBuilder: (context, index) { 20 | final type = MainTagType.values[index]; 21 | return CupertinoTabView(builder: (context) { 22 | return type.page; 23 | }); 24 | }, 25 | tabBar: CupertinoTabBar( 26 | items: MainTagTypeExt.items, 27 | /// 这个地方目前这样写无法感知到变化,于是我使用了全局的GetMaterialController这个库来进行App的重启 28 | backgroundColor: ThemeService.find.rxCurrentThemeType.value == ThemeType.dark ? Colors.black : Colors.white, 29 | currentIndex: controller.selectedIndex, //默认选中的 index 30 | onTap: controller.onItemTapped, 31 | ), 32 | ); 33 | }), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/pages/my/binding/login_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:getx_study/pages/my/controller/login_controller.dart'; 3 | 4 | class LoginBinding extends Bindings { 5 | @override 6 | void dependencies() { 7 | Get.lazyPut( 8 | () => LoginController(), 9 | // fenix: true, 10 | ); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/pages/my/binding/my_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'package:getx_study/pages/my/controller/my_controller.dart'; 4 | import 'package:getx_study/pages/my/repository/my_repository.dart'; 5 | 6 | class MyBinding extends Bindings { 7 | @override 8 | void dependencies() { 9 | Get.lazyPut( 10 | fenix: true, 11 | () => MyRepository(), 12 | ); 13 | Get.lazyPut( 14 | () => MyController(), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/pages/my/binding/my_coin_history_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/class_name.dart'; 2 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import 'package:getx_study/pages/my/controller/my_coin_history_controller.dart'; 6 | import 'package:getx_study/pages/my/repository/my_coin_history_repository.dart'; 7 | 8 | class MyCoinHistoryBinding extends Bindings { 9 | @override 10 | void dependencies() { 11 | Get.lazyPut( 12 | () => MyCoinHistoryRepository(), 13 | ); 14 | 15 | /// 需要通过tag来进行区分,避免RefreshController反复使用导致的内存泄露与崩溃 16 | Get.lazyPut( 17 | tag: className(MyCoinHistoryController), 18 | () => RefreshController(initialRefresh: true), 19 | ); 20 | Get.lazyPut( 21 | tag: className(MyCoinHistoryController), 22 | () => 1, 23 | ); 24 | Get.lazyPut( 25 | () => MyCoinHistoryController(), 26 | ); 27 | } 28 | } -------------------------------------------------------------------------------- /lib/pages/my/binding/my_collect_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/class_name.dart'; 2 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import 'package:getx_study/pages/my/controller/my_collect_controller.dart'; 6 | import 'package:getx_study/pages/my/repository/my_collect_repository.dart'; 7 | 8 | class MyCollectBinding extends Bindings { 9 | @override 10 | void dependencies() { 11 | Get.lazyPut( 12 | () => MyCollectRepository(), 13 | ); 14 | Get.lazyPut( 15 | tag: className(MyCollectController), 16 | () => RefreshController(initialRefresh: true), 17 | ); 18 | Get.lazyPut( 19 | tag: className(MyCollectController), 20 | () => 0, 21 | ); 22 | Get.lazyPut( 23 | () => MyCollectController(), 24 | ); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/pages/my/binding/register_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'package:getx_study/pages/my/controller/register_controller.dart'; 4 | 5 | class RegisterBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => RegisterController(), 10 | ); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/pages/my/controller/get_user_info_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/app_service/account_service.dart'; 2 | import 'package:getx_study/base/base_request_controller.dart'; 3 | import 'package:getx_study/entity/account_info_entity.dart'; 4 | import 'package:getx_study/pages/my/repository/my_repository.dart'; 5 | 6 | mixin GetUserInfoMixin 7 | on BaseRequestController { 8 | var userInfo = "等级 -- 排名 -- 积分 --"; 9 | 10 | Future getUserCoinInfo() async { 11 | final response = await request.getUserCoinInfo(); 12 | final userInfo = 13 | "等级 ${response.data?.level ?? "--"} 排名 ${response.data?.rank ?? "--"} 积分 ${response.data?.coinCount ?? "--"}"; 14 | this.userInfo = userInfo; 15 | AccountService.find.userInfo = userInfo; 16 | return userInfo; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/pages/my/controller/my_coin_history_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'package:getx_study/base/base_refresh_controller.dart'; 4 | import 'package:getx_study/entity/my_coin_history_entity.dart'; 5 | import 'package:getx_study/enum/response_status.dart'; 6 | import 'package:getx_study/enum/scroll_view_action_type.dart'; 7 | import 'package:getx_study/base/class_name.dart'; 8 | import 'package:getx_study/pages/my/repository/my_coin_history_repository.dart'; 9 | 10 | class MyCoinHistoryController 11 | extends BaseRefreshController { 12 | @override 13 | void onInit() { 14 | super.onInit(); 15 | initPage = Get.find(tag: className(MyCoinHistoryController)); 16 | page = initPage; 17 | refreshController = Get.find(tag: className(MyCoinHistoryController)); 18 | } 19 | 20 | @override 21 | Future onRefresh() async { 22 | page = initPage; 23 | await aRequest(type: ScrollViewActionType.refresh); 24 | } 25 | 26 | @override 27 | Future onLoadMore() async { 28 | page = page + 1; 29 | await aRequest(type: ScrollViewActionType.loadMore); 30 | } 31 | 32 | @override 33 | Future aRequest({ 34 | required ScrollViewActionType type, 35 | Map? parameters, 36 | }) async { 37 | response = await request.getCoinRankList(page).catchError((error) { 38 | return processError(type: type, error: error); 39 | }); 40 | status = response?.responseStatus ?? ResponseStatus.loading; 41 | 42 | final models = response?.data?.dataSource ?? []; 43 | 44 | switch (type) { 45 | case ScrollViewActionType.refresh: 46 | dataSource.clear(); 47 | dataSource.addAll(models); 48 | break; 49 | case ScrollViewActionType.loadMore: 50 | dataSource.addAll(models); 51 | break; 52 | } 53 | 54 | refreshControllerStatusUpdate(type); 55 | 56 | update(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/pages/my/controller/my_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'package:getx_study/app_service/account_service.dart'; 4 | import 'package:getx_study/base/base_request_controller.dart'; 5 | import 'package:getx_study/entity/account_info_entity.dart'; 6 | import 'package:getx_study/pages/my/controller/get_user_info_mixin.dart'; 7 | import 'package:getx_study/pages/my/repository/my_repository.dart'; 8 | import 'package:getx_study/logger/logger.dart'; 9 | 10 | class MyController 11 | extends BaseRequestController 12 | with GetUserInfoMixin { 13 | /* 14 | 为了避免这种问题,_observable_的第一次变化将总是触发一个事件,即使它包含相同的.value。 15 | 如果你想删除这种行为,你可以使用: isLogin.firstRebuild = false;。 16 | */ 17 | final isLogin = AccountService.find.isLogin.obs; 18 | 19 | final rxUserInfo = AccountService.find.userInfo.obs; 20 | 21 | @override 22 | void onInit() { 23 | super.onInit(); 24 | logger.d("onInit"); 25 | } 26 | 27 | Future logout() async { 28 | final response = await request.logout(); 29 | String message; 30 | if (response.isSuccess) { 31 | message = "登出成功"; 32 | AccountService.find.clear(); 33 | } else { 34 | message = "登出失败"; 35 | } 36 | Get.snackbar( 37 | "", 38 | message, 39 | duration: const Duration(seconds: 1), 40 | ); 41 | return AccountService.find.isLogin; 42 | } 43 | 44 | Future autoLogin() async { 45 | final username = await AccountService.find.getLastLoginUserName(); 46 | final password = await AccountService.find.getLastLoginPassword(); 47 | 48 | if (username.isNotEmpty && password.isNotEmpty) { 49 | final response = 50 | await request.login(username: username, password: password); 51 | 52 | String message; 53 | if (response.isSuccess == true && response.data != null) { 54 | await AccountService.find 55 | .save(info: response.data!, isLogin: true, password: password); 56 | message = "自动登录成功"; 57 | 58 | await getUserCoinInfo(); 59 | 60 | isLogin.value = AccountService.find.isLogin; 61 | rxUserInfo.value = AccountService.find.userInfo; 62 | } else { 63 | message = "自动登录失败"; 64 | } 65 | Get.snackbar( 66 | "", 67 | message, 68 | duration: const Duration(seconds: 1), 69 | ); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/pages/my/controller/register_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | import 'package:getx_study/app_service/account_service.dart'; 6 | import 'package:getx_study/base/resign_first_responder.dart'; 7 | import 'package:getx_study/pages/my/controller/login_controller.dart'; 8 | import 'package:getx_study/logger/logger.dart'; 9 | 10 | class RegisterController extends LoginController { 11 | final rePasswordTextFiledController = TextEditingController(text: ""); 12 | 13 | final password = "".obs; 14 | 15 | final rePassword = "".obs; 16 | 17 | final reObscureText = true.obs; 18 | 19 | final rePasswordFocusNode = FocusNode(); 20 | 21 | @override 22 | void onInit() { 23 | super.onInit(); 24 | logger.d("onInit"); 25 | } 26 | 27 | void register( 28 | {required String username, 29 | required String password, 30 | required String rePassword}) async { 31 | ResignFirstResponder.unfocus(); 32 | final response = await request.register( 33 | username: username, password: password, rePassword: rePassword); 34 | 35 | String message; 36 | if (response.isSuccess == true && response.data != null) { 37 | await AccountService.find 38 | .save(info: response.data!, isLogin: true, password: password); 39 | await getUserCoinInfo(); 40 | message = "注册成功"; 41 | } else { 42 | message = "注册失败"; 43 | } 44 | Get.snackbar( 45 | "", 46 | message, 47 | duration: const Duration(seconds: 1), 48 | snackbarStatus: (status) { 49 | if (status == SnackbarStatus.CLOSED) { 50 | if (response.isSuccess) { 51 | Future.delayed( 52 | const Duration(seconds: 0), 53 | () => navigator?.pop(AccountService.find.isLogin), 54 | ); 55 | } 56 | } 57 | }, 58 | ); 59 | } 60 | 61 | void passwordTextFieldOnSubmitted(BuildContext context) { 62 | passwordFocusNode.unfocus(); 63 | FocusScope.of(context).requestFocus(rePasswordFocusNode); 64 | } 65 | 66 | @override 67 | void onClose() { 68 | super.onClose(); 69 | rePasswordFocusNode.dispose(); 70 | } 71 | 72 | bool get isShowRegisterButton => 73 | (userNameIsNotEmpty.value && 74 | password.value.isNotEmpty && 75 | rePassword.value.isNotEmpty) && 76 | (password == rePassword); 77 | } 78 | -------------------------------------------------------------------------------- /lib/pages/my/repository/my_coin_history_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/entity/base_entity.dart'; 3 | import 'package:getx_study/entity/my_coin_history_entity.dart'; 4 | import 'package:getx_study/entity/page_entity.dart'; 5 | import 'package:getx_study/http_util/request.dart' as http; 6 | import 'package:getx_study/http_util/api.dart'; 7 | 8 | class MyCoinHistoryRepository extends IRepository { 9 | Future>>> getCoinRankList( 10 | int page) => 11 | http.Request.get(api: "${Api.getCoinList}${page.toString()}/json"); 12 | } -------------------------------------------------------------------------------- /lib/pages/my/repository/my_collect_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/entity/base_entity.dart'; 3 | import 'package:getx_study/entity/article_info_entity.dart'; 4 | import 'package:getx_study/entity/page_entity.dart'; 5 | import 'package:getx_study/http_util/request.dart' as http; 6 | import 'package:getx_study/http_util/api.dart'; 7 | 8 | class MyCollectRepository extends IRepository { 9 | Future>>> getCollectArticleList( 10 | {required int page}) => 11 | http.Request.get( 12 | api: "${Api.getCollectArticleList}${page.toString()}/json"); 13 | 14 | Future> unCollectAction({required int originId}) => 15 | http.Request.post( 16 | api: "${Api.postUnCollectArticle}${originId.toString()}/json"); 17 | } 18 | -------------------------------------------------------------------------------- /lib/pages/my/repository/my_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/entity/base_entity.dart'; 3 | import 'package:getx_study/entity/coin_rank_entity.dart'; 4 | 5 | import 'package:getx_study/http_util/request.dart' as http; 6 | import 'package:getx_study/http_util/api.dart'; 7 | import 'package:getx_study/entity/account_info_entity.dart'; 8 | 9 | class MyRepository extends IRepository { 10 | Future> login( 11 | {required String username, required String password}) async { 12 | final params = {}; 13 | params["username"] = username; 14 | params["password"] = password; 15 | return await http.Request.post(api: Api.postLogin, params: params); 16 | } 17 | 18 | Future> register( 19 | {required String username, 20 | required String password, 21 | required String rePassword}) async { 22 | final params = {}; 23 | params["username"] = username; 24 | params["password"] = password; 25 | params["repassword"] = rePassword; 26 | return await http.Request.post(api: Api.postRegister, params: params); 27 | } 28 | 29 | Future> logout() => http.Request.get(api: Api.getLogout); 30 | 31 | Future> getUserCoinInfo() => http.Request.get(api: Api.getUserCoinInfo); 32 | } 33 | -------------------------------------------------------------------------------- /lib/pages/my/view/my_coin_history_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:getx_study/pages/common/status_view.dart'; 4 | import 'package:getx_study/pages/my/controller/my_coin_history_controller.dart'; 5 | import 'package:getx_study/routes/routes.dart'; 6 | import 'package:getx_study/pages/common/refresh_header_footer.dart'; 7 | 8 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 9 | import 'package:get/get.dart'; 10 | 11 | class MyCoinHistoryPage extends GetView { 12 | const MyCoinHistoryPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return CupertinoPageScaffold( 17 | navigationBar: const CupertinoNavigationBar( 18 | middle: Text("我的积分"), 19 | ), 20 | child: StatusView( 21 | contentBuilder: (controller) { 22 | return SmartRefresher( 23 | enablePullUp: true, 24 | header: const RefreshHeader(), 25 | footer: const RefreshFooter(), 26 | controller: controller.refreshController, 27 | onRefresh: controller.onRefresh, 28 | onLoading: controller.onLoadMore, 29 | child: ListView.builder( 30 | shrinkWrap: true, 31 | itemCount: controller.dataSource.length, 32 | itemBuilder: (BuildContext context, int index) { 33 | final model = controller.dataSource[index]; 34 | 35 | return ListTile( 36 | leading: Text(model.reason.toString()), 37 | title: Text(model.desc.toString()), 38 | trailing: Text(model.coinCount.toString()), 39 | onTap: () => Get.toNamed(Routes.stateMixinExample), 40 | ); 41 | }, 42 | ), 43 | ); 44 | }, 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/pages/my/view/theme_setting_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:get/get.dart'; 5 | 6 | import 'package:getx_study/enum/theme_type.dart'; 7 | import 'package:getx_study/app_service/theme_service.dart'; 8 | 9 | class ThemeSettingPage extends StatelessWidget { 10 | const ThemeSettingPage({Key? key}) : super(key: key); 11 | 12 | final dataSource = ThemeType.values; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final themeService = ThemeService.find; 17 | 18 | return CupertinoPageScaffold( 19 | navigationBar: CupertinoNavigationBar( 20 | middle: Text("theme_setting".tr), 21 | ), 22 | child: ListView.separated( 23 | itemBuilder: (context, index) { 24 | return ListTile( 25 | title: Text(dataSource[index].title), 26 | trailing: const Icon(Icons.arrow_forward_ios), 27 | onTap: () async { 28 | themeService.switchTheme(dataSource[index]); 29 | /// 尝试使用国际化 30 | if (index == 0) { 31 | var locale = const Locale('en', 'US'); 32 | Get.updateLocale(locale); 33 | } else if (index == 1) { 34 | var locale = const Locale('zh', 'CN'); 35 | Get.updateLocale(locale); 36 | } else if (index == 2) { 37 | var locale = const Locale('fr', 'FR'); 38 | Get.updateLocale(locale); 39 | } else { 40 | 41 | } 42 | }); 43 | }, 44 | separatorBuilder: (context, index) { 45 | return const Divider( 46 | indent: 15, 47 | height: 0.5, 48 | ); 49 | }, 50 | itemCount: dataSource.length), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/pages/tree/bindings/tabs_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'package:getx_study/enum/tag_type.dart'; 4 | import 'package:getx_study/pages/tree/controller/tabs_controller.dart'; 5 | import 'package:getx_study/pages/tree/repository/tab_list_repository.dart'; 6 | import 'package:getx_study/pages/tree/repository/tabs_repository.dart'; 7 | 8 | class TabsBinding extends Bindings { 9 | TabsBinding(this.type); 10 | 11 | TagType type; 12 | 13 | @override 14 | void dependencies() { 15 | Get.lazyPut( 16 | () => TabsRepository(type), 17 | tag: type.toString() 18 | ); 19 | Get.lazyPut( 20 | () => TabsController(type), 21 | tag: type.toString() 22 | ); 23 | Get.lazyPut( 24 | () => TabListRepository(), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/pages/tree/controller/tab_list_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/base_refresh_controller.dart'; 2 | import 'package:getx_study/enum/response_status.dart'; 3 | import 'package:getx_study/entity/article_info_entity.dart'; 4 | import 'package:getx_study/enum/scroll_view_action_type.dart'; 5 | import 'package:getx_study/enum/tag_type.dart'; 6 | import 'package:getx_study/pages/tree/repository/tab_list_repository.dart'; 7 | 8 | class TabListController 9 | extends BaseRefreshController { 10 | 11 | late TagType tagType; 12 | 13 | late String id; 14 | 15 | @override 16 | Future onRefresh() async { 17 | page = initPage; 18 | await aRequest(type: ScrollViewActionType.refresh); 19 | } 20 | 21 | @override 22 | Future onLoadMore() async { 23 | page = page + 1; 24 | await aRequest(type: ScrollViewActionType.loadMore); 25 | } 26 | 27 | @override 28 | Future aRequest({ 29 | required ScrollViewActionType type, 30 | Map? parameters, 31 | }) async { 32 | 33 | response = await request.getList(page: page, id: id, tagType: tagType).catchError((error) { 34 | return processError(type: type, error: error); 35 | }); 36 | status = response?.responseStatus ?? ResponseStatus.loading; 37 | 38 | final models = response?.data?.dataSource ?? []; 39 | 40 | switch (type) { 41 | case ScrollViewActionType.refresh: 42 | dataSource.clear(); 43 | dataSource.addAll(models); 44 | break; 45 | case ScrollViewActionType.loadMore: 46 | dataSource.addAll(models); 47 | break; 48 | } 49 | 50 | refreshControllerStatusUpdate(type); 51 | 52 | update(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/pages/tree/controller/tabs_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:get/get_state_manager/get_state_manager.dart'; 3 | 4 | import 'package:getx_study/enum/tag_type.dart'; 5 | import 'package:getx_study/base/base_request_controller.dart'; 6 | import 'package:getx_study/entity/tab_entity.dart'; 7 | import 'package:getx_study/enum/response_status.dart'; 8 | import 'package:getx_study/pages/tree/repository/tabs_repository.dart'; 9 | import 'package:getx_study/logger/logger.dart'; 10 | 11 | class TabsController 12 | extends BaseRequestController> with ScrollMixin { 13 | TabsController(this.type); 14 | 15 | TagType type; 16 | 17 | @override 18 | void onInit() async { 19 | super.onInit(); 20 | request = Get.find(tag: type.toString()); 21 | aRequest(); 22 | } 23 | 24 | @override 25 | Future aRequest({Map? parameters}) async { 26 | response = await request.getTab().catchError((error) { 27 | status = ResponseStatus.fail; 28 | update(); 29 | return error; 30 | }); 31 | data = response?.data ?? []; 32 | status = response?.responseStatus ?? ResponseStatus.loading; 33 | update(); 34 | } 35 | 36 | @override 37 | Future onTopScroll() async { 38 | logger.d("滑到了顶部"); 39 | } 40 | 41 | @override 42 | Future onEndScroll() async { 43 | logger.d("滑到了底部"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/pages/tree/repository/tab_list_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/entity/base_entity.dart'; 3 | import 'package:getx_study/entity/article_info_entity.dart'; 4 | import 'package:getx_study/entity/page_entity.dart'; 5 | import 'package:getx_study/enum/tag_type.dart'; 6 | import 'package:getx_study/http_util/request.dart' as http; 7 | import 'package:getx_study/http_util/api.dart'; 8 | 9 | class TabListRepository extends IRepository { 10 | Future>>> getList( 11 | {required int page, required String id, required TagType tagType}) async { 12 | switch (tagType) { 13 | case TagType.project: 14 | final params = {}; 15 | params["cid"] = id.toString(); 16 | final api = "${Api.getProjectClassifyList}${page.toString()}/json"; 17 | return await http.Request.get(api: api, params: params); 18 | case TagType.publicNumber: 19 | final api = 20 | "${Api.getPublicNumberList}${id.toString()}/${page.toString()}/json"; 21 | return await http.Request.get(api: api); 22 | case TagType.tree: 23 | return BaseEntity>>(null, null, null); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/pages/tree/repository/tabs_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/enum/tag_type.dart'; 3 | import 'package:getx_study/entity/base_entity.dart'; 4 | import 'package:getx_study/entity/tab_entity.dart'; 5 | import 'package:getx_study/http_util/request.dart' as http; 6 | 7 | class TabsRepository extends IRepository { 8 | TabsRepository(this.type); 9 | 10 | TagType type; 11 | 12 | Future>> getTab() => 13 | http.Request.get(api: type.tabApi); 14 | } 15 | -------------------------------------------------------------------------------- /lib/pages/tree/view/tab_list_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | import 'package:get/get.dart'; 5 | import 'package:getx_study/pages/common/empty_view.dart'; 6 | import 'package:getx_study/pages/common/refresh_header_footer.dart'; 7 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 8 | 9 | import 'package:getx_study/pages/tree/controller/tab_list_controller.dart'; 10 | import 'package:getx_study/pages/common/info_cell.dart'; 11 | import 'package:getx_study/routes/routes.dart'; 12 | 13 | class TabListPage extends StatelessWidget { 14 | final TabListController _controller; 15 | 16 | const TabListPage({Key? key, required TabListController controller}) 17 | : _controller = controller, 18 | super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return GetBuilder( 23 | tag: _controller.id, 24 | builder: ((controller) { 25 | return SmartRefresher( 26 | enablePullUp: true, 27 | header: const RefreshHeader(), 28 | footer: const RefreshFooter(), 29 | controller: controller.refreshController, 30 | onRefresh: controller.onRefresh, 31 | onLoading: controller.onLoadMore, 32 | child: ListView.builder( 33 | padding: EdgeInsets.zero, 34 | shrinkWrap: true, 35 | itemCount: controller.dataSource.length, 36 | itemBuilder: (BuildContext context, int index) { 37 | if (controller.dataSource.isEmpty) { 38 | return const EmptyView(); 39 | } 40 | 41 | final model = controller.dataSource[index]; 42 | return InfoCell( 43 | model: model, 44 | callback: (_) { 45 | Get.toNamed(Routes.web, arguments: model); 46 | }, 47 | ); 48 | }, 49 | ), 50 | ); 51 | }), 52 | ); 53 | } 54 | } -------------------------------------------------------------------------------- /lib/pages/tree/view/tree_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | import 'package:getx_study/enum/tag_type.dart'; 6 | import 'package:getx_study/pages/common/status_view.dart'; 7 | import 'package:getx_study/pages/tree/controller/tabs_controller.dart'; 8 | import 'tree_cell.dart'; 9 | 10 | class TreePage extends GetView { 11 | const TreePage({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return CupertinoPageScaffold( 16 | navigationBar: const CupertinoNavigationBar( 17 | middle: Text("体系"), 18 | ), 19 | child: StatusView( 20 | tag: TagType.tree.toString(), 21 | contentBuilder: (controller) { 22 | return ListView.builder( 23 | /// 通过ScrollMixin的scoll,可以监听滑动到顶部与滑动到底部, 24 | /// 这里添加了ScrollMixin的scoll之后,iOS特有的点击顶部回到列表顶部就失效了 25 | controller: controller.scroll, 26 | shrinkWrap: true, 27 | itemCount: controller.data?.length ?? 0, 28 | itemBuilder: ((context, index) { 29 | final model = controller.data?[index]; 30 | if (model != null) { 31 | return TreeCell(model); 32 | } else { 33 | return Container(); 34 | } 35 | }), 36 | ); 37 | }, 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/pages/web/binding/web_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 3 | 4 | import 'package:getx_study/pages/web/controller/web_controller.dart'; 5 | import 'package:getx_study/pages/web/repository/web_repository.dart'; 6 | import 'package:getx_study/base/class_name.dart'; 7 | 8 | class WebBinding extends Bindings { 9 | @override 10 | void dependencies() { 11 | Get.lazyPut( 12 | () => WebRepository(), 13 | ); 14 | Get.lazyPut( 15 | () => WebController(), 16 | ); 17 | Get.lazyPut( 18 | tag: className(WebController), 19 | () => RefreshController(initialRefresh: false), 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /lib/pages/web/repository/web_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:getx_study/base/interface.dart'; 2 | import 'package:getx_study/entity/base_entity.dart'; 3 | import 'package:getx_study/http_util/request.dart' as http; 4 | import 'package:getx_study/http_util/api.dart'; 5 | 6 | class WebRepository extends IRepository { 7 | Future> unCollectAction({required int originId}) => 8 | http.Request.post( 9 | api: "${Api.postUnCollectArticle}${originId.toString()}/json"); 10 | 11 | Future> collectAction({required int originId}) => 12 | http.Request.post(api: "${Api.postCollectArticle}${originId.toString()}/json"); 13 | } 14 | -------------------------------------------------------------------------------- /lib/resource/constant.dart: -------------------------------------------------------------------------------- 1 | abstract class Constant { 2 | 3 | /// BaseEntity 4 | static const String data = "data"; 5 | static const String errorCode = "errorCode"; 6 | static const String errorMsg = "errorMsg"; 7 | 8 | /// PageEntity 9 | static const String datas = "datas"; 10 | static const String curPage = "curPage"; 11 | static const String offset = "offset"; 12 | static const String over = "over"; 13 | static const String pageCount = "pageCount"; 14 | static const String size = "size"; 15 | static const String total = "total"; 16 | } -------------------------------------------------------------------------------- /lib/routes/getx_router_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get_navigation/src/router_report.dart'; 4 | 5 | GetXRouterObserver getXRouterObserver = GetXRouterObserver(); 6 | 7 | class GetXRouterObserver extends NavigatorObserver { 8 | @override 9 | void didPush(Route route, Route? previousRoute) { 10 | RouterReportManager.reportCurrentRoute(route); 11 | } 12 | 13 | @override 14 | void didPop(Route route, Route? previousRoute) async { 15 | RouterReportManager.reportRouteDispose(route); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/routes/history_router_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | HistoryRouterObserver historyRouterObserver = HistoryRouterObserver(); 4 | 5 | ///记录路由历史 6 | class HistoryRouterObserver extends RouteObserver { 7 | List> history = >[]; 8 | 9 | @override 10 | void didPop(Route route, Route? previousRoute) { 11 | super.didPop(route, previousRoute); 12 | history.remove(route); 13 | //调用Navigator.of(context).pop() 出栈时回调 14 | } 15 | 16 | @override 17 | void didPush(Route route, Route? previousRoute) { 18 | super.didPush(route, previousRoute); 19 | history.add(route); 20 | //调用Navigator.of(context).push(Route()) 进栈时回调 21 | } 22 | 23 | @override 24 | void didRemove(Route route, Route? previousRoute) { 25 | super.didRemove(route, previousRoute); 26 | history.remove(route); 27 | //调用Navigator.of(context).removeRoute(Route()) 移除某个路由回调 28 | } 29 | 30 | @override 31 | void didReplace({Route? newRoute, Route? oldRoute}) { 32 | super.didReplace(newRoute: newRoute, oldRoute: oldRoute); 33 | if (oldRoute != null) { 34 | history.remove(oldRoute); 35 | } 36 | if (newRoute != null) { 37 | history.add(newRoute); 38 | } 39 | //调用Navigator.of(context).replace( oldRoute:Route("old"),newRoute:Route("new")) 替换路由时回调 40 | } 41 | } -------------------------------------------------------------------------------- /lib/routes/middleware/login_middleware.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | import 'package:getx_study/app_service/account_service.dart'; 6 | import 'package:getx_study/routes/routes.dart'; 7 | 8 | class LoginMiddleware extends GetMiddleware { 9 | /// 当需要执行路由的重定向时,就可以调用此函数,比如使用它实现强制登录逻辑,即没有登录时跳转登录逻辑。 10 | @override 11 | RouteSettings? redirect(String? route) { 12 | return AccountService.find.isLogin 13 | ? super.redirect(route) 14 | : const RouteSettings(name: Routes.login); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/routes/middleware/web_middleware.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | /// 这地方感觉特别像一个页面的生命周期 7 | class WebMiddleware extends GetMiddleware { 8 | /// 页面消失的时候操作 9 | @override 10 | void onPageDispose() { 11 | super.onPageDispose(); 12 | EasyLoading.dismiss(animation: false); 13 | } 14 | 15 | /// 有时候,我们需要在页面创建之前调用某个额函数,比如可以使用它来更改页面的某些内容或为其提供新页面。 16 | @override 17 | GetPage? onPageCalled(GetPage? page) { 18 | // TODO: implement onPageCalled 19 | return super.onPageCalled(page); 20 | } 21 | 22 | /// 此函数将在初始化绑定之前被调用。在这个函数中,我们可以更改页面的绑定。 23 | @override 24 | List? onBindingsStart(List? bindings) { 25 | // TODO: implement onBindingsStart 26 | return super.onBindingsStart(bindings); 27 | } 28 | 29 | /// 此函数将在绑定初始化之后被调用。在这个函数中,我们可以在创建绑定之后和创建页面小部件之前执行一些操作 30 | @override 31 | GetPageBuilder? onPageBuildStart(GetPageBuilder? page) { 32 | // TODO: implement onPageBuildStart 33 | return super.onPageBuildStart(page); 34 | } 35 | 36 | /// 创建小部件之后执行 37 | @override 38 | Widget onPageBuilt(Widget page) { 39 | // TODO: implement onPageBuilt 40 | return super.onPageBuilt(page); 41 | } 42 | } -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | void fl_register_plugins(FlPluginRegistry* registry) { 14 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 15 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 16 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 17 | g_autoptr(FlPluginRegistrar) open_file_linux_registrar = 18 | fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); 19 | open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); 20 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 21 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 22 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 23 | } 24 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_linux 7 | open_file_linux 8 | url_launcher_linux 9 | ) 10 | 11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 12 | jni 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import device_info_plus 9 | import file_selector_macos 10 | import open_file_mac 11 | import package_info_plus 12 | import path_provider_foundation 13 | import shared_preferences_foundation 14 | import sqflite_darwin 15 | import url_launcher_macos 16 | import webview_flutter_wkwebview 17 | 18 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 19 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 20 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 21 | OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) 22 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 23 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 24 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 25 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 26 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 27 | FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) 28 | } 29 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = getx_study 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.getxStudy 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: getx_study 2 | description: A GetX Study Flutter project. 3 | publish_to: none 4 | version: 1.0.0+1 5 | environment: 6 | sdk: '>=2.17.6 <3.0.0' 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | cupertino_icons: ^1.0.5 11 | get: ^4.6.5 12 | dio: ^5.2.0+1 13 | pretty_dio_logger: ^1.3.1 14 | native_dio_adapter: ^1.0.0+1 15 | cached_network_image: ^3.2.3 16 | shared_preferences: ^2.1.1 17 | flutter_easyloading: ^3.0.5 18 | pull_to_refresh: ^2.0.0 19 | card_swiper: ^3.0.1 20 | webview_flutter: ^4.2.2 21 | flutter_slidable: ^3.0.0 22 | lpinyin: ^2.0.3 23 | marqueer: ^1.4.4 24 | share: ^2.0.4 25 | rxdart: ^0.27.7 26 | image_picker: ^1.0.1 27 | url_launcher: ^6.1.5 28 | image_gallery_saver: ^2.0.3 29 | path_provider: ^2.0.11 30 | event_bus: ^2.0.0 31 | open_file: ^3.2.1 32 | visibility_detector: ^0.4.0+2 33 | permission_handler: ^10.4.3 34 | logger: ^1.4.0 35 | device_info_plus: ^9.0.2 36 | retrofit: '>=4.0.0 <5.0.0' 37 | json_annotation: ^4.8.1 38 | package_info_plus: ^8.3.0 39 | dev_dependencies: 40 | flutter_test: 41 | sdk: flutter 42 | flutter_lints: ^2.0.0 43 | retrofit_generator: '>=7.0.0 <8.0.0' 44 | build_runner: '>=2.3.0 <4.0.0' 45 | json_serializable: ^6.6.2 46 | dependency_validator: ^3.2.3 47 | webview_flutter_jsbridge: 48 | git: 49 | url: 'https://github.com/seasonZhu/webview_flutter_jsbridge' 50 | flutter: 51 | uses-material-design: true 52 | assets: 53 | - assets/images/ic_eye.png 54 | - assets/images/ic_head.jpeg 55 | - assets/images/launchImage.png 56 | - assets/images/placeholder.png 57 | - assets/images/saber.jpg 58 | - assets/images/saber_logo.jpg 59 | - assets/images/season_ali_pay.jpg 60 | - assets/images/upgrade.png 61 | - assets/images/welcome_1.png 62 | - assets/images/welcome_2.jpg 63 | - assets/html/index.html 64 | flr: 65 | core_version: 3.1.0 66 | dartfmt_line_length: 80 67 | assets: [] 68 | fonts: [] 69 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:getx_study/my_app.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp(isFirst: true,)); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | getx_study 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getx_study", 3 | "short_name": "getx_study", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | void RegisterPlugins(flutter::PluginRegistry* registry) { 14 | FileSelectorWindowsRegisterWithRegistrar( 15 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 16 | PermissionHandlerWindowsPluginRegisterWithRegistrar( 17 | registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); 18 | UrlLauncherWindowsRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 20 | } 21 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_windows 7 | permission_handler_windows 8 | url_launcher_windows 9 | ) 10 | 11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 12 | jni 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Disable Windows macros that collide with C++ standard library functions. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 25 | 26 | # Add dependency libraries and include directories. Add any application-specific 27 | # dependencies here. 28 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 29 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 30 | 31 | # Run the Flutter tool portions of the build. This must not be removed. 32 | add_dependencies(${BINARY_NAME} flutter_assemble) 33 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.CreateAndShow(L"getx_study", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seasonZhu/GetXStudy/3bff369b56d78da3107a8637f5fc21494f3ad97d/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | std::string utf8_string; 52 | if (target_length == 0 || target_length > utf8_string.max_size()) { 53 | return utf8_string; 54 | } 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------