├── .github └── workflows │ └── publish_app_release.yml ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── xycz │ │ │ │ └── dmzjx │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-ldpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── playstore-icon.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── network_security_config.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── images │ ├── logo.png │ ├── logo_dmzj.png │ ├── vip.png │ ├── vip_chapter.png │ └── vip_comic.png ├── lotties │ ├── empty.json │ ├── error.json │ └── loadding.json ├── proto │ ├── comic.proto │ ├── news.proto │ └── novel.proto └── statement.txt ├── distribute_options.yaml ├── document ├── RELEASE.txt ├── app_version.json ├── logo.png ├── screenshot_dark.jpg └── screenshot_light.jpg ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── flutter_export_environment.sh ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-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 │ ├── en.lproj │ └── InfoPlist.strings │ └── zh-Hans.lproj │ ├── InfoPlist.strings │ ├── LaunchScreen.strings │ └── Main.strings ├── lib ├── app │ ├── app_color.dart │ ├── app_constant.dart │ ├── app_error.dart │ ├── app_style.dart │ ├── controller │ │ └── base_controller.dart │ ├── dialog_utils.dart │ ├── event_bus.dart │ ├── keys.dart │ ├── log.dart │ └── utils.dart ├── main.dart ├── models │ ├── comic │ │ ├── author_model.dart │ │ ├── category_comic_model.dart │ │ ├── category_filter_model.dart │ │ ├── category_item_model.dart │ │ ├── chapter_detail_web_model.dart │ │ ├── chapter_info.dart │ │ ├── comic_related_model.dart │ │ ├── detail_info.dart │ │ ├── detail_v1_model.dart │ │ ├── recommend_model.dart │ │ ├── search_item.dart │ │ ├── search_model.dart │ │ ├── special_detail_model.dart │ │ ├── special_model.dart │ │ ├── view_point_model.dart │ │ └── web_search_model.dart │ ├── comment │ │ ├── comment_item.dart │ │ └── user_comment_item.dart │ ├── db │ │ ├── comic_download_info.dart │ │ ├── comic_download_info.g.dart │ │ ├── comic_history.dart │ │ ├── comic_history.g.dart │ │ ├── download_status.dart │ │ ├── download_status.g.dart │ │ ├── local_favorite.dart │ │ ├── local_favorite.g.dart │ │ ├── novel_download_info.dart │ │ ├── novel_download_info.g.dart │ │ ├── novel_history.dart │ │ └── novel_history.g.dart │ ├── news │ │ ├── news_banner_model.dart │ │ ├── news_stat_model.dart │ │ └── news_tag_model.dart │ ├── novel │ │ ├── category_filter_model.dart │ │ ├── category_model.dart │ │ ├── category_novel_model.dart │ │ ├── latest_model.dart │ │ ├── novel_detail_model.dart │ │ ├── rank_model.dart │ │ ├── recommend_model.dart │ │ └── search_model.dart │ ├── proto │ │ ├── comic.pb.dart │ │ ├── comic.pbjson.dart │ │ ├── news.pb.dart │ │ ├── news.pbjson.dart │ │ ├── novel.pb.dart │ │ └── novel.pbjson.dart │ ├── user │ │ ├── bind_status_model.dart │ │ ├── comic_history_model.dart │ │ ├── login_result_model.dart │ │ ├── novel_history_model.dart │ │ ├── subscribe_comic_model.dart │ │ ├── subscribe_news_model.dart │ │ ├── subscribe_novel_model.dart │ │ └── user_profile_model.dart │ └── version_model.dart ├── modules │ ├── comic │ │ ├── author_detail │ │ │ ├── author_detail_controller.dart │ │ │ └── author_detail_page.dart │ │ ├── category_detail │ │ │ ├── category_detail_controller.dart │ │ │ └── category_detail_page.dart │ │ ├── detail │ │ │ ├── comic_detail_controller.dart │ │ │ ├── comic_detail_page.dart │ │ │ └── comic_detail_related_page.dart │ │ ├── home │ │ │ ├── category │ │ │ │ ├── comic_category_controller.dart │ │ │ │ └── comic_category_view.dart │ │ │ ├── comic_home_controller.dart │ │ │ ├── comic_home_page.dart │ │ │ ├── latest │ │ │ │ ├── comic_latest_controller.dart │ │ │ │ └── comic_latest_view.dart │ │ │ ├── rank │ │ │ │ ├── comic_rank_controller.dart │ │ │ │ └── comic_rank_view.dart │ │ │ ├── recommend │ │ │ │ ├── comic_recommend_controller.dart │ │ │ │ └── comic_recommend_view.dart │ │ │ └── special │ │ │ │ ├── comic_special_controller.dart │ │ │ │ └── comic_special_view.dart │ │ ├── reader │ │ │ ├── comic_reader_controller.dart │ │ │ └── comic_reader_page.dart │ │ ├── search │ │ │ ├── comic_search_controller.dart │ │ │ └── comic_search_page.dart │ │ ├── select_chapter │ │ │ ├── comic_select_chapter_controller.dart │ │ │ └── comic_select_chapter_page.dart │ │ └── special_detail │ │ │ ├── special_detail_controller.dart │ │ │ └── special_detail_page.dart │ ├── common │ │ ├── comment │ │ │ ├── add_comment_controller.dart │ │ │ ├── add_comment_page.dart │ │ │ ├── comment_controller.dart │ │ │ ├── comment_list_controller.dart │ │ │ ├── comment_list_view.dart │ │ │ └── comment_page.dart │ │ ├── download │ │ │ ├── comic │ │ │ │ ├── comic_download_page.dart │ │ │ │ ├── comic_downloaded_detail_controller.dart │ │ │ │ ├── comic_downloaded_detail_page.dart │ │ │ │ ├── comic_downloaded_view.dart │ │ │ │ └── comic_downloading_view.dart │ │ │ └── novel │ │ │ │ ├── novel_download_page.dart │ │ │ │ ├── novel_downloaded_detail_controller.dart │ │ │ │ ├── novel_downloaded_detail_page.dart │ │ │ │ ├── novel_downloaded_view.dart │ │ │ │ └── novel_downloading_view.dart │ │ ├── empty_page.dart │ │ ├── test_subroute_page.dart │ │ └── webview │ │ │ ├── webview_controller.dart │ │ │ └── webview_page.dart │ ├── index │ │ ├── index_controller.dart │ │ └── index_page.dart │ ├── news │ │ ├── detail │ │ │ ├── news_detail_controller.dart │ │ │ └── news_detail_page.dart │ │ └── home │ │ │ ├── news_home_controller.dart │ │ │ ├── news_home_page.dart │ │ │ ├── news_list_controller.dart │ │ │ └── news_list_view.dart │ ├── novel │ │ ├── category_detail │ │ │ ├── novel_category_detail_controller.dart │ │ │ └── novel_category_detail_page.dart │ │ ├── detail │ │ │ ├── novel_detail_controller.dart │ │ │ └── novel_detail_page.dart │ │ ├── home │ │ │ ├── category │ │ │ │ ├── novel_category_controller.dart │ │ │ │ └── novel_category_view.dart │ │ │ ├── latest │ │ │ │ ├── novel_latest_controller.dart │ │ │ │ └── novel_latest_view.dart │ │ │ ├── novel_home_controller.dart │ │ │ ├── novel_home_page.dart │ │ │ ├── rank │ │ │ │ ├── novel_rank_controller.dart │ │ │ │ └── novel_rank_view.dart │ │ │ └── recommend │ │ │ │ ├── novel_recommend_controller.dart │ │ │ │ └── novel_recommend_view.dart │ │ ├── reader │ │ │ ├── novel_horizontal_reader.dart │ │ │ ├── novel_reader_controller.dart │ │ │ └── novel_reader_page.dart │ │ ├── search │ │ │ ├── novel_search_controller.dart │ │ │ └── novel_search_page.dart │ │ └── select_chapter │ │ │ ├── novel_select_chapter_controller.dart │ │ │ └── novel_select_chapter_page.dart │ └── user │ │ ├── comment │ │ ├── user_comment_controller.dart │ │ ├── user_comment_page.dart │ │ └── user_comment_view.dart │ │ ├── history │ │ ├── comic │ │ │ ├── comic_history_controller.dart │ │ │ └── comic_history_view.dart │ │ ├── novel │ │ │ ├── novel_history_controller.dart │ │ │ └── novel_history_view.dart │ │ ├── user_history_controller.dart │ │ └── user_history_page.dart │ │ ├── local_favorite │ │ ├── local_favorite_controller.dart │ │ └── local_favorite_page.dart │ │ ├── local_history │ │ ├── comic │ │ │ ├── comic_history_controller.dart │ │ │ └── comic_history_view.dart │ │ ├── local_history_controller.dart │ │ ├── local_history_page.dart │ │ └── novel │ │ │ ├── novel_history_controller.dart │ │ │ └── novel_history_view.dart │ │ ├── login │ │ ├── user_login_controller.dart │ │ └── user_login_dialog.dart │ │ ├── settings │ │ ├── settings_controller.dart │ │ └── settings_page.dart │ │ ├── subscribe │ │ ├── comic │ │ │ ├── comic_subscribe_controller.dart │ │ │ └── comic_subscribe_view.dart │ │ ├── news │ │ │ ├── news_subscribe_controller.dart │ │ │ └── news_subscribe_view.dart │ │ ├── novel │ │ │ ├── novel_subscribe_controller.dart │ │ │ └── novel_subscribe_view.dart │ │ ├── user_subscribe_controller.dart │ │ └── user_subscribe_pgae.dart │ │ ├── user_home_controller.dart │ │ └── user_home_page.dart ├── requests │ ├── comic_request.dart │ ├── comment_request.dart │ ├── common │ │ ├── api.dart │ │ ├── custom_interceptor.dart │ │ └── http_client.dart │ ├── common_request.dart │ ├── news_request.dart │ ├── novel_request.dart │ └── user_request.dart ├── routes │ ├── app_navigator.dart │ ├── app_pages.dart │ └── route_path.dart ├── services │ ├── app_settings_service.dart │ ├── comic_download_service.dart │ ├── db_service.dart │ ├── download_task │ │ ├── comic_downloader.dart │ │ └── novel_downloader.dart │ ├── local_storage_service.dart │ ├── novel_download_service.dart │ └── user_service.dart └── widgets │ ├── border_text.dart │ ├── comment_item_widget.dart │ ├── custom_header.dart │ ├── keep_alive_wrapper.dart │ ├── loadding.dart │ ├── local_image.dart │ ├── net_image.dart │ ├── page_grid_view.dart │ ├── page_list_view.dart │ ├── refresh_until_widget.dart │ ├── shadow_card.dart │ ├── status │ ├── app_empty_widget.dart │ ├── app_error_widget.dart │ └── app_loadding_widget.dart │ ├── tab_appbar.dart │ └── user_photo.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 └── packaging │ └── deb │ └── make_config.yaml ├── 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 │ │ │ ├── 1024.png │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 256.png │ │ │ ├── 32.png │ │ │ ├── 512.png │ │ │ ├── 64.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Image.imageset │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements └── packaging │ └── dmg │ └── make_config.yaml ├── pubspec.lock ├── pubspec.yaml └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake ├── packaging └── msix │ └── make_config.yaml └── 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 /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 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: 12cb4eb7a009f52b347b62ade7cb4854b926af72 17 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 18 | - platform: windows 19 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 20 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | DMZJX logo 6 |

7 |

动漫之家X

8 | 9 |

10 | 使用Flutter编写的动漫之家跨平台第三方客户端 11 |

12 | 13 | ![浅色模式](/document/screenshot_light.jpg) 14 | 15 | ![深色模式](/document/screenshot_dark.jpg) 16 | 17 | ## 支持平台 18 | 19 | - [x] Android 20 | - [x] iOS 21 | - [x] Windows `Beta` 22 | - [x] MacOS `Beta` 23 | - [x] Linux `Beta` 24 | 25 | 请到[Releases](https://github.com/xiaoyaocz/flutter_dmzj/releases)下载最新版本,iOS请下载ipa文件自行签名安装。 26 | 27 | 反馈问题、相关讨论请到[Discussions](https://github.com/xiaoyaocz/flutter_dmzj/discussions),代码改进请直接提交PR。 28 | 29 | ## 声明 30 | 31 | - 本项目为[动漫之家](https://dmzj.com)第三方开源APP 32 | 33 | - 本项目仅用于学习交流编程技术,严禁将本项目用于商业目的。如有任何商业行为,均与本项目无关。 34 | 35 | - 本项目内所有资源版权均归属于其著作者或动漫之家所有 36 | 37 | - 如果本项目存在侵犯您的相关权益的情况,请及时与开发者联系,开发者将会及时删除有关内容。 38 | 39 | ## License 40 | 41 | [GPL-3.0 License](https://github.com/xiaoyaocz/flutter_dmzj/blob/main/LICENSE),禁止用于任何商业用途 -------------------------------------------------------------------------------- /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/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 12 | 20 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/xycz/dmzjx/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.xycz.dmzjx 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/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/android/app/src/main/res/mipmap-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/playstore-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/android/app/src/main/res/playstore-icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /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/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/logo_dmzj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/assets/images/logo_dmzj.png -------------------------------------------------------------------------------- /assets/images/vip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/assets/images/vip.png -------------------------------------------------------------------------------- /assets/images/vip_chapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/assets/images/vip_chapter.png -------------------------------------------------------------------------------- /assets/images/vip_comic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/assets/images/vip_comic.png -------------------------------------------------------------------------------- /assets/proto/news.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | // 该文件使用ChatGPT辅助生成 3 | 4 | message NewsListResponseProto { 5 | int32 errno = 1; 6 | string errmsg = 2; 7 | repeated NewsListInfoProto data = 3; 8 | } 9 | 10 | message NewsListInfoProto { 11 | int64 articleId = 1; 12 | string title = 2; 13 | string fromName = 3; 14 | string fromUrl = 4; 15 | int64 createTime = 5; 16 | int32 isForeign = 6; 17 | string foreignUrl = 7; 18 | string intro = 8; 19 | int64 authorId = 9; 20 | int32 status = 10; 21 | string rowPicUrl = 11; 22 | string colPicUrl = 12; 23 | int32 qchatShow = 13; 24 | string pageUrl = 14; 25 | int64 commentAmount = 15; 26 | int64 authorUid = 16; 27 | string cover = 17; 28 | string nickname = 18; 29 | int64 moodAmount = 19; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /assets/proto/novel.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | // 该文件使用ChatGPT辅助生成 3 | 4 | message NovelChapterDetailProto { 5 | int64 chapterId = 1; 6 | string chapterName = 2; 7 | int32 chapterOrder = 3; 8 | } 9 | 10 | message NovelVolumeProto { 11 | int64 volume_id = 1; 12 | int64 lnovel_id = 2; 13 | string volume_name = 3; 14 | int32 volume_order = 4; 15 | int64 addtime = 5; 16 | int32 sum_chapters = 6; 17 | } 18 | message NovelChapterResponseProto { 19 | int32 errno = 1; 20 | string errmsg = 2; 21 | repeated NovelVolumeDetailProto data = 3; 22 | } 23 | message NovelVolumeDetailProto { 24 | int64 volume_id = 1; 25 | string volume_name = 2; 26 | int32 volume_order = 3; 27 | repeated NovelChapterDetailProto chapters = 4; 28 | } 29 | 30 | message NovelDetailProto { 31 | int64 novel_id = 1; 32 | string name = 2; 33 | string zone = 3; 34 | string status = 4; 35 | string last_update_volume_name = 5; 36 | string last_update_chapter_name = 6; 37 | int64 last_update_volume_id = 7; 38 | int64 last_update_chapter_id = 8; 39 | int64 last_update_time = 9; 40 | string cover = 10; 41 | int64 hot_hits = 11; 42 | string introduction = 12; 43 | repeated string types = 13; 44 | string authors = 14; 45 | string first_letter = 15; 46 | int64 subscribe_num = 16; 47 | int64 redis_update_time = 17; 48 | repeated NovelVolumeProto volume = 18; 49 | } 50 | message NovelDetailResponseProto { 51 | int32 errno = 1; 52 | string errmsg = 2; 53 | NovelDetailProto data = 3; 54 | } -------------------------------------------------------------------------------- /assets/statement.txt: -------------------------------------------------------------------------------- 1 | 在使用本软件之前,请您仔细阅读以下内容,并确保您充分理解并同意以下条款: 2 | 3 | 1、本软件为开源的第三方软件,可以免费下载用于测试相关功能,在测试完毕后应及时卸载本软件。 4 | 5 | 2、本软件为第三方开源软件,不与动漫之家(dmzj.com)有任何关联。软件内所有内容均来自动漫之家(dmzj.com)公开在互联网的资源,仅供用户参考和学习使用,不得用于商业和非法用途。对于使用本软件所造成的任何后果,本软件作者概不负责。 6 | 7 | 3、如果本软件存在侵犯您的合法权益的情况,请及时与作者联系,作者将会及时删除有关内容。 8 | 9 | 4、本软件不会收集、存储、使用任何用户的个人信息,包括但不限于姓名、地址、电子邮件地址、电话号码等。在使用本软件过程中,不会进行任何形式的个人信息采集。如用户提供任何个人信息,将被视为用户已自愿提供,并且用户将自行承担由此产生的所有法律责任。 10 | 11 | 5、本软件使用者应遵守国家相关法律法规和使用规范,不得利用本软件从事任何违法违规行为。如因使用本软件而导致的违法行为,使用者应承担相应的法律责任。 12 | 13 | 6、本软件作者保留对免责声明的最终解释权。 14 | 15 | 如您不同意本免责声明中的任何内容,请勿使用本软件。使用本软件即代表您已完全理解并同意上述内容。 -------------------------------------------------------------------------------- /distribute_options.yaml: -------------------------------------------------------------------------------- 1 | output: build/dist/ -------------------------------------------------------------------------------- /document/RELEASE.txt: -------------------------------------------------------------------------------- 1 | 1. 适配Flutter 3.22 2 | 2. 点击换一批按钮时旋转加载图标 #216 @GZGavinZhao -------------------------------------------------------------------------------- /document/app_version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.1.2-beta", 3 | "version_num": 20102, 4 | "version_desc": "1. 更新请求接口", 5 | "prerelease":false, 6 | "download_url": "https://github.com/xiaoyaocz/flutter_dmzj/releases" 7 | } -------------------------------------------------------------------------------- /document/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/document/logo.png -------------------------------------------------------------------------------- /document/screenshot_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/document/screenshot_dark.jpg -------------------------------------------------------------------------------- /document/screenshot_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/document/screenshot_light.jpg -------------------------------------------------------------------------------- /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 | 11.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/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=D:\flutter" 4 | export "FLUTTER_APPLICATION_PATH=D:\projects\dmzj_flutter" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_TARGET=lib\main.dart" 7 | export "FLUTTER_BUILD_DIR=build" 8 | export "FLUTTER_BUILD_NAME=2.1.0" 9 | export "FLUTTER_BUILD_NUMBER=20100" 10 | export "DART_OBFUSCATION=false" 11 | export "TRACK_WIDGET_CREATION=true" 12 | export "TREE_SHAKE_ICONS=false" 13 | export "PACKAGE_CONFIG=.dart_tool/package_config.json" 14 | -------------------------------------------------------------------------------- /ios/Runner.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 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/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/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/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/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/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/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/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/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/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/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 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | DMZJX 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | DMZJX 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | NSAppTransportSecurity 51 | 52 | NSAllowsArbitraryLoads 53 | 54 | 55 | NSPhotoLibraryUsageDescription 56 | Save pictures to your gallery 57 | 58 | 59 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | Runner 4 | 5 | Created by xiaoyaocz on 2023/3/15. 6 | 7 | */ 8 | "CFBundleDisplayName" = "DMZJX"; 9 | "NSPhotoLibraryUsageDescription" = "Save pictures to your gallery"; -------------------------------------------------------------------------------- /ios/Runner/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | Runner 4 | 5 | Created by xiaoyaocz on 2023/3/15. 6 | 7 | */ 8 | "CFBundleDisplayName" = "动漫之家X"; 9 | "NSPhotoLibraryUsageDescription" = "保存图片至相册"; 10 | -------------------------------------------------------------------------------- /ios/Runner/zh-Hans.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/zh-Hans.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/app/app_color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColor { 4 | static ColorScheme colorSchemeLight = ColorScheme.fromSwatch( 5 | primarySwatch: Colors.blue, 6 | brightness: Brightness.light, 7 | ); 8 | static ColorScheme colorSchemeDark = ColorScheme.fromSwatch( 9 | primarySwatch: Colors.blue, 10 | accentColor: Colors.blue, 11 | //primaryColorDark: Colors.blue, 12 | brightness: Brightness.dark, 13 | ); 14 | static const Color backgroundColor = Color(0xfffafafa); 15 | static const Color backgroundColorDark = Color(0xff212121); 16 | static const Color black333 = Color(0xff333333); 17 | static const Color greyf0f0f0 = Color(0xfff0f0f0); 18 | 19 | static Map> novelThemes = { 20 | 0: [ 21 | const Color.fromRGBO(245, 239, 217, 1), 22 | const Color(0xff301e1b), 23 | ], 24 | 1: [ 25 | const Color.fromRGBO(248, 247, 252, 1), 26 | black333, 27 | ], 28 | 2: [ 29 | const Color.fromRGBO(192, 237, 198, 1), 30 | Colors.black, 31 | ], 32 | 3: [ 33 | const Color(0xff3b3a39), 34 | const Color.fromRGBO(230, 230, 230, 1), 35 | ], 36 | 4: [ 37 | Colors.black, 38 | const Color.fromRGBO(200, 200, 200, 1), 39 | ], 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /lib/app/app_constant.dart: -------------------------------------------------------------------------------- 1 | class AppConstant { 2 | /// 定义平板宽度,当大于此宽度时APP进入双栏模式 3 | static const double kTabletWidth = 1000; 4 | 5 | /// 类型ID-漫画 6 | static const int kTypeComic = 4; 7 | 8 | /// 类型ID-新闻 9 | static const int kTypeNews = 6; 10 | 11 | /// 类型ID-专题 12 | static const int kTypeSpecial = 2; 13 | 14 | /// 类型ID-轻小说 15 | static const int kTypeNovel = 1; 16 | } 17 | 18 | class ReaderDirection { 19 | /// 左右 0 20 | static const int kLeftToRight = 0; 21 | 22 | /// 上下 1 23 | static const int kUpToDown = 1; 24 | 25 | /// 右左 2 26 | static const int kRightToLeft = 2; 27 | } 28 | -------------------------------------------------------------------------------- /lib/app/app_error.dart: -------------------------------------------------------------------------------- 1 | class AppError implements Exception { 2 | final int code; 3 | final String message; 4 | AppError( 5 | this.message, { 6 | this.code = 0, 7 | }); 8 | 9 | @override 10 | String toString() { 11 | return message; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/app/event_bus.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_dmzj/app/log.dart'; 4 | 5 | /// 全局事件 6 | class EventBus { 7 | /// 点击了底部导航 8 | static const String kBottomNavigationBarClicked = 9 | "BottomNavigationBarClicked"; 10 | 11 | /// 更新了漫画记录 12 | static const String kUpdatedComicHistory = "UpdateComicHistory"; 13 | 14 | /// 更新了小说记录 15 | static const String kUpdatedNovelHistory = "UpdateNovelHistory"; 16 | static EventBus? _instance; 17 | 18 | static EventBus get instance { 19 | _instance ??= EventBus(); 20 | return _instance!; 21 | } 22 | 23 | final Map _streams = {}; 24 | 25 | /// 触发事件 26 | void emit(String name, T data) { 27 | if (!_streams.containsKey(name)) { 28 | _streams.addAll({name: StreamController.broadcast()}); 29 | } 30 | Log.d("Emit Event:$name\r\n$data"); 31 | 32 | _streams[name]!.add(data); 33 | } 34 | 35 | /// 监听事件 36 | StreamSubscription listen(String name, Function(dynamic)? onData) { 37 | if (!_streams.containsKey(name)) { 38 | _streams.addAll({name: StreamController.broadcast()}); 39 | } 40 | return _streams[name]!.stream.listen(onData); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/app/keys.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | class Keys { 4 | /// APP相关设置的Hive Box名称 5 | static const SETTINGS_BOX_NAME = "DmzjSettings"; 6 | 7 | /// 主题模式 8 | static const SETTINGS_THEME_MODE = "ThemeMode"; 9 | 10 | /// 主题颜色 11 | static const SETTINGS_THEME_COLOR = "ThemeColor"; 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/log.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:logger/logger.dart'; 3 | 4 | class Log { 5 | static Logger logger = Logger( 6 | printer: PrettyPrinter( 7 | methodCount: 0, 8 | errorMethodCount: 8, 9 | lineLength: 120, 10 | colors: true, 11 | printEmojis: true, 12 | printTime: false, 13 | ), 14 | ); 15 | 16 | static d(String message) { 17 | logger.d("${DateTime.now().toString()}\n$message"); 18 | } 19 | 20 | static i(String message) { 21 | logger.i("${DateTime.now().toString()}\n$message"); 22 | } 23 | 24 | static e(String message, StackTrace stackTrace) { 25 | logger.e("${DateTime.now().toString()}\n$message", stackTrace: stackTrace); 26 | } 27 | 28 | static w(String message) { 29 | logger.w("${DateTime.now().toString()}\n$message"); 30 | } 31 | 32 | static void logPrint(dynamic obj) { 33 | if (obj is Error) { 34 | Log.e(obj.toString(), obj.stackTrace ?? StackTrace.current); 35 | } else if (kDebugMode) { 36 | print(obj); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/models/comic/author_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class ComicAuthorModel { 11 | ComicAuthorModel({ 12 | required this.nickname, 13 | this.description, 14 | required this.cover, 15 | required this.data, 16 | }); 17 | 18 | factory ComicAuthorModel.fromJson(Map json) { 19 | final List? data = 20 | json['data'] is List ? [] : null; 21 | if (data != null) { 22 | for (final dynamic item in json['data']!) { 23 | if (item != null) { 24 | data.add( 25 | ComicAuthorComicModel.fromJson(asT>(item)!)); 26 | } 27 | } 28 | } 29 | return ComicAuthorModel( 30 | nickname: asT(json['nickname'])!, 31 | description: asT(json['description']) ?? "", 32 | cover: asT(json['cover'])!, 33 | data: data!, 34 | ); 35 | } 36 | 37 | String nickname; 38 | String? description; 39 | String cover; 40 | List data; 41 | 42 | @override 43 | String toString() { 44 | return jsonEncode(this); 45 | } 46 | 47 | Map toJson() => { 48 | 'nickname': nickname, 49 | 'description': description, 50 | 'cover': cover, 51 | 'data': data, 52 | }; 53 | } 54 | 55 | class ComicAuthorComicModel { 56 | ComicAuthorComicModel({ 57 | required this.id, 58 | required this.name, 59 | required this.cover, 60 | required this.status, 61 | }); 62 | 63 | factory ComicAuthorComicModel.fromJson(Map json) => 64 | ComicAuthorComicModel( 65 | id: asT(json['id'])!, 66 | name: asT(json['name'])!, 67 | cover: asT(json['cover'])!, 68 | status: asT(json['status'])!, 69 | ); 70 | 71 | int id; 72 | String name; 73 | String cover; 74 | String status; 75 | 76 | @override 77 | String toString() { 78 | return jsonEncode(this); 79 | } 80 | 81 | Map toJson() => { 82 | 'id': id, 83 | 'name': name, 84 | 'cover': cover, 85 | 'status': status, 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /lib/models/comic/category_comic_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class ComicCategoryComicModel { 11 | ComicCategoryComicModel({ 12 | required this.id, 13 | required this.title, 14 | required this.authors, 15 | required this.status, 16 | required this.cover, 17 | required this.types, 18 | required this.lastUpdatetime, 19 | required this.num, 20 | }); 21 | 22 | factory ComicCategoryComicModel.fromJson(Map json) => 23 | ComicCategoryComicModel( 24 | id: asT(json['id'])!, 25 | title: asT(json['title'])!, 26 | authors: asT(json['authors'])!, 27 | status: asT(json['status'])!, 28 | cover: asT(json['cover'])!, 29 | types: asT(json['types'])!, 30 | lastUpdatetime: asT(json['last_updatetime'])!, 31 | num: asT(json['num'])!, 32 | ); 33 | 34 | int id; 35 | String title; 36 | String authors; 37 | String status; 38 | String cover; 39 | String types; 40 | int lastUpdatetime; 41 | int num; 42 | 43 | @override 44 | String toString() { 45 | return jsonEncode(this); 46 | } 47 | 48 | Map toJson() => { 49 | 'id': id, 50 | 'title': title, 51 | 'authors': authors, 52 | 'status': status, 53 | 'cover': cover, 54 | 'types': types, 55 | 'last_updatetime': lastUpdatetime, 56 | 'num': num, 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /lib/models/comic/category_filter_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | T? asT(dynamic value) { 6 | if (value is T) { 7 | return value; 8 | } 9 | return null; 10 | } 11 | 12 | class ComicCategoryFilterModel { 13 | ComicCategoryFilterModel({ 14 | required this.title, 15 | required this.items, 16 | }); 17 | 18 | factory ComicCategoryFilterModel.fromJson(Map json) { 19 | final List? items = 20 | json['items'] is List ? [] : null; 21 | if (items != null) { 22 | for (final dynamic item in json['items']!) { 23 | if (item != null) { 24 | items.add(ComicCategoryFilterItemModel.fromJson( 25 | asT>(item)!)); 26 | } 27 | } 28 | } 29 | return ComicCategoryFilterModel( 30 | title: asT(json['title'])!, 31 | items: items!, 32 | ); 33 | } 34 | 35 | String title; 36 | List items; 37 | var selectId = 0.obs; 38 | @override 39 | String toString() { 40 | return jsonEncode(this); 41 | } 42 | 43 | Map toJson() => { 44 | 'title': title, 45 | 'items': items, 46 | }; 47 | } 48 | 49 | class ComicCategoryFilterItemModel { 50 | ComicCategoryFilterItemModel({ 51 | required this.tagId, 52 | required this.tagName, 53 | }); 54 | 55 | factory ComicCategoryFilterItemModel.fromJson(Map json) => 56 | ComicCategoryFilterItemModel( 57 | tagId: asT(json['tag_id'])!, 58 | tagName: asT(json['tag_name'])!, 59 | ); 60 | 61 | int tagId; 62 | String tagName; 63 | 64 | @override 65 | String toString() { 66 | return jsonEncode(this); 67 | } 68 | 69 | Map toJson() => { 70 | 'tag_id': tagId, 71 | 'tag_name': tagName, 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /lib/models/comic/category_item_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class ComicCategoryItemModel { 11 | ComicCategoryItemModel({ 12 | required this.tagId, 13 | required this.title, 14 | required this.cover, 15 | }); 16 | 17 | factory ComicCategoryItemModel.fromJson(Map json) => 18 | ComicCategoryItemModel( 19 | tagId: asT(json['tag_id'])!, 20 | title: asT(json['title'])!, 21 | cover: asT(json['cover'])!, 22 | ); 23 | 24 | int tagId; 25 | String title; 26 | String cover; 27 | 28 | @override 29 | String toString() { 30 | return jsonEncode(this); 31 | } 32 | 33 | Map toJson() => { 34 | 'tag_id': tagId, 35 | 'title': title, 36 | 'cover': cover, 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /lib/models/comic/search_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/models/comic/search_model.dart'; 2 | import 'package:flutter_dmzj/models/comic/web_search_model.dart'; 3 | 4 | class SearchComicItem { 5 | final int comicId; 6 | final String title; 7 | final String cover; 8 | final String author; 9 | final String lastChapterName; 10 | final String tags; 11 | SearchComicItem({ 12 | required this.author, 13 | required this.comicId, 14 | required this.cover, 15 | required this.lastChapterName, 16 | required this.tags, 17 | required this.title, 18 | }); 19 | 20 | factory SearchComicItem.fromApi(ComicSearchModel item) => SearchComicItem( 21 | author: item.authors, 22 | comicId: item.id, 23 | cover: item.cover, 24 | lastChapterName: item.lastName, 25 | tags: item.types, 26 | title: item.title, 27 | ); 28 | factory SearchComicItem.fromWeb(ComicWebSearchModel item) => SearchComicItem( 29 | author: item.comicAuthor, 30 | comicId: item.id, 31 | cover: item.cover, 32 | lastChapterName: item.lastUpdateChapterName, 33 | tags: "/", 34 | title: item.comicName, 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /lib/models/comic/special_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class ComicSpecialModel { 11 | ComicSpecialModel({ 12 | required this.id, 13 | required this.title, 14 | required this.shortTitle, 15 | required this.createTime, 16 | required this.smallCover, 17 | required this.pageType, 18 | required this.sort, 19 | required this.pageUrl, 20 | }); 21 | 22 | factory ComicSpecialModel.fromJson(Map json) => 23 | ComicSpecialModel( 24 | id: asT(json['id'])!, 25 | title: asT(json['title'])!, 26 | shortTitle: asT(json['short_title'])!, 27 | createTime: asT(json['create_time'])!, 28 | smallCover: asT(json['small_cover'])!, 29 | pageType: asT(json['page_type'])!, 30 | sort: asT(json['sort'])!, 31 | pageUrl: asT(json['page_url'])!, 32 | ); 33 | 34 | int id; 35 | String title; 36 | String shortTitle; 37 | int createTime; 38 | String smallCover; 39 | int pageType; 40 | int sort; 41 | String pageUrl; 42 | 43 | @override 44 | String toString() { 45 | return jsonEncode(this); 46 | } 47 | 48 | Map toJson() => { 49 | 'id': id, 50 | 'title': title, 51 | 'short_title': shortTitle, 52 | 'create_time': createTime, 53 | 'small_cover': smallCover, 54 | 'page_type': pageType, 55 | 'sort': sort, 56 | 'page_url': pageUrl, 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /lib/models/comic/view_point_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | T? asT(dynamic value) { 6 | if (value is T) { 7 | return value; 8 | } 9 | return null; 10 | } 11 | 12 | class ComicViewPointModel { 13 | ComicViewPointModel({ 14 | required this.id, 15 | required this.uid, 16 | required this.content, 17 | required this.num, 18 | required this.page, 19 | }); 20 | 21 | factory ComicViewPointModel.fromJson(Map json) => 22 | ComicViewPointModel( 23 | id: asT(json['id'])!, 24 | uid: asT(json['uid'])!, 25 | content: asT(json['content'])!, 26 | num: (asT(json['num']) ?? 0).obs, 27 | page: asT(json['page'])!, 28 | ); 29 | 30 | int id; 31 | int uid; 32 | String content; 33 | RxInt num; 34 | int page; 35 | 36 | @override 37 | String toString() { 38 | return jsonEncode(this); 39 | } 40 | 41 | Map toJson() => { 42 | 'id': id, 43 | 'uid': uid, 44 | 'content': content, 45 | 'num': num, 46 | 'page': page, 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /lib/models/comic/web_search_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class ComicWebSearchModel { 11 | ComicWebSearchModel({ 12 | required this.id, 13 | required this.comicName, 14 | required this.comicAuthor, 15 | required this.comicCover, 16 | required this.cover, 17 | required this.lastUpdateChapterName, 18 | required this.comicUrlRaw, 19 | required this.comicUrl, 20 | required this.status, 21 | required this.chapterUrlRaw, 22 | required this.chapterUrl, 23 | }); 24 | 25 | factory ComicWebSearchModel.fromJson(Map json) => 26 | ComicWebSearchModel( 27 | id: asT(json['id'])!, 28 | comicName: asT(json['comic_name'])!, 29 | comicAuthor: asT(json['comic_author']) ?? "", 30 | comicCover: asT(json['comic_cover']) ?? "", 31 | cover: asT(json['cover']) ?? "", 32 | lastUpdateChapterName: 33 | asT(json['last_update_chapter_name']) ?? "", 34 | comicUrlRaw: asT(json['comic_url_raw']) ?? "", 35 | comicUrl: asT(json['comic_url']) ?? "", 36 | status: asT(json['status']) ?? "", 37 | chapterUrlRaw: asT(json['chapter_url_raw']) ?? "", 38 | chapterUrl: asT(json['chapter_url']) ?? "", 39 | ); 40 | 41 | int id; 42 | String comicName; 43 | String comicAuthor; 44 | String comicCover; 45 | String cover; 46 | String lastUpdateChapterName; 47 | String comicUrlRaw; 48 | String comicUrl; 49 | String status; 50 | String chapterUrlRaw; 51 | String chapterUrl; 52 | 53 | @override 54 | String toString() { 55 | return jsonEncode(this); 56 | } 57 | 58 | Map toJson() => { 59 | 'id': id, 60 | 'comic_name': comicName, 61 | 'comic_author': comicAuthor, 62 | 'comic_cover': comicCover, 63 | 'cover': cover, 64 | 'last_update_chapter_name': lastUpdateChapterName, 65 | 'comic_url_raw': comicUrlRaw, 66 | 'comic_url': comicUrl, 67 | 'status': status, 68 | 'chapter_url_raw': chapterUrlRaw, 69 | 'chapter_url': chapterUrl, 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /lib/models/comment/comment_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | /// 动漫之家评论接口太TM混乱了 4 | /// 使用此类统一Model 5 | 6 | class CommentItem { 7 | CommentItem({ 8 | required this.id, 9 | required this.objId, 10 | required this.content, 11 | required this.avatarUrl, 12 | required this.createTime, 13 | required this.images, 14 | required this.likeAmount, 15 | required this.nickname, 16 | required this.replyAmount, 17 | required this.userId, 18 | required this.gender, 19 | required this.type, 20 | required this.originId, 21 | this.isEmpty = false, 22 | }); 23 | 24 | factory CommentItem.createEmpty() { 25 | return CommentItem( 26 | id: 0, 27 | objId: 0, 28 | content: "该评论不存在,可能已被删除", 29 | avatarUrl: "", 30 | createTime: 0, 31 | images: [], 32 | likeAmount: 0.obs, 33 | nickname: "-", 34 | replyAmount: 0, 35 | userId: 0, 36 | gender: 0, 37 | type: 0, 38 | originId: 0, 39 | isEmpty: true, 40 | ); 41 | } 42 | 43 | int id; 44 | int objId; 45 | String content; 46 | int createTime; 47 | Rx likeAmount; 48 | int replyAmount; 49 | String nickname; 50 | String avatarUrl; 51 | List images; 52 | int userId; 53 | List parents = []; 54 | bool isEmpty; 55 | int gender; 56 | int type; 57 | int originId; 58 | } 59 | -------------------------------------------------------------------------------- /lib/models/db/comic_download_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/models/db/download_status.dart'; 2 | import 'package:hive/hive.dart'; 3 | part 'comic_download_info.g.dart'; 4 | 5 | @HiveType(typeId: 3) 6 | class ComicDownloadInfo { 7 | ComicDownloadInfo({ 8 | required this.addTime, 9 | required this.chapterId, 10 | required this.chapterSort, 11 | required this.comicCover, 12 | required this.comicId, 13 | required this.comicName, 14 | required this.files, 15 | required this.index, 16 | required this.savePath, 17 | required this.status, 18 | required this.taskId, 19 | required this.total, 20 | required this.volumeName, 21 | required this.urls, 22 | required this.chapterName, 23 | required this.isVip, 24 | required this.isLongComic, 25 | }); 26 | 27 | ///TaskID 任务,由漫画ID_章节ID组成 28 | @HiveField(0) 29 | String taskId; 30 | 31 | ///ComicID 漫画ID 32 | @HiveField(1) 33 | int comicId; 34 | 35 | ///ComicName 漫画名称 36 | @HiveField(2) 37 | String comicName; 38 | 39 | ///ComicCover 漫画封面 40 | @HiveField(3) 41 | String comicCover; 42 | 43 | ///ChapterID 章节ID 44 | @HiveField(4) 45 | int chapterId; 46 | 47 | @HiveField(5) 48 | String chapterName; 49 | 50 | ///VoulmeName 分卷名称 51 | @HiveField(6) 52 | String volumeName; 53 | 54 | ///ChapterSort 排序 55 | @HiveField(7) 56 | int chapterSort; 57 | 58 | ///SavePath 存储路径 59 | @HiveField(8) 60 | String savePath; 61 | 62 | ///Files 文件列表 63 | @HiveField(9) 64 | List files; 65 | 66 | ///Index 当前下载页数 67 | @HiveField(10) 68 | int index; 69 | 70 | ///Total 总计页数 71 | @HiveField(11) 72 | int total; 73 | 74 | ///Status 当前状态 75 | @HiveField(12) 76 | DownloadStatus status; 77 | 78 | ///AddTime 任务时间 79 | @HiveField(13) 80 | DateTime addTime; 81 | 82 | /// 下载图片链接 83 | @HiveField(14) 84 | List urls; 85 | 86 | /// 是否VIP章节 87 | /// * 暂时没啥用,总之先加上 88 | @HiveField(15) 89 | bool isVip; 90 | 91 | /// 是否为条漫 92 | @HiveField(16) 93 | bool isLongComic; 94 | } 95 | -------------------------------------------------------------------------------- /lib/models/db/comic_history.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | part 'comic_history.g.dart'; 3 | 4 | @HiveType(typeId: 1) 5 | class ComicHistory { 6 | ComicHistory({ 7 | required this.comicId, 8 | required this.chapterId, 9 | required this.comicName, 10 | required this.comicCover, 11 | required this.chapterName, 12 | required this.updateTime, 13 | required this.page, 14 | }); 15 | 16 | @HiveField(0) 17 | int comicId; 18 | 19 | @HiveField(1) 20 | int chapterId; 21 | 22 | @HiveField(2) 23 | String comicName; 24 | 25 | @HiveField(3) 26 | String comicCover; 27 | 28 | @HiveField(4) 29 | String chapterName; 30 | 31 | @HiveField(5) 32 | int page; 33 | 34 | @HiveField(6) 35 | DateTime updateTime; 36 | } 37 | -------------------------------------------------------------------------------- /lib/models/db/comic_history.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'comic_history.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class ComicHistoryAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 1; 12 | 13 | @override 14 | ComicHistory read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return ComicHistory( 20 | comicId: fields[0] as int, 21 | chapterId: fields[1] as int, 22 | comicName: fields[2] as String, 23 | comicCover: fields[3] as String, 24 | chapterName: fields[4] as String, 25 | updateTime: fields[6] as DateTime, 26 | page: fields[5] as int, 27 | ); 28 | } 29 | 30 | @override 31 | void write(BinaryWriter writer, ComicHistory obj) { 32 | writer 33 | ..writeByte(7) 34 | ..writeByte(0) 35 | ..write(obj.comicId) 36 | ..writeByte(1) 37 | ..write(obj.chapterId) 38 | ..writeByte(2) 39 | ..write(obj.comicName) 40 | ..writeByte(3) 41 | ..write(obj.comicCover) 42 | ..writeByte(4) 43 | ..write(obj.chapterName) 44 | ..writeByte(5) 45 | ..write(obj.page) 46 | ..writeByte(6) 47 | ..write(obj.updateTime); 48 | } 49 | 50 | @override 51 | int get hashCode => typeId.hashCode; 52 | 53 | @override 54 | bool operator ==(Object other) => 55 | identical(this, other) || 56 | other is ComicHistoryAdapter && 57 | runtimeType == other.runtimeType && 58 | typeId == other.typeId; 59 | } 60 | -------------------------------------------------------------------------------- /lib/models/db/download_status.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | part 'download_status.g.dart'; 3 | 4 | /// 下载状态 5 | @HiveType(typeId: 4) 6 | enum DownloadStatus { 7 | /// 等待下载中 8 | @HiveField(0) 9 | wait, 10 | 11 | /// 正在读取章节信息 12 | @HiveField(1) 13 | loadding, 14 | 15 | /// 下载中 16 | @HiveField(2) 17 | downloading, 18 | 19 | /// 使用数据,自动暂停,当网络切换时恢复下载 20 | @HiveField(3) 21 | pauseCellular, 22 | 23 | /// 暂停 24 | @HiveField(4) 25 | pause, 26 | 27 | /// 已完成 28 | @HiveField(5) 29 | complete, 30 | 31 | /// 读取信息时出现错误 32 | @HiveField(6) 33 | errorLoad, 34 | 35 | /// 下载出错 36 | @HiveField(7) 37 | error, 38 | 39 | /// 已取消 40 | @HiveField(8) 41 | cancel, 42 | 43 | /// 等待网络连接 44 | @HiveField(9) 45 | waitNetwork, 46 | } 47 | -------------------------------------------------------------------------------- /lib/models/db/local_favorite.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:hive/hive.dart'; 3 | part 'local_favorite.g.dart'; 4 | 5 | @HiveType(typeId: 6) 6 | class LocalFavorite { 7 | LocalFavorite({ 8 | required this.id, 9 | required this.objId, 10 | required this.title, 11 | required this.cover, 12 | required this.type, 13 | required this.updateTime, 14 | }); 15 | 16 | @HiveField(0) 17 | String id; 18 | 19 | String get hiveId => "${type}_$objId"; 20 | 21 | @HiveField(1) 22 | int objId; 23 | 24 | @HiveField(2) 25 | String title; 26 | 27 | @HiveField(3) 28 | String cover; 29 | 30 | /// 类型,对应app_constant,漫画或小说 31 | @HiveField(4) 32 | int type; 33 | 34 | @HiveField(5) 35 | DateTime updateTime; 36 | 37 | //是否被选中 38 | Rx isChecked = false.obs; 39 | } 40 | -------------------------------------------------------------------------------- /lib/models/db/local_favorite.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'local_favorite.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class LocalFavoriteAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 6; 12 | 13 | @override 14 | LocalFavorite read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return LocalFavorite( 20 | id: fields[0] as String, 21 | objId: fields[1] as int, 22 | title: fields[2] as String, 23 | cover: fields[3] as String, 24 | type: fields[4] as int, 25 | updateTime: fields[5] as DateTime, 26 | ); 27 | } 28 | 29 | @override 30 | void write(BinaryWriter writer, LocalFavorite obj) { 31 | writer 32 | ..writeByte(6) 33 | ..writeByte(0) 34 | ..write(obj.id) 35 | ..writeByte(1) 36 | ..write(obj.objId) 37 | ..writeByte(2) 38 | ..write(obj.title) 39 | ..writeByte(3) 40 | ..write(obj.cover) 41 | ..writeByte(4) 42 | ..write(obj.type) 43 | ..writeByte(5) 44 | ..write(obj.updateTime); 45 | } 46 | 47 | @override 48 | int get hashCode => typeId.hashCode; 49 | 50 | @override 51 | bool operator ==(Object other) => 52 | identical(this, other) || 53 | other is LocalFavoriteAdapter && 54 | runtimeType == other.runtimeType && 55 | typeId == other.typeId; 56 | } 57 | -------------------------------------------------------------------------------- /lib/models/db/novel_history.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | part 'novel_history.g.dart'; 3 | 4 | @HiveType(typeId: 2) 5 | class NovelHistory { 6 | NovelHistory({ 7 | required this.novelId, 8 | required this.chapterId, 9 | required this.novelName, 10 | required this.novelCover, 11 | required this.chapterName, 12 | required this.updateTime, 13 | required this.index, 14 | required this.total, 15 | required this.volumeId, 16 | required this.volumeName, 17 | }); 18 | 19 | @HiveField(0) 20 | int novelId; 21 | 22 | @HiveField(1) 23 | int chapterId; 24 | 25 | @HiveField(2) 26 | String novelName; 27 | 28 | @HiveField(3) 29 | String novelCover; 30 | 31 | @HiveField(4) 32 | String chapterName; 33 | 34 | @HiveField(5) 35 | int index; 36 | 37 | @HiveField(6) 38 | int total; 39 | 40 | @HiveField(7) 41 | int volumeId; 42 | 43 | @HiveField(8) 44 | String volumeName; 45 | 46 | @HiveField(9) 47 | DateTime updateTime; 48 | } 49 | -------------------------------------------------------------------------------- /lib/models/db/novel_history.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'novel_history.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class NovelHistoryAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 2; 12 | 13 | @override 14 | NovelHistory read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return NovelHistory( 20 | novelId: fields[0] as int, 21 | chapterId: fields[1] as int, 22 | novelName: fields[2] as String, 23 | novelCover: fields[3] as String, 24 | chapterName: fields[4] as String, 25 | updateTime: fields[9] as DateTime, 26 | index: fields[5] as int, 27 | total: fields[6] as int, 28 | volumeId: fields[7] as int, 29 | volumeName: fields[8] as String, 30 | ); 31 | } 32 | 33 | @override 34 | void write(BinaryWriter writer, NovelHistory obj) { 35 | writer 36 | ..writeByte(10) 37 | ..writeByte(0) 38 | ..write(obj.novelId) 39 | ..writeByte(1) 40 | ..write(obj.chapterId) 41 | ..writeByte(2) 42 | ..write(obj.novelName) 43 | ..writeByte(3) 44 | ..write(obj.novelCover) 45 | ..writeByte(4) 46 | ..write(obj.chapterName) 47 | ..writeByte(5) 48 | ..write(obj.index) 49 | ..writeByte(6) 50 | ..write(obj.total) 51 | ..writeByte(7) 52 | ..write(obj.volumeId) 53 | ..writeByte(8) 54 | ..write(obj.volumeName) 55 | ..writeByte(9) 56 | ..write(obj.updateTime); 57 | } 58 | 59 | @override 60 | int get hashCode => typeId.hashCode; 61 | 62 | @override 63 | bool operator ==(Object other) => 64 | identical(this, other) || 65 | other is NovelHistoryAdapter && 66 | runtimeType == other.runtimeType && 67 | typeId == other.typeId; 68 | } 69 | -------------------------------------------------------------------------------- /lib/models/news/news_banner_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class NewsBannerModel { 11 | NewsBannerModel({ 12 | required this.id, 13 | required this.title, 14 | required this.picUrl, 15 | this.objectId, 16 | this.objectUrl, 17 | }); 18 | 19 | factory NewsBannerModel.fromJson(Map json) => 20 | NewsBannerModel( 21 | id: asT(json['id'])!, 22 | title: asT(json['title'])!, 23 | picUrl: asT(json['pic_url'])!, 24 | objectId: asT(json['object_id']), 25 | objectUrl: asT(json['object_url']), 26 | ); 27 | 28 | int id; 29 | String title; 30 | String picUrl; 31 | int? objectId; 32 | String? objectUrl; 33 | 34 | @override 35 | String toString() { 36 | return jsonEncode(this); 37 | } 38 | 39 | Map toJson() => { 40 | 'id': id, 41 | 'title': title, 42 | 'pic_url': picUrl, 43 | 'object_id': objectId, 44 | 'object_url': objectUrl, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /lib/models/news/news_stat_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class NewsStatModel { 11 | NewsStatModel({ 12 | required this.commentAmount, 13 | required this.moodAmount, 14 | required this.rowPicUrl, 15 | required this.title, 16 | }); 17 | 18 | factory NewsStatModel.fromJson(Map json) => NewsStatModel( 19 | /// DMZJ后端是真混乱... commentAmount是string,mood_amount是int 20 | commentAmount: int.tryParse(json['comment_amount'].toString()) ?? 0, 21 | moodAmount: int.tryParse(json['mood_amount'].toString()) ?? 0, 22 | rowPicUrl: asT(json['row_pic_url'])!, 23 | title: asT(json['title'])!, 24 | ); 25 | 26 | int commentAmount; 27 | int moodAmount; 28 | String rowPicUrl; 29 | String title; 30 | 31 | @override 32 | String toString() { 33 | return jsonEncode(this); 34 | } 35 | 36 | Map toJson() => { 37 | 'comment_amount': commentAmount, 38 | 'mood_amount': moodAmount, 39 | 'row_pic_url': rowPicUrl, 40 | 'title': title, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /lib/models/news/news_tag_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class NewsTagModel { 11 | NewsTagModel({ 12 | required this.tagId, 13 | required this.tagName, 14 | }); 15 | 16 | factory NewsTagModel.fromJson(Map json) => NewsTagModel( 17 | tagId: asT(json['tag_id'])!, 18 | tagName: asT(json['tag_name'])!, 19 | ); 20 | 21 | int tagId; 22 | String tagName; 23 | 24 | @override 25 | String toString() { 26 | return jsonEncode(this); 27 | } 28 | 29 | Map toJson() => { 30 | 'tag_id': tagId, 31 | 'tag_name': tagName, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /lib/models/novel/category_filter_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | T? asT(dynamic value) { 6 | if (value is T) { 7 | return value; 8 | } 9 | return null; 10 | } 11 | 12 | class NovelCategoryFilterModel { 13 | NovelCategoryFilterModel({ 14 | required this.title, 15 | required this.items, 16 | }); 17 | 18 | factory NovelCategoryFilterModel.fromJson(Map json) { 19 | final List? items = 20 | json['items'] is List ? [] : null; 21 | if (items != null) { 22 | for (final dynamic item in json['items']!) { 23 | if (item != null) { 24 | items.add(NovelCategoryFilterItemModel.fromJson( 25 | asT>(item)!)); 26 | } 27 | } 28 | } 29 | return NovelCategoryFilterModel( 30 | title: asT(json['title'])!, 31 | items: items!, 32 | ); 33 | } 34 | 35 | String title; 36 | List items; 37 | var selectId = 0.obs; 38 | @override 39 | String toString() { 40 | return jsonEncode(this); 41 | } 42 | 43 | Map toJson() => { 44 | 'title': title, 45 | 'items': items, 46 | }; 47 | } 48 | 49 | class NovelCategoryFilterItemModel { 50 | NovelCategoryFilterItemModel({ 51 | required this.tagId, 52 | required this.tagName, 53 | }); 54 | 55 | factory NovelCategoryFilterItemModel.fromJson(Map json) => 56 | NovelCategoryFilterItemModel( 57 | tagId: asT(json['tag_id'])!, 58 | tagName: asT(json['tag_name'])!, 59 | ); 60 | 61 | int tagId; 62 | String tagName; 63 | 64 | @override 65 | String toString() { 66 | return jsonEncode(this); 67 | } 68 | 69 | Map toJson() => { 70 | 'tag_id': tagId, 71 | 'tag_name': tagName, 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /lib/models/novel/category_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class NovelCategoryModel { 11 | NovelCategoryModel({ 12 | required this.tagId, 13 | required this.title, 14 | required this.cover, 15 | }); 16 | 17 | factory NovelCategoryModel.fromJson(Map json) => 18 | NovelCategoryModel( 19 | tagId: asT(json['tag_id'])!, 20 | title: asT(json['title'])!, 21 | cover: asT(json['cover'])!, 22 | ); 23 | 24 | int tagId; 25 | String title; 26 | String cover; 27 | 28 | @override 29 | String toString() { 30 | return jsonEncode(this); 31 | } 32 | 33 | Map toJson() => { 34 | 'tag_id': tagId, 35 | 'title': title, 36 | 'cover': cover, 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /lib/models/novel/category_novel_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class NovelCategoryNovelModel { 11 | NovelCategoryNovelModel({ 12 | required this.cover, 13 | required this.name, 14 | required this.authors, 15 | required this.id, 16 | }); 17 | 18 | factory NovelCategoryNovelModel.fromJson(Map json) => 19 | NovelCategoryNovelModel( 20 | cover: asT(json['cover'])!, 21 | name: asT(json['name'])!, 22 | authors: asT(json['authors'])!, 23 | id: asT(json['id'])!, 24 | ); 25 | 26 | String cover; 27 | String name; 28 | String authors; 29 | int id; 30 | 31 | @override 32 | String toString() { 33 | return jsonEncode(this); 34 | } 35 | 36 | Map toJson() => { 37 | 'cover': cover, 38 | 'name': name, 39 | 'authors': authors, 40 | 'id': id, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /lib/models/novel/rank_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class NovelRankModel { 11 | NovelRankModel({ 12 | required this.id, 13 | required this.lastUpdateTime, 14 | required this.name, 15 | required this.types, 16 | required this.cover, 17 | required this.authors, 18 | required this.lastUpdateChapterName, 19 | required this.top, 20 | required this.subscribeAmount, 21 | }); 22 | 23 | factory NovelRankModel.fromJson(Map json) { 24 | final List? types = json['types'] is List ? [] : null; 25 | if (types != null) { 26 | for (final dynamic item in json['types']!) { 27 | if (item != null) { 28 | types.add(asT(item)!); 29 | } 30 | } 31 | } 32 | return NovelRankModel( 33 | id: asT(json['id'])!, 34 | lastUpdateTime: asT(json['last_update_time'])!, 35 | name: asT(json['name'])!, 36 | types: types!, 37 | cover: asT(json['cover'])!, 38 | authors: asT(json['authors'])!, 39 | lastUpdateChapterName: asT(json['last_update_chapter_name'])!, 40 | top: asT(json['top'])!, 41 | subscribeAmount: asT(json['subscribe_amount'])!, 42 | ); 43 | } 44 | 45 | int id; 46 | int lastUpdateTime; 47 | String name; 48 | List types; 49 | String cover; 50 | String authors; 51 | String lastUpdateChapterName; 52 | int top; 53 | int subscribeAmount; 54 | 55 | @override 56 | String toString() { 57 | return jsonEncode(this); 58 | } 59 | 60 | Map toJson() => { 61 | 'id': id, 62 | 'last_update_time': lastUpdateTime, 63 | 'name': name, 64 | 'types': types, 65 | 'cover': cover, 66 | 'authors': authors, 67 | 'last_update_chapter_name': lastUpdateChapterName, 68 | 'top': top, 69 | 'subscribe_amount': subscribeAmount, 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /lib/models/novel/search_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class NovelSearchModel { 11 | NovelSearchModel({ 12 | required this.biz, 13 | required this.addtime, 14 | required this.authors, 15 | required this.copyright, 16 | required this.cover, 17 | required this.hidden, 18 | required this.hotHits, 19 | required this.lastName, 20 | required this.status, 21 | required this.title, 22 | required this.types, 23 | required this.id, 24 | }); 25 | 26 | factory NovelSearchModel.fromJson(Map json) => 27 | NovelSearchModel( 28 | biz: asT(json['_biz'])!, 29 | addtime: asT(json['addtime'])!, 30 | authors: asT(json['authors'])!, 31 | copyright: asT(json['copyright'])!, 32 | cover: asT(json['cover'])!, 33 | hidden: asT(json['hidden'])!, 34 | hotHits: asT(json['hot_hits'])!, 35 | lastName: asT(json['last_name'])!, 36 | status: asT(json['status'])!, 37 | title: asT(json['title'])!, 38 | types: asT(json['types'])!, 39 | id: asT(json['id'])!, 40 | ); 41 | 42 | String biz; 43 | int addtime; 44 | String authors; 45 | int copyright; 46 | String cover; 47 | int hidden; 48 | int hotHits; 49 | String lastName; 50 | int status; 51 | String title; 52 | String types; 53 | int id; 54 | 55 | @override 56 | String toString() { 57 | return jsonEncode(this); 58 | } 59 | 60 | Map toJson() => { 61 | '_biz': biz, 62 | 'addtime': addtime, 63 | 'authors': authors, 64 | 'copyright': copyright, 65 | 'cover': cover, 66 | 'hidden': hidden, 67 | 'hot_hits': hotHits, 68 | 'last_name': lastName, 69 | 'status': status, 70 | 'title': title, 71 | 'types': types, 72 | 'id': id, 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /lib/models/user/bind_status_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class UserBindStatusModel { 11 | UserBindStatusModel({ 12 | required this.isBindTel, 13 | required this.isSetPwd, 14 | }); 15 | 16 | factory UserBindStatusModel.fromJson(Map json) => 17 | UserBindStatusModel( 18 | isBindTel: asT(json['is_bind_tel'])!, 19 | isSetPwd: asT(json['is_set_pwd'])!, 20 | ); 21 | 22 | int isBindTel; 23 | int isSetPwd; 24 | 25 | @override 26 | String toString() { 27 | return jsonEncode(this); 28 | } 29 | 30 | Map toJson() => { 31 | 'is_bind_tel': isBindTel, 32 | 'is_set_pwd': isSetPwd, 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /lib/models/user/comic_history_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class UserComicHistoryModel { 11 | UserComicHistoryModel({ 12 | this.uid, 13 | this.type, 14 | required this.comicId, 15 | this.chapterId, 16 | this.record, 17 | this.viewingTime, 18 | required this.comicName, 19 | required this.cover, 20 | this.chapterName, 21 | }); 22 | 23 | factory UserComicHistoryModel.fromJson(Map json) => 24 | // 接口不知道那些可能为空,所以全部变为可空 25 | UserComicHistoryModel( 26 | uid: asT(json['uid']) ?? 0, 27 | type: asT(json['type']) ?? 0, 28 | comicId: asT(json['comic_id']) ?? 0, 29 | chapterId: asT(json['chapter_id']) ?? 0, 30 | record: asT(json['record']) ?? 0, 31 | viewingTime: asT(json['viewing_time']) ?? 0, 32 | comicName: asT(json['comic_name']) ?? "未知漫画", 33 | cover: asT(json['cover']) ?? "", 34 | chapterName: asT(json['chapter_name']) ?? "-", 35 | ); 36 | 37 | int? uid; 38 | int? type; 39 | int comicId; 40 | int? chapterId; 41 | int? record; 42 | int? viewingTime; 43 | String comicName; 44 | String cover; 45 | String? chapterName; 46 | 47 | @override 48 | String toString() { 49 | return jsonEncode(this); 50 | } 51 | 52 | Map toJson() => { 53 | 'uid': uid, 54 | 'type': type, 55 | 'comic_id': comicId, 56 | 'chapter_id': chapterId, 57 | 'record': record, 58 | 'viewing_time': viewingTime, 59 | 'comic_name': comicName, 60 | 'cover': cover, 61 | 'chapter_name': chapterName, 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /lib/models/user/login_result_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class LoginResultModel { 11 | LoginResultModel({ 12 | required this.uid, 13 | required this.nickname, 14 | required this.dmzjToken, 15 | required this.photo, 16 | required this.bindPhone, 17 | required this.email, 18 | required this.passwd, 19 | required this.cookieVal, 20 | }); 21 | 22 | factory LoginResultModel.fromJson(Map json) => 23 | LoginResultModel( 24 | uid: asT(json['uid'])!, 25 | nickname: asT(json['nickname'])!, 26 | dmzjToken: asT(json['dmzj_token'])!, 27 | photo: asT(json['photo'])!, 28 | bindPhone: asT(json['bind_phone'])!, 29 | email: asT(json['email'])!, 30 | passwd: asT(json['passwd'])!, 31 | cookieVal: asT(json['cookie_val'])!, 32 | ); 33 | 34 | String uid; 35 | String nickname; 36 | String dmzjToken; 37 | String photo; 38 | String bindPhone; 39 | String email; 40 | String passwd; 41 | String cookieVal; 42 | 43 | @override 44 | String toString() { 45 | return jsonEncode(this); 46 | } 47 | 48 | Map toJson() => { 49 | 'uid': uid, 50 | 'nickname': nickname, 51 | 'dmzj_token': dmzjToken, 52 | 'photo': photo, 53 | 'bind_phone': bindPhone, 54 | 'email': email, 55 | 'passwd': passwd, 56 | 'cookie_val': cookieVal, 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /lib/models/user/novel_history_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class UserNovelHistoryModel { 11 | UserNovelHistoryModel({ 12 | this.uid, 13 | this.type, 14 | required this.lnovelId, 15 | this.volumeId, 16 | this.chapterId, 17 | this.record, 18 | this.viewingTime, 19 | this.totalNum, 20 | required this.cover, 21 | required this.novelName, 22 | this.volumeName, 23 | this.chapterName, 24 | }); 25 | 26 | factory UserNovelHistoryModel.fromJson(Map json) => 27 | // 接口不知道那些可能为空,所以全部变为可空 28 | UserNovelHistoryModel( 29 | uid: asT(json['uid']) ?? 0, 30 | type: asT(json['type']) ?? 0, 31 | lnovelId: int.tryParse(json['lnovel_id'].toString()) ?? 0, 32 | volumeId: asT(json['volume_id']) ?? 0, 33 | chapterId: asT(json['chapter_id']) ?? 0, 34 | record: asT(json['record']) ?? 0, 35 | viewingTime: asT(json['viewing_time']) ?? 0, 36 | totalNum: asT(json['total_num']) ?? 0, 37 | cover: asT(json['cover']) ?? "", 38 | novelName: asT(json['novel_name']) ?? "未知小说", 39 | volumeName: asT(json['volume_name']) ?? "-", 40 | chapterName: asT(json['chapter_name']) ?? "-", 41 | ); 42 | 43 | int? uid; 44 | int? type; 45 | int lnovelId; 46 | int? volumeId; 47 | int? chapterId; 48 | int? record; 49 | int? viewingTime; 50 | int? totalNum; 51 | String cover; 52 | String novelName; 53 | String? volumeName; 54 | String? chapterName; 55 | 56 | @override 57 | String toString() { 58 | return jsonEncode(this); 59 | } 60 | 61 | Map toJson() => { 62 | 'uid': uid, 63 | 'type': type, 64 | 'lnovel_id': lnovelId, 65 | 'volume_id': volumeId, 66 | 'chapter_id': chapterId, 67 | 'record': record, 68 | 'viewing_time': viewingTime, 69 | 'total_num': totalNum, 70 | 'cover': cover, 71 | 'novel_name': novelName, 72 | 'volume_name': volumeName, 73 | 'chapter_name': chapterName, 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /lib/models/user/subscribe_comic_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | T? asT(dynamic value) { 6 | if (value is T) { 7 | return value; 8 | } 9 | return null; 10 | } 11 | 12 | class UserSubscribeComicModel { 13 | UserSubscribeComicModel({ 14 | required this.name, 15 | required this.subUpdate, 16 | required this.subImg, 17 | required this.subUptime, 18 | required this.subFirstLetter, 19 | required this.subReaded, 20 | required this.id, 21 | required this.status, 22 | required this.hasNew, 23 | }); 24 | 25 | factory UserSubscribeComicModel.fromJson(Map json) => 26 | UserSubscribeComicModel( 27 | name: asT(json['name'])!, 28 | subUpdate: asT(json['sub_update'])!, 29 | subImg: asT(json['sub_img'])!, 30 | subUptime: asT(json['sub_uptime'])!, 31 | subFirstLetter: asT(json['sub_first_letter'])!, 32 | subReaded: asT(json['sub_readed'])!, 33 | id: asT(json['id'])!, 34 | status: asT(json['status'])!, 35 | hasNew: (asT(json['sub_readed']) == 0).obs, 36 | ); 37 | 38 | String name; 39 | String subUpdate; 40 | String subImg; 41 | int subUptime; 42 | String subFirstLetter; 43 | int subReaded; 44 | int id; 45 | String status; 46 | 47 | //是否有新的更新 48 | Rx hasNew = false.obs; 49 | 50 | //是否被选中 51 | Rx isChecked = false.obs; 52 | 53 | @override 54 | String toString() { 55 | return jsonEncode(this); 56 | } 57 | 58 | Map toJson() => { 59 | 'name': name, 60 | 'sub_update': subUpdate, 61 | 'sub_img': subImg, 62 | 'sub_uptime': subUptime, 63 | 'sub_first_letter': subFirstLetter, 64 | 'sub_readed': subReaded, 65 | 'id': id, 66 | 'status': status, 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /lib/models/user/subscribe_novel_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | T? asT(dynamic value) { 6 | if (value is T) { 7 | return value; 8 | } 9 | return null; 10 | } 11 | 12 | class UserSubscribeNovelModel { 13 | UserSubscribeNovelModel({ 14 | required this.name, 15 | required this.subUpdate, 16 | required this.subImg, 17 | required this.subUptime, 18 | required this.subFirstLetter, 19 | required this.subReaded, 20 | required this.id, 21 | required this.status, 22 | required this.hasNew, 23 | }); 24 | 25 | factory UserSubscribeNovelModel.fromJson(Map json) => 26 | UserSubscribeNovelModel( 27 | name: asT(json['name'])!, 28 | subUpdate: asT(json['sub_update'])!, 29 | subImg: asT(json['sub_img'])!, 30 | subUptime: asT(json['sub_uptime'])!, 31 | subFirstLetter: asT(json['sub_first_letter'])!, 32 | subReaded: asT(json['sub_readed'])!, 33 | id: asT(json['id'])!, 34 | status: asT(json['status'])!, 35 | hasNew: (asT(json['sub_readed']) == 0).obs, 36 | ); 37 | 38 | String name; 39 | String subUpdate; 40 | String subImg; 41 | int subUptime; 42 | String subFirstLetter; 43 | int subReaded; 44 | int id; 45 | String status; 46 | 47 | //是否有新的更新 48 | Rx hasNew = false.obs; 49 | 50 | //是否被选中 51 | Rx isChecked = false.obs; 52 | 53 | @override 54 | String toString() { 55 | return jsonEncode(this); 56 | } 57 | 58 | Map toJson() => { 59 | 'name': name, 60 | 'sub_update': subUpdate, 61 | 'sub_img': subImg, 62 | 'sub_uptime': subUptime, 63 | 'sub_first_letter': subFirstLetter, 64 | 'sub_readed': subReaded, 65 | 'id': id, 66 | 'status': status, 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /lib/models/version_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | T? asT(dynamic value) { 4 | if (value is T) { 5 | return value; 6 | } 7 | return null; 8 | } 9 | 10 | class VersionModel { 11 | VersionModel({ 12 | required this.version, 13 | required this.versionNum, 14 | required this.versionDesc, 15 | required this.downloadUrl, 16 | }); 17 | 18 | factory VersionModel.fromJson(Map json) => VersionModel( 19 | version: asT(json['version'])!, 20 | versionNum: asT(json['version_num'])!, 21 | versionDesc: asT(json['version_desc'])!, 22 | downloadUrl: asT(json['download_url'])!, 23 | ); 24 | 25 | String version; 26 | int versionNum; 27 | String versionDesc; 28 | String downloadUrl; 29 | 30 | @override 31 | String toString() { 32 | return jsonEncode(this); 33 | } 34 | 35 | Map toJson() => { 36 | 'version': version, 37 | 'version_num': versionNum, 38 | 'version_desc': versionDesc, 39 | 'download_url': downloadUrl, 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /lib/modules/comic/author_detail/author_detail_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/app_constant.dart'; 2 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 3 | import 'package:flutter_dmzj/models/comic/author_model.dart'; 4 | import 'package:flutter_dmzj/requests/comic_request.dart'; 5 | import 'package:flutter_dmzj/services/user_service.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | class ComicAuthorDetailController extends BaseController { 9 | final int id; 10 | ComicAuthorDetailController(this.id); 11 | 12 | final ComicRequest request = ComicRequest(); 13 | 14 | Rx detail = Rx(null); 15 | 16 | @override 17 | void onInit() { 18 | loadData(); 19 | super.onInit(); 20 | } 21 | 22 | void loadData() async { 23 | try { 24 | pageLoadding.value = true; 25 | pageError.value = false; 26 | var result = await request.authorDetail(id: id); 27 | detail.value = result; 28 | } catch (e) { 29 | pageError.value = true; 30 | errorMsg.value = e.toString(); 31 | } finally { 32 | pageLoadding.value = false; 33 | } 34 | } 35 | 36 | void subscribeAll() { 37 | if (detail.value == null) { 38 | return; 39 | } 40 | UserService.instance.addSubscribe( 41 | detail.value!.data.map((e) => e.id).toList(), 42 | AppConstant.kTypeComic, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/modules/comic/home/category/comic_category_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/comic/category_item_model.dart'; 3 | import 'package:flutter_dmzj/requests/comic_request.dart'; 4 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 5 | 6 | class ComicCategoryController 7 | extends BasePageController { 8 | final ComicRequest request = ComicRequest(); 9 | 10 | @override 11 | Future> getData(int page, int pageSize) async { 12 | if (page > 1) { 13 | return []; 14 | } 15 | var ls = await request.categores(); 16 | 17 | return ls; 18 | } 19 | 20 | void toDetail(ComicCategoryItemModel item) { 21 | AppNavigator.toComicCategoryDetail(item.tagId); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/modules/comic/home/category/comic_category_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:flutter_dmzj/modules/comic/home/category/comic_category_controller.dart'; 4 | import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart'; 5 | import 'package:flutter_dmzj/widgets/net_image.dart'; 6 | import 'package:flutter_dmzj/widgets/page_grid_view.dart'; 7 | import 'package:flutter_dmzj/widgets/shadow_card.dart'; 8 | import 'package:get/get.dart'; 9 | 10 | class ComicCategoryView extends StatelessWidget { 11 | final ComicCategoryController controller; 12 | ComicCategoryView({Key? key}) 13 | : controller = Get.put(ComicCategoryController()), 14 | super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return LayoutBuilder(builder: (context, constraints) { 19 | var count = constraints.maxWidth ~/ 160; 20 | if (count < 3) count = 3; 21 | return KeepAliveWrapper( 22 | child: PageGridView( 23 | pageController: controller, 24 | firstRefresh: true, 25 | loadMore: false, 26 | crossAxisCount: count, 27 | padding: AppStyle.edgeInsetsH12.copyWith(bottom: 12), 28 | mainAxisSpacing: 12, 29 | crossAxisSpacing: 12, 30 | itemBuilder: (context, i) { 31 | var item = controller.list[i]; 32 | return ShadowCard( 33 | onTap: () { 34 | controller.toDetail(item); 35 | }, 36 | child: Column( 37 | children: [ 38 | AspectRatio( 39 | aspectRatio: 1.0, 40 | child: NetImage( 41 | item.cover, 42 | borderRadius: 8, 43 | ), 44 | ), 45 | Padding( 46 | padding: AppStyle.edgeInsetsA8, 47 | child: Text( 48 | item.title, 49 | textAlign: TextAlign.center, 50 | style: const TextStyle(height: 1), 51 | ), 52 | ), 53 | ], 54 | ), 55 | ); 56 | }, 57 | ), 58 | ); 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/modules/comic/home/comic_home_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 5 | import 'package:flutter_dmzj/app/event_bus.dart'; 6 | import 'package:flutter_dmzj/modules/comic/home/category/comic_category_controller.dart'; 7 | import 'package:flutter_dmzj/modules/comic/home/latest/comic_latest_controller.dart'; 8 | import 'package:flutter_dmzj/modules/comic/home/rank/comic_rank_controller.dart'; 9 | import 'package:flutter_dmzj/modules/comic/home/recommend/comic_recommend_controller.dart'; 10 | import 'package:flutter_dmzj/modules/comic/home/special/comic_special_controller.dart'; 11 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 12 | import 'package:get/get.dart'; 13 | 14 | class ComicHomeController extends GetxController 15 | with GetTickerProviderStateMixin { 16 | late TabController tabController; 17 | 18 | StreamSubscription? streamSubscription; 19 | 20 | @override 21 | void onInit() { 22 | streamSubscription = EventBus.instance.listen( 23 | EventBus.kBottomNavigationBarClicked, 24 | (index) { 25 | if (index == 0) { 26 | refreshOrScrollTop(); 27 | } 28 | }, 29 | ); 30 | tabController = TabController(length: 5, vsync: this); 31 | 32 | super.onInit(); 33 | } 34 | 35 | void refreshOrScrollTop() { 36 | var tabIndex = tabController.index; 37 | BasePageController? controller; 38 | if (tabIndex == 0) { 39 | controller = Get.find(); 40 | } else if (tabIndex == 1) { 41 | controller = Get.find(); 42 | } else if (tabIndex == 2) { 43 | controller = Get.find(); 44 | } else if (tabIndex == 3) { 45 | controller = Get.find(); 46 | } else if (tabIndex == 4) { 47 | controller = Get.find(); 48 | } 49 | controller?.scrollToTopOrRefresh(); 50 | } 51 | 52 | void search() { 53 | AppNavigator.toComicSearch(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/modules/comic/home/comic_home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/modules/comic/home/category/comic_category_view.dart'; 3 | import 'package:flutter_dmzj/modules/comic/home/comic_home_controller.dart'; 4 | import 'package:flutter_dmzj/modules/comic/home/latest/comic_latest_view.dart'; 5 | import 'package:flutter_dmzj/modules/comic/home/rank/comic_rank_view.dart'; 6 | import 'package:flutter_dmzj/modules/comic/home/recommend/comic_recommend_view.dart'; 7 | import 'package:flutter_dmzj/modules/comic/home/special/comic_special_view.dart'; 8 | import 'package:flutter_dmzj/widgets/tab_appbar.dart'; 9 | import 'package:get/get.dart'; 10 | 11 | class ComicHomePage extends GetView { 12 | const ComicHomePage({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Scaffold( 17 | appBar: TabAppBar( 18 | tabs: const [ 19 | Tab(text: "推荐"), 20 | Tab(text: "更新"), 21 | Tab(text: "分类"), 22 | Tab(text: "排行"), 23 | Tab(text: "专题"), 24 | ], 25 | controller: controller.tabController, 26 | action: IconButton( 27 | onPressed: controller.search, 28 | icon: const Icon( 29 | Icons.search, 30 | ), 31 | ), 32 | ), 33 | body: TabBarView( 34 | controller: controller.tabController, 35 | children: [ 36 | ComicRecommendView(), 37 | ComicLatestView(), 38 | ComicCategoryView(), 39 | ComicRankView(), 40 | ComicSpecialView(), 41 | ], 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/modules/comic/home/latest/comic_latest_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/proto/comic.pb.dart'; 3 | import 'package:flutter_dmzj/requests/comic_request.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | class ComicLatestController 7 | extends BasePageController { 8 | final ComicRequest request = ComicRequest(); 9 | Map types = { 10 | "全部漫画": 100, 11 | "原创漫画": 1, 12 | "译制漫画": 0, 13 | }; 14 | var type = 100.obs; 15 | 16 | @override 17 | Future> getData(int page, int pageSize) async { 18 | var ls = await request.latest(type: type.value, page: page); 19 | 20 | return ls; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/modules/comic/home/rank/comic_rank_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/proto/comic.pb.dart'; 3 | import 'package:flutter_dmzj/requests/comic_request.dart'; 4 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | class ComicRankController extends BasePageController { 8 | final ComicRequest request = ComicRequest(); 9 | RxMap tags = { 10 | 0: "全部分类", 11 | }.obs; 12 | var tag = 0.obs; 13 | 14 | Map byTimes = { 15 | 0: "日排行", 16 | 1: "周排行", 17 | 2: "月排行", 18 | 3: "总排行", 19 | }; 20 | var byTime = 0.obs; 21 | 22 | Map rankTypes = { 23 | 0: "人气排行", 24 | 1: "吐槽排行", 25 | 2: "订阅排行", 26 | }; 27 | var rankType = 0.obs; 28 | 29 | @override 30 | void onInit() { 31 | loadFilter(); 32 | super.onInit(); 33 | } 34 | 35 | void loadFilter() async { 36 | try { 37 | tags.value = await request.rankFilter(); 38 | } catch (e) { 39 | SmartDialog.showToast(e.toString()); 40 | } 41 | } 42 | 43 | @override 44 | Future> getData(int page, int pageSize) async { 45 | var ls = await request.rank( 46 | tagId: tag.value, 47 | byTime: byTime.value, 48 | rankType: rankType.value, 49 | page: page, 50 | ); 51 | 52 | return ls; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/modules/comic/home/special/comic_special_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/comic/special_model.dart'; 3 | import 'package:flutter_dmzj/requests/comic_request.dart'; 4 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 5 | 6 | class ComicSpecialController extends BasePageController { 7 | final ComicRequest request = ComicRequest(); 8 | 9 | @override 10 | Future> getData(int page, int pageSize) async { 11 | var ls = await request.special(page: page - 1); 12 | 13 | return ls; 14 | } 15 | 16 | void toDetail(ComicSpecialModel item) { 17 | if (item.pageType == 3) { 18 | AppNavigator.toSpecialDetail(item.id); 19 | } else { 20 | AppNavigator.toWebView(item.pageUrl); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/modules/comic/special_detail/special_detail_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/app_constant.dart'; 2 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 3 | import 'package:flutter_dmzj/app/utils.dart'; 4 | import 'package:flutter_dmzj/models/comic/special_detail_model.dart'; 5 | import 'package:flutter_dmzj/requests/comic_request.dart'; 6 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 7 | import 'package:flutter_dmzj/services/user_service.dart'; 8 | import 'package:get/get.dart'; 9 | 10 | class SpecialDetailController extends BaseController { 11 | final int id; 12 | SpecialDetailController(this.id); 13 | 14 | final ComicRequest request = ComicRequest(); 15 | 16 | Rx detail = Rx(null); 17 | 18 | @override 19 | void onInit() { 20 | loadData(); 21 | super.onInit(); 22 | } 23 | 24 | void loadData() async { 25 | try { 26 | pageLoadding.value = true; 27 | pageError.value = false; 28 | var result = await request.specialDetail(id: id); 29 | detail.value = result; 30 | } catch (e) { 31 | handleError(e, showPageError: true); 32 | } finally { 33 | pageLoadding.value = false; 34 | } 35 | } 36 | 37 | void subscribeAll() { 38 | if (detail.value == null) { 39 | return; 40 | } 41 | UserService.instance.addSubscribe( 42 | detail.value!.comics.map((e) => e.id).toList(), 43 | AppConstant.kTypeComic, 44 | ); 45 | } 46 | 47 | void share() { 48 | if (detail.value == null) { 49 | return; 50 | } 51 | Utils.share( 52 | "http://m.idmzj.com/zhuanti/${detail.value!.pageUrl}", 53 | content: detail.value?.title ?? "", 54 | ); 55 | } 56 | 57 | void comment() { 58 | if (detail.value == null) { 59 | return; 60 | } 61 | AppNavigator.toComment(objId: id, type: AppConstant.kTypeSpecial); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/modules/common/comment/add_comment_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 3 | import 'package:flutter_dmzj/models/comment/comment_item.dart'; 4 | import 'package:flutter_dmzj/requests/comment_request.dart'; 5 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 6 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 7 | 8 | class AddCommentController extends BaseController { 9 | final int type; 10 | final int objId; 11 | final CommentItem? replyItem; 12 | AddCommentController({ 13 | required this.objId, 14 | required this.type, 15 | this.replyItem, 16 | }); 17 | final CommentRequest request = CommentRequest(); 18 | final TextEditingController textEditingController = TextEditingController(); 19 | 20 | void submit() async { 21 | if (textEditingController.text.isEmpty) { 22 | SmartDialog.showToast("内容不能为空"); 23 | return; 24 | } 25 | try { 26 | SmartDialog.showLoading(); 27 | if (replyItem == null) { 28 | await request.sendComment( 29 | objId: objId, 30 | type: type, 31 | content: textEditingController.text, 32 | ); 33 | } else { 34 | await request.sendComment( 35 | objId: objId, 36 | type: type, 37 | content: textEditingController.text, 38 | toCommentId: replyItem!.id.toString(), 39 | originCommentId: replyItem!.originId.toString(), 40 | toUid: replyItem!.userId.toString(), 41 | ); 42 | } 43 | 44 | SmartDialog.showToast("发表成功"); 45 | AppNavigator.closePage(); 46 | } catch (e) { 47 | SmartDialog.showToast(e.toString()); 48 | } finally { 49 | SmartDialog.dismiss(status: SmartStatus.loading); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/modules/common/comment/add_comment_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:flutter_dmzj/models/comment/comment_item.dart'; 4 | import 'package:flutter_dmzj/modules/common/comment/add_comment_controller.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | class AddCommentPage extends StatelessWidget { 8 | final int type; 9 | final int objId; 10 | final CommentItem? replyItem; 11 | final AddCommentController controller; 12 | AddCommentPage({ 13 | Key? key, 14 | required this.objId, 15 | required this.type, 16 | this.replyItem, 17 | }) : controller = Get.put( 18 | AddCommentController(objId: objId, type: type, replyItem: replyItem), 19 | tag: DateTime.now().millisecondsSinceEpoch.toString(), 20 | ), 21 | super(key: key); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scaffold( 26 | appBar: AppBar( 27 | title: const Text("添加评论"), 28 | ), 29 | body: ListView( 30 | padding: AppStyle.edgeInsetsA12, 31 | children: [ 32 | Visibility( 33 | visible: replyItem != null, 34 | child: Container( 35 | decoration: BoxDecoration( 36 | color: Colors.grey.withOpacity(.2), 37 | borderRadius: AppStyle.radius4, 38 | ), 39 | margin: AppStyle.edgeInsetsB12, 40 | padding: AppStyle.edgeInsetsA8, 41 | child: Text("${replyItem?.nickname}:${replyItem?.content}"), 42 | ), 43 | ), 44 | TextField( 45 | controller: controller.textEditingController, 46 | decoration: const InputDecoration( 47 | hintText: "你想说点什么...", 48 | border: OutlineInputBorder(), 49 | ), 50 | onSubmitted: (e) { 51 | controller.submit(); 52 | }, 53 | minLines: 4, 54 | maxLines: 6, 55 | maxLength: 1000, 56 | ), 57 | AppStyle.vGap12, 58 | ElevatedButton( 59 | onPressed: controller.submit, 60 | child: const Text("发布"), 61 | ), 62 | ], 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/modules/common/comment/comment_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class CommentController extends GetxController 5 | with GetSingleTickerProviderStateMixin { 6 | final int type; 7 | final int objId; 8 | CommentController(this.type, this.objId); 9 | late TabController tabController = TabController(length: 2, vsync: this); 10 | } 11 | -------------------------------------------------------------------------------- /lib/modules/common/comment/comment_list_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/comment/comment_item.dart'; 3 | import 'package:flutter_dmzj/requests/comment_request.dart'; 4 | 5 | class CommentListController extends BasePageController { 6 | final int type; 7 | final int objId; 8 | final bool isHot; 9 | final CommentRequest request = CommentRequest(); 10 | CommentListController({ 11 | required this.type, 12 | required this.objId, 13 | required this.isHot, 14 | }); 15 | 16 | @override 17 | Future> getData(int page, int pageSize) async { 18 | if (isHot) { 19 | return await request.getHotComment( 20 | type: type, 21 | objId: objId, 22 | page: page, 23 | ); 24 | } else { 25 | return await request.getLatestComment( 26 | type: type, 27 | objId: objId, 28 | page: page, 29 | ); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/modules/common/comment/comment_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/modules/common/comment/comment_list_controller.dart'; 3 | import 'package:flutter_dmzj/widgets/comment_item_widget.dart'; 4 | import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart'; 5 | import 'package:flutter_dmzj/widgets/page_list_view.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | class CommentListView extends StatelessWidget { 9 | final int type; 10 | final int objId; 11 | final bool isHot; 12 | final CommentListController controller; 13 | CommentListView({ 14 | Key? key, 15 | required this.objId, 16 | required this.type, 17 | required this.isHot, 18 | }) : controller = Get.put( 19 | CommentListController(objId: objId, type: type, isHot: isHot), 20 | tag: "${objId}_${type}_${isHot ? 1 : 0}", 21 | ), 22 | super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return KeepAliveWrapper( 27 | child: PageListView( 28 | pageController: controller, 29 | firstRefresh: true, 30 | separatorBuilder: (context, i) => Divider( 31 | endIndent: 12, 32 | indent: 12, 33 | color: Colors.grey.withOpacity(.2), 34 | height: 4, 35 | ), 36 | itemBuilder: (context, i) { 37 | var item = controller.list[i]; 38 | return CommentItemWidget(item); 39 | }, 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/modules/common/comment/comment_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:flutter_dmzj/modules/common/comment/comment_list_view.dart'; 4 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | class CommentPage extends StatelessWidget { 8 | final int objId; 9 | final int type; 10 | const CommentPage({required this.objId, required this.type, Key? key}) 11 | : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return DefaultTabController( 16 | length: 2, 17 | child: Scaffold( 18 | appBar: AppBar( 19 | title: Container( 20 | alignment: Alignment.center, 21 | child: TabBar( 22 | isScrollable: true, 23 | labelPadding: AppStyle.edgeInsetsH24, 24 | tabAlignment: TabAlignment.start, 25 | indicatorSize: TabBarIndicatorSize.label, 26 | indicatorColor: Theme.of(context).colorScheme.primary, 27 | labelColor: Theme.of(context).colorScheme.primary, 28 | unselectedLabelColor: 29 | Get.isDarkMode ? Colors.white70 : Colors.black87, 30 | tabs: const [ 31 | Tab(text: "最新评论"), 32 | Tab(text: "热门评论"), 33 | ], 34 | ), 35 | ), 36 | ), 37 | body: TabBarView( 38 | children: [ 39 | CommentListView(objId: objId, type: type, isHot: false), 40 | CommentListView(objId: objId, type: type, isHot: true), 41 | ], 42 | ), 43 | floatingActionButton: FloatingActionButton( 44 | onPressed: () { 45 | AppNavigator.toAddComment(objId: objId, type: type); 46 | }, 47 | child: const Icon(Icons.add), 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/modules/common/download/comic/comic_download_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/modules/common/download/comic/comic_downloaded_view.dart'; 3 | import 'package:flutter_dmzj/modules/common/download/comic/comic_downloading_view.dart'; 4 | import 'package:flutter_dmzj/services/comic_download_service.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | class ComicDownloadPage extends StatelessWidget { 8 | final int type; 9 | const ComicDownloadPage(this.type, {super.key}); 10 | @override 11 | Widget build(BuildContext context) { 12 | return DefaultTabController( 13 | length: 2, 14 | initialIndex: type, 15 | child: Scaffold( 16 | appBar: AppBar( 17 | title: Container( 18 | alignment: Alignment.center, 19 | padding: const EdgeInsets.only(right: 56), 20 | child: TabBar( 21 | isScrollable: true, 22 | tabAlignment: TabAlignment.start, 23 | indicatorSize: TabBarIndicatorSize.label, 24 | indicatorColor: Theme.of(context).colorScheme.primary, 25 | labelColor: Theme.of(context).colorScheme.primary, 26 | unselectedLabelColor: 27 | Get.isDarkMode ? Colors.white70 : Colors.black87, 28 | tabs: [ 29 | const Tab(text: "已完成"), 30 | Obx( 31 | () => Tab( 32 | text: ComicDownloadService.instance.taskQueues.isEmpty 33 | ? "下载中" 34 | : "下载中(${ComicDownloadService.instance.taskQueues.length})"), 35 | ) 36 | ], 37 | ), 38 | ), 39 | ), 40 | body: const TabBarView( 41 | children: [ 42 | ComicDownloadedView(), 43 | ComicDownloadingView(), 44 | ], 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/modules/common/download/novel/novel_download_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/modules/common/download/novel/novel_downloaded_view.dart'; 3 | import 'package:flutter_dmzj/modules/common/download/novel/novel_downloading_view.dart'; 4 | import 'package:flutter_dmzj/services/novel_download_service.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | class NovelDownloadPage extends StatelessWidget { 8 | final int type; 9 | const NovelDownloadPage(this.type, {super.key}); 10 | @override 11 | Widget build(BuildContext context) { 12 | return DefaultTabController( 13 | length: 2, 14 | initialIndex: type, 15 | child: Scaffold( 16 | appBar: AppBar( 17 | title: Container( 18 | alignment: Alignment.center, 19 | padding: const EdgeInsets.only(right: 56), 20 | child: TabBar( 21 | isScrollable: true, 22 | tabAlignment: TabAlignment.start, 23 | indicatorSize: TabBarIndicatorSize.label, 24 | indicatorColor: Theme.of(context).colorScheme.primary, 25 | labelColor: Theme.of(context).colorScheme.primary, 26 | unselectedLabelColor: 27 | Get.isDarkMode ? Colors.white70 : Colors.black87, 28 | tabs: [ 29 | const Tab(text: "已完成"), 30 | Obx( 31 | () => Tab( 32 | text: NovelDownloadService.instance.taskQueues.isEmpty 33 | ? "下载中" 34 | : "下载中(${NovelDownloadService.instance.taskQueues.length})"), 35 | ) 36 | ], 37 | ), 38 | ), 39 | ), 40 | body: const TabBarView( 41 | children: [ 42 | NovelDownloadedView(), 43 | NovelDownloadingView(), 44 | ], 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/modules/common/empty_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_constant.dart'; 3 | 4 | class EmptyPage extends StatelessWidget { 5 | const EmptyPage({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return MediaQuery.of(context).size.width <= AppConstant.kTabletWidth 10 | ? Container() 11 | : Scaffold( 12 | resizeToAvoidBottomInset: false, 13 | body: Center( 14 | child: Image.asset( 15 | "assets/images/logo_dmzj.png", 16 | height: 80, 17 | ), 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/modules/common/test_subroute_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 3 | 4 | class TestSubRoutePage extends StatelessWidget { 5 | const TestSubRoutePage({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Scaffold( 10 | appBar: AppBar( 11 | title: const Text("测试路由"), 12 | leading: IconButton( 13 | onPressed: () { 14 | AppNavigator.closePage(); 15 | }, 16 | icon: const Icon(Icons.arrow_back), 17 | ), 18 | ), 19 | body: Center( 20 | child: ElevatedButton( 21 | child: const Text("Back"), 22 | onPressed: () { 23 | AppNavigator.closePage(); 24 | }, 25 | ), 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/modules/common/webview/webview_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_color.dart'; 3 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 4 | import 'package:flutter_dmzj/app/log.dart'; 5 | import 'package:flutter_dmzj/services/user_service.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | import 'package:webview_flutter/webview_flutter.dart'; 9 | 10 | class WebViewPageController extends BaseController { 11 | final String url; 12 | WebViewPageController(this.url); 13 | final WebViewController webViewController = WebViewController(); 14 | var title = "加载中".obs; 15 | @override 16 | void onInit() { 17 | initWebView(); 18 | super.onInit(); 19 | } 20 | 21 | void initWebView() async { 22 | webViewController.setJavaScriptMode(JavaScriptMode.unrestricted); 23 | 24 | webViewController.setBackgroundColor( 25 | Get.isDarkMode ? Colors.black : AppColor.backgroundColor); 26 | webViewController.setNavigationDelegate( 27 | NavigationDelegate( 28 | onPageStarted: (String url) { 29 | pageLoadding.value = true; 30 | }, 31 | onPageFinished: (String url) async { 32 | pageLoadding.value = false; 33 | title.value = (await webViewController.getTitle()) ?? ""; 34 | }, 35 | onNavigationRequest: (NavigationRequest request) { 36 | var uri = Uri.parse(request.url); 37 | Log.d(request.url); 38 | if (uri.scheme == "https" || uri.scheme == "http") { 39 | return NavigationDecision.navigate; 40 | } 41 | 42 | return NavigationDecision.prevent; 43 | }, 44 | ), 45 | ); 46 | webViewController.loadRequest(Uri.parse(url), headers: { 47 | "Cookie": UserService.instance.userProfile.value?.cookieVal ?? "", 48 | }); 49 | 50 | /// TODO 无法加载Mixed Content 51 | /// 19年的问题了,Flutter还没解决... 52 | /// https://github.com/flutter/flutter/issues/43595 53 | } 54 | 55 | void refreshWeb() { 56 | webViewController.reload(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/modules/news/home/news_home_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 5 | import 'package:flutter_dmzj/app/event_bus.dart'; 6 | import 'package:flutter_dmzj/models/news/news_tag_model.dart'; 7 | import 'package:flutter_dmzj/modules/news/home/news_list_controller.dart'; 8 | import 'package:flutter_dmzj/requests/news_request.dart'; 9 | import 'package:get/get.dart'; 10 | 11 | class NewsHomeController extends GetxController 12 | with GetTickerProviderStateMixin { 13 | NewsRequest request = NewsRequest(); 14 | late TabController tabController; 15 | var loadding = true; 16 | List categores = []; 17 | var error = false; 18 | var errorMsg = ""; 19 | 20 | StreamSubscription? streamSubscription; 21 | 22 | @override 23 | void onInit() { 24 | streamSubscription = EventBus.instance.listen( 25 | EventBus.kBottomNavigationBarClicked, 26 | (index) { 27 | if (index == 1) { 28 | refreshOrScrollTop(); 29 | } 30 | }, 31 | ); 32 | loadCategores(); 33 | super.onInit(); 34 | } 35 | 36 | @override 37 | void onClose() { 38 | streamSubscription?.cancel(); 39 | super.onClose(); 40 | } 41 | 42 | void loadCategores() async { 43 | try { 44 | loadding = true; 45 | error = false; 46 | update(); 47 | var category = await request.category(); 48 | category.insert(0, NewsTagModel(tagId: 0, tagName: "最新")); 49 | tabController = TabController(length: category.length, vsync: this); 50 | 51 | categores = category; 52 | } catch (e) { 53 | errorMsg = e.toString(); 54 | error = true; 55 | } finally { 56 | loadding = false; 57 | update(); 58 | } 59 | } 60 | 61 | void refreshOrScrollTop() { 62 | var tabIndex = tabController.index; 63 | BasePageController controller; 64 | controller = 65 | Get.find(tag: "${categores[tabIndex].tagId}"); 66 | controller.scrollToTopOrRefresh(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/modules/news/home/news_home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/modules/news/home/news_home_controller.dart'; 3 | import 'package:flutter_dmzj/modules/news/home/news_list_view.dart'; 4 | import 'package:flutter_dmzj/widgets/status/app_error_widget.dart'; 5 | import 'package:flutter_dmzj/widgets/status/app_loadding_widget.dart'; 6 | import 'package:flutter_dmzj/widgets/tab_appbar.dart'; 7 | import 'package:get/get.dart'; 8 | 9 | class NewsHomePage extends GetView { 10 | const NewsHomePage({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return GetBuilder( 15 | init: controller, 16 | builder: (controller) { 17 | if (controller.loadding) { 18 | return const Scaffold( 19 | body: AppLoaddingWidget(), 20 | ); 21 | } 22 | if (!controller.loadding && controller.error) { 23 | return Scaffold( 24 | body: AppErrorWidget( 25 | errorMsg: controller.errorMsg, 26 | onRefresh: controller.loadCategores, 27 | ), 28 | ); 29 | } 30 | return Scaffold( 31 | appBar: TabAppBar( 32 | tabs: 33 | controller.categores.map((e) => Tab(text: e.tagName)).toList(), 34 | controller: controller.tabController, 35 | ), 36 | body: TabBarView( 37 | controller: controller.tabController, 38 | children: 39 | controller.categores.map((e) => NewsListView(tag: e)).toList(), 40 | ), 41 | ); 42 | }, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/modules/news/home/news_list_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/news/news_banner_model.dart'; 3 | import 'package:flutter_dmzj/models/news/news_tag_model.dart'; 4 | import 'package:flutter_dmzj/models/proto/news.pb.dart'; 5 | import 'package:flutter_dmzj/requests/news_request.dart'; 6 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 7 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 8 | import 'package:get/get.dart'; 9 | 10 | class NewsListController extends BasePageController { 11 | final NewsRequest request = NewsRequest(); 12 | final NewsTagModel tag; 13 | NewsListController(this.tag); 14 | 15 | RxList banners = RxList(); 16 | 17 | @override 18 | Future> getData(int page, int pageSize) async { 19 | if (tag.tagId == 0 && page == 1) { 20 | loadBanner(); 21 | } 22 | return await request.getNewsList(tag.tagId, page); 23 | } 24 | 25 | void loadBanner() async { 26 | try { 27 | banners.value = await request.banner(); 28 | } catch (e) { 29 | SmartDialog.showToast(e.toString()); 30 | } 31 | } 32 | 33 | void openBanner(NewsBannerModel item) { 34 | AppNavigator.toNewsDetail( 35 | url: item.objectUrl ?? "", 36 | newsId: item.objectId ?? 0, 37 | title: item.title, 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/modules/novel/home/category/novel_category_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/novel/category_model.dart'; 3 | import 'package:flutter_dmzj/requests/novel_request.dart'; 4 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 5 | 6 | class NovelCategoryController extends BasePageController { 7 | final NovelRequest request = NovelRequest(); 8 | 9 | @override 10 | Future> getData(int page, int pageSize) async { 11 | if (page > 1) { 12 | return []; 13 | } 14 | var ls = await request.categores(); 15 | 16 | return ls; 17 | } 18 | 19 | void toDetail(NovelCategoryModel item) { 20 | AppNavigator.toNovelCategoryDetail(item.tagId); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/modules/novel/home/category/novel_category_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:flutter_dmzj/modules/novel/home/category/novel_category_controller.dart'; 4 | import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart'; 5 | import 'package:flutter_dmzj/widgets/net_image.dart'; 6 | import 'package:flutter_dmzj/widgets/page_grid_view.dart'; 7 | import 'package:flutter_dmzj/widgets/shadow_card.dart'; 8 | import 'package:get/get.dart'; 9 | 10 | class NovelCategoryView extends StatelessWidget { 11 | final NovelCategoryController controller; 12 | NovelCategoryView({Key? key}) 13 | : controller = Get.put(NovelCategoryController()), 14 | super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return LayoutBuilder(builder: (context, constraints) { 19 | var count = constraints.maxWidth ~/ 160; 20 | if (count < 3) count = 3; 21 | return KeepAliveWrapper( 22 | child: PageGridView( 23 | pageController: controller, 24 | firstRefresh: true, 25 | loadMore: false, 26 | crossAxisCount: count, 27 | padding: AppStyle.edgeInsetsH12.copyWith(bottom: 12), 28 | mainAxisSpacing: 12, 29 | crossAxisSpacing: 12, 30 | itemBuilder: (context, i) { 31 | var item = controller.list[i]; 32 | return ShadowCard( 33 | onTap: () { 34 | controller.toDetail(item); 35 | }, 36 | child: Column( 37 | children: [ 38 | AspectRatio( 39 | aspectRatio: 1.0, 40 | child: NetImage( 41 | item.cover, 42 | borderRadius: 8, 43 | ), 44 | ), 45 | Padding( 46 | padding: AppStyle.edgeInsetsA8, 47 | child: Text( 48 | item.title, 49 | textAlign: TextAlign.center, 50 | style: const TextStyle(height: 1), 51 | ), 52 | ), 53 | ], 54 | ), 55 | ); 56 | }, 57 | ), 58 | ); 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/modules/novel/home/latest/novel_latest_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/novel/latest_model.dart'; 3 | import 'package:flutter_dmzj/requests/novel_request.dart'; 4 | 5 | class NovelLatestController extends BasePageController { 6 | final NovelRequest request = NovelRequest(); 7 | 8 | @override 9 | Future> getData(int page, int pageSize) async { 10 | var ls = await request.latest(page: page - 1); 11 | 12 | return ls; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/modules/novel/home/novel_home_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 5 | import 'package:flutter_dmzj/app/event_bus.dart'; 6 | import 'package:flutter_dmzj/modules/novel/home/category/novel_category_controller.dart'; 7 | import 'package:flutter_dmzj/modules/novel/home/latest/novel_latest_controller.dart'; 8 | import 'package:flutter_dmzj/modules/novel/home/rank/novel_rank_controller.dart'; 9 | import 'package:flutter_dmzj/modules/novel/home/recommend/novel_recommend_controller.dart'; 10 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 11 | import 'package:get/get.dart'; 12 | 13 | class NovelHomeController extends GetxController 14 | with GetTickerProviderStateMixin { 15 | late TabController tabController; 16 | 17 | StreamSubscription? streamSubscription; 18 | 19 | @override 20 | void onInit() { 21 | streamSubscription = EventBus.instance.listen( 22 | EventBus.kBottomNavigationBarClicked, 23 | (index) { 24 | if (index == 2) { 25 | refreshOrScrollTop(); 26 | } 27 | }, 28 | ); 29 | tabController = TabController(length: 4, vsync: this); 30 | 31 | super.onInit(); 32 | } 33 | 34 | void refreshOrScrollTop() { 35 | var tabIndex = tabController.index; 36 | BasePageController? controller; 37 | if (tabIndex == 0) { 38 | controller = Get.find(); 39 | } else if (tabIndex == 1) { 40 | controller = Get.find(); 41 | } else if (tabIndex == 2) { 42 | controller = Get.find(); 43 | } else if (tabIndex == 3) { 44 | controller = Get.find(); 45 | } 46 | controller?.scrollToTopOrRefresh(); 47 | } 48 | 49 | void search() { 50 | AppNavigator.toNovelSearch(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/modules/novel/home/novel_home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/modules/novel/home/category/novel_category_view.dart'; 3 | import 'package:flutter_dmzj/modules/novel/home/latest/novel_latest_view.dart'; 4 | import 'package:flutter_dmzj/modules/novel/home/novel_home_controller.dart'; 5 | import 'package:flutter_dmzj/modules/novel/home/rank/novel_rank_view.dart'; 6 | import 'package:flutter_dmzj/modules/novel/home/recommend/novel_recommend_view.dart'; 7 | import 'package:flutter_dmzj/widgets/tab_appbar.dart'; 8 | import 'package:get/get.dart'; 9 | 10 | class NovelHomePage extends GetView { 11 | const NovelHomePage({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | appBar: TabAppBar( 17 | tabs: const [ 18 | Tab(text: "推荐"), 19 | Tab(text: "更新"), 20 | Tab(text: "分类"), 21 | Tab(text: "排行"), 22 | ], 23 | controller: controller.tabController, 24 | action: IconButton( 25 | onPressed: controller.search, 26 | icon: const Icon( 27 | Icons.search, 28 | ), 29 | ), 30 | ), 31 | body: TabBarView( 32 | controller: controller.tabController, 33 | children: [ 34 | NovelRecommendView(), 35 | NovelLatestView(), 36 | NovelCategoryView(), 37 | NovelRankView(), 38 | ], 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/modules/novel/home/rank/novel_rank_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/novel/rank_model.dart'; 3 | import 'package:flutter_dmzj/requests/novel_request.dart'; 4 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | class NovelRankController extends BasePageController { 8 | final NovelRequest request = NovelRequest(); 9 | RxMap tags = { 10 | 0: "全部分类", 11 | }.obs; 12 | var tag = 0.obs; 13 | 14 | Map rankTypes = { 15 | 0: "人气排行", 16 | 1: "订阅排行", 17 | }; 18 | var rankType = 0.obs; 19 | 20 | @override 21 | void onInit() { 22 | loadFilter(); 23 | super.onInit(); 24 | } 25 | 26 | void loadFilter() async { 27 | try { 28 | tags.value = await request.rankFilter(); 29 | } catch (e) { 30 | SmartDialog.showToast(e.toString()); 31 | } 32 | } 33 | 34 | @override 35 | Future> getData(int page, int pageSize) async { 36 | var ls = await request.rank( 37 | tagId: tag.value, 38 | sort: rankType.value, 39 | page: page - 1, 40 | ); 41 | 42 | return ls; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/modules/novel/home/recommend/novel_recommend_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 4 | import 'package:flutter_dmzj/models/novel/recommend_model.dart'; 5 | import 'package:flutter_dmzj/modules/novel/home/novel_home_controller.dart'; 6 | import 'package:flutter_dmzj/requests/novel_request.dart'; 7 | import 'package:flutter_dmzj/routes/app_navigator.dart'; 8 | 9 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 10 | import 'package:get/get.dart'; 11 | import 'package:url_launcher/url_launcher_string.dart'; 12 | 13 | class NovelRecommendController extends BasePageController { 14 | final NovelRequest request = NovelRequest(); 15 | 16 | @override 17 | Future> getData(int page, int pageSize) async { 18 | var ls = await request.recommend(); 19 | 20 | return ls; 21 | } 22 | 23 | void openDetail(NovelRecommendItemModel item) { 24 | //漫画=1 25 | if (item.type == null || item.type == 2) { 26 | AppNavigator.toNovelDetail( 27 | item.objId ?? item.id ?? 0, 28 | ); 29 | } else if (item.type == 1) { 30 | //专题=5 31 | AppNavigator.toComicDetail( 32 | item.objId ?? 0, 33 | ); 34 | } else if (item.type == 5) { 35 | //专题=5 36 | AppNavigator.toSpecialDetail( 37 | item.objId ?? 0, 38 | ); 39 | } else if (item.type == 6) { 40 | //网页=6 41 | AppNavigator.toWebView(item.url ?? ""); 42 | } else if (item.type == 7) { 43 | //新闻=7 44 | AppNavigator.toNewsDetail( 45 | url: item.url ?? "", 46 | newsId: item.objId ?? 0, 47 | title: item.title, 48 | ); 49 | } else if (item.type == 8) { 50 | //作者=8 51 | AppNavigator.toComicAuthorDetail(item.objId ?? 0); 52 | } else if (item.type == 13) { 53 | //社区=13 54 | //直接跳转至网页 55 | launchUrlString( 56 | "http://m.forum.idmzj.com/thread/detail?tid=${item.objId}"); 57 | } else { 58 | SmartDialog.showToast("未知类型,无法跳转"); 59 | } 60 | } 61 | 62 | void toLatest() { 63 | var homeController = Get.find(); 64 | homeController.tabController.animateTo(1); 65 | } 66 | 67 | void toMySubscribe() {} 68 | } 69 | -------------------------------------------------------------------------------- /lib/modules/novel/search/novel_search_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 3 | import 'package:flutter_dmzj/app/log.dart'; 4 | import 'package:flutter_dmzj/models/novel/search_model.dart'; 5 | import 'package:flutter_dmzj/requests/novel_request.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | class NovelSearchController extends BasePageController { 9 | final String keyword; 10 | NovelSearchController(this.keyword) { 11 | searchController = TextEditingController(text: keyword); 12 | } 13 | late TextEditingController searchController; 14 | final NovelRequest request = NovelRequest(); 15 | 16 | String _keyword = ""; 17 | RxMap hotWords = {}.obs; 18 | var showHotWord = true.obs; 19 | 20 | @override 21 | void onInit() { 22 | loadHotWord(); 23 | if (keyword.isNotEmpty) { 24 | submit(); 25 | } 26 | super.onInit(); 27 | } 28 | 29 | void submit() { 30 | if (searchController.text.isEmpty) { 31 | list.clear(); 32 | showHotWord.value = true; 33 | return; 34 | } 35 | showHotWord.value = false; 36 | _keyword = searchController.text; 37 | refreshData(); 38 | } 39 | 40 | @override 41 | Future> getData(int page, int pageSize) async { 42 | if (searchController.text.isEmpty) { 43 | return []; 44 | } 45 | return await request.search(keyword: _keyword, page: page - 1); 46 | } 47 | 48 | void loadHotWord() async { 49 | try { 50 | hotWords.value = await request.searchHotWord(); 51 | } catch (e) { 52 | Log.logPrint(e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/modules/user/comment/user_comment_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/comment/user_comment_item.dart'; 3 | import 'package:flutter_dmzj/requests/comment_request.dart'; 4 | 5 | class UserCommentController extends BasePageController { 6 | final int type; 7 | final int userId; 8 | UserCommentController({required this.type, required this.userId}); 9 | final CommentRequest request = CommentRequest(); 10 | 11 | @override 12 | Future> getData(int page, int pageSize) async { 13 | return await request.getUserComment( 14 | type: type, 15 | uid: userId, 16 | page: page - 1, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/modules/user/comment/user_comment_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:flutter_dmzj/modules/user/comment/user_comment_view.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | class UserCommentPage extends StatelessWidget { 7 | final int userId; 8 | const UserCommentPage(this.userId, {Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return DefaultTabController( 13 | length: 3, 14 | child: Scaffold( 15 | appBar: AppBar( 16 | title: Container( 17 | alignment: Alignment.center, 18 | padding: const EdgeInsets.only(right: 56), 19 | child: TabBar( 20 | isScrollable: true, 21 | tabAlignment: TabAlignment.start, 22 | labelPadding: AppStyle.edgeInsetsH24, 23 | indicatorColor: Theme.of(context).colorScheme.primary, 24 | labelColor: Theme.of(context).colorScheme.primary, 25 | unselectedLabelColor: 26 | Get.isDarkMode ? Colors.white70 : Colors.black87, 27 | tabs: const [ 28 | Tab(text: "漫画"), 29 | Tab(text: "小说"), 30 | Tab(text: "新闻"), 31 | ], 32 | ), 33 | ), 34 | ), 35 | body: TabBarView( 36 | children: [ 37 | UserCommentView(type: 0, userId: userId), 38 | UserCommentView(type: 1, userId: userId), 39 | UserCommentView(type: 2, userId: userId), 40 | ], 41 | ), 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/modules/user/history/comic/comic_history_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/user/comic_history_model.dart'; 3 | import 'package:flutter_dmzj/requests/user_request.dart'; 4 | 5 | class ComicHistoryController extends BasePageController { 6 | final UserRequest request = UserRequest(); 7 | 8 | @override 9 | Future> getData(int page, int pageSize) async { 10 | if (page > 1) { 11 | return []; 12 | } 13 | return await request.comicHistory(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/user/history/novel/novel_history_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/user/novel_history_model.dart'; 3 | import 'package:flutter_dmzj/requests/user_request.dart'; 4 | 5 | class NovelHistoryController extends BasePageController { 6 | final UserRequest request = UserRequest(); 7 | 8 | @override 9 | Future> getData(int page, int pageSize) async { 10 | if (page > 1) { 11 | return []; 12 | } 13 | return await request.novelHistory(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/user/history/user_history_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | class UserHistoryController extends GetxController 6 | with GetSingleTickerProviderStateMixin { 7 | final int type; 8 | UserHistoryController(this.type); 9 | late TabController tabController; 10 | 11 | @override 12 | void onInit() { 13 | tabController = TabController(length: 2, vsync: this, initialIndex: type); 14 | 15 | super.onInit(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/modules/user/history/user_history_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:flutter_dmzj/modules/user/history/comic/comic_history_view.dart'; 4 | import 'package:flutter_dmzj/modules/user/history/novel/novel_history_view.dart'; 5 | import 'package:flutter_dmzj/modules/user/history/user_history_controller.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | class UserHistoryPage extends StatelessWidget { 9 | final UserHistoryController controller; 10 | final int type; 11 | UserHistoryPage({this.type = 0, super.key}) 12 | : controller = Get.put( 13 | UserHistoryController(type), 14 | tag: DateTime.now().millisecondsSinceEpoch.toString(), 15 | ); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: AppBar( 21 | title: Container( 22 | alignment: Alignment.center, 23 | padding: const EdgeInsets.only(right: 56), 24 | child: TabBar( 25 | controller: controller.tabController, 26 | isScrollable: true, 27 | tabAlignment: TabAlignment.start, 28 | labelPadding: AppStyle.edgeInsetsH24, 29 | indicatorColor: Theme.of(context).colorScheme.primary, 30 | labelColor: Theme.of(context).colorScheme.primary, 31 | unselectedLabelColor: 32 | Get.isDarkMode ? Colors.white70 : Colors.black87, 33 | tabs: const [ 34 | Tab(text: "漫画记录"), 35 | Tab(text: "小说记录"), 36 | ], 37 | ), 38 | ), 39 | ), 40 | body: TabBarView( 41 | controller: controller.tabController, 42 | children: [ 43 | ComicHistoryView(), 44 | NovelHistoryView(), 45 | ], 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/modules/user/local_favorite/local_favorite_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/app_constant.dart'; 2 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 3 | import 'package:flutter_dmzj/models/db/local_favorite.dart'; 4 | import 'package:flutter_dmzj/services/db_service.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | class LocalFavoriteController extends BasePageController { 8 | var editMode = false.obs; 9 | @override 10 | Future> getData(int page, int pageSize) async { 11 | if (page > 1) { 12 | return []; 13 | } 14 | return DBService.instance.localFavoriteBox.values 15 | .where((x) => x.type == AppConstant.kTypeComic) 16 | .toList(); 17 | } 18 | 19 | void cancelEdit() { 20 | for (var item in list) { 21 | item.isChecked.value = false; 22 | } 23 | editMode.value = false; 24 | } 25 | 26 | void cancelFavorite() async { 27 | var items = list.where((x) => x.isChecked.value).toList(); 28 | if (items.isEmpty) { 29 | cancelEdit(); 30 | return; 31 | } 32 | cancelEdit(); 33 | for (var item in items) { 34 | DBService.instance.removeComicFavorite(comicId: item.objId); 35 | } 36 | 37 | refreshData(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/modules/user/local_history/comic/comic_history_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/db/comic_history.dart'; 3 | import 'package:flutter_dmzj/requests/user_request.dart'; 4 | import 'package:flutter_dmzj/services/db_service.dart'; 5 | 6 | class LocalComicHistoryController extends BasePageController { 7 | final UserRequest request = UserRequest(); 8 | 9 | @override 10 | Future> getData(int page, int pageSize) async { 11 | if (page > 1) { 12 | return []; 13 | } 14 | 15 | return DBService.instance.getComicHistoryList(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/modules/user/local_history/local_history_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | class LocalHistoryController extends GetxController 6 | with GetSingleTickerProviderStateMixin { 7 | final int type; 8 | LocalHistoryController(this.type); 9 | late TabController tabController; 10 | 11 | @override 12 | void onInit() { 13 | tabController = TabController(length: 2, vsync: this, initialIndex: type); 14 | 15 | super.onInit(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/modules/user/local_history/local_history_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:flutter_dmzj/modules/user/local_history/comic/comic_history_view.dart'; 4 | 5 | import 'package:flutter_dmzj/modules/user/local_history/local_history_controller.dart'; 6 | import 'package:flutter_dmzj/modules/user/local_history/novel/novel_history_view.dart'; 7 | import 'package:get/get.dart'; 8 | 9 | class LocalHistoryPage extends StatelessWidget { 10 | final LocalHistoryController controller; 11 | final int type; 12 | LocalHistoryPage({this.type = 0, super.key}) 13 | : controller = Get.put( 14 | LocalHistoryController(type), 15 | tag: DateTime.now().millisecondsSinceEpoch.toString(), 16 | ); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: AppBar( 22 | title: Container( 23 | alignment: Alignment.center, 24 | padding: const EdgeInsets.only(right: 56), 25 | child: TabBar( 26 | controller: controller.tabController, 27 | isScrollable: true, 28 | tabAlignment: TabAlignment.start, 29 | labelPadding: AppStyle.edgeInsetsH24, 30 | indicatorColor: Theme.of(context).colorScheme.primary, 31 | indicatorSize: TabBarIndicatorSize.label, 32 | labelColor: Theme.of(context).colorScheme.primary, 33 | unselectedLabelColor: 34 | Get.isDarkMode ? Colors.white70 : Colors.black87, 35 | tabs: const [ 36 | Tab(text: "漫画记录"), 37 | Tab(text: "小说记录"), 38 | ], 39 | ), 40 | ), 41 | ), 42 | body: TabBarView( 43 | controller: controller.tabController, 44 | children: [ 45 | LocalComicHistoryView(), 46 | LocalNovelHistoryView(), 47 | ], 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/modules/user/local_history/novel/novel_history_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/db/novel_history.dart'; 3 | import 'package:flutter_dmzj/requests/user_request.dart'; 4 | import 'package:flutter_dmzj/services/db_service.dart'; 5 | 6 | class LocalNovelHistoryController extends BasePageController { 7 | final UserRequest request = UserRequest(); 8 | 9 | @override 10 | Future> getData(int page, int pageSize) async { 11 | if (page > 1) { 12 | return []; 13 | } 14 | 15 | return DBService.instance.getNovelHistoryList(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/modules/user/login/user_login_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/log.dart'; 3 | import 'package:flutter_dmzj/requests/user_request.dart'; 4 | import 'package:flutter_dmzj/services/user_service.dart'; 5 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | class UserLoginController extends GetxController { 9 | final TextEditingController userNameController = TextEditingController(); 10 | final TextEditingController passwordController = TextEditingController(); 11 | final UserRequest userRequest = UserRequest(); 12 | var loadding = false.obs; 13 | void login() async { 14 | if (userNameController.text.isEmpty) { 15 | SmartDialog.showToast("请输入用户名"); 16 | return; 17 | } 18 | if (passwordController.text.isEmpty) { 19 | SmartDialog.showToast("请输入密码"); 20 | return; 21 | } 22 | try { 23 | loadding.value = true; 24 | var data = await userRequest.login( 25 | nickname: userNameController.text, 26 | password: passwordController.text, 27 | ); 28 | UserService.instance.setAuthInfo(data); 29 | 30 | loadding.value = false; 31 | Get.back(result: true); 32 | } catch (e) { 33 | SmartDialog.showToast(e.toString()); 34 | Log.logPrint(e); 35 | } finally { 36 | loadding.value = false; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/modules/user/subscribe/news/news_subscribe_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 2 | import 'package:flutter_dmzj/models/user/subscribe_news_model.dart'; 3 | import 'package:flutter_dmzj/requests/user_request.dart'; 4 | 5 | class NewsSubscribeController 6 | extends BasePageController { 7 | final UserRequest request = UserRequest(); 8 | 9 | @override 10 | Future> getData(int page, int pageSize) async { 11 | return await request.newsSubscribes( 12 | page: page, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/user/subscribe/novel/novel_subscribe_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dmzj/app/app_constant.dart'; 2 | import 'package:flutter_dmzj/app/controller/base_controller.dart'; 3 | import 'package:flutter_dmzj/models/user/subscribe_novel_model.dart'; 4 | import 'package:flutter_dmzj/requests/user_request.dart'; 5 | import 'package:flutter_dmzj/services/user_service.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | class NovelSubscribeController 9 | extends BasePageController { 10 | NovelSubscribeController() { 11 | for (var item in List.generate( 12 | 26, (index) => String.fromCharCode(index + 65).toLowerCase())) { 13 | letters.addAll({item: "${item.toUpperCase()}开头"}); 14 | } 15 | } 16 | final UserRequest request = UserRequest(); 17 | 18 | var letter = "all".obs; 19 | 20 | Map letters = { 21 | "all": "全部", 22 | "number": "数字开头", 23 | }; 24 | 25 | Map types = { 26 | 1: "全部订阅", 27 | 2: "未读", 28 | 3: "已读", 29 | 4: "完结", 30 | }; 31 | var type = 1.obs; 32 | 33 | @override 34 | Future> getData(int page, int pageSize) async { 35 | var ls = await request.novelSubscribes( 36 | subType: type.value, 37 | letter: letter.value, 38 | page: page - 1, 39 | ); 40 | UserService.instance.subscribedNovelIds.addAll(ls.map((e) => e.id)); 41 | return ls; 42 | } 43 | 44 | var editMode = false.obs; 45 | void cancelEdit() { 46 | for (var item in list) { 47 | item.isChecked.value = false; 48 | } 49 | editMode.value = false; 50 | } 51 | 52 | void cancelSub() async { 53 | var ids = list.where((x) => x.isChecked.value).map((e) => e.id).toList(); 54 | if (ids.isEmpty) { 55 | cancelEdit(); 56 | return; 57 | } 58 | cancelEdit(); 59 | await UserService.instance.cancelSubscribe(ids, AppConstant.kTypeNovel); 60 | easyRefreshController.callRefresh(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/modules/user/subscribe/user_subscribe_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class UserSubscribeController extends GetxController 5 | with GetSingleTickerProviderStateMixin { 6 | final int type; 7 | UserSubscribeController(this.type); 8 | late TabController tabController; 9 | 10 | @override 11 | void onInit() { 12 | tabController = TabController(length: 3, vsync: this, initialIndex: type); 13 | 14 | super.onInit(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/modules/user/subscribe/user_subscribe_pgae.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:flutter_dmzj/modules/user/subscribe/comic/comic_subscribe_view.dart'; 4 | import 'package:flutter_dmzj/modules/user/subscribe/news/news_subscribe_view.dart'; 5 | import 'package:flutter_dmzj/modules/user/subscribe/novel/novel_subscribe_view.dart'; 6 | import 'package:flutter_dmzj/modules/user/subscribe/user_subscribe_controller.dart'; 7 | import 'package:get/get.dart'; 8 | 9 | class UserSubscribePage extends StatelessWidget { 10 | final UserSubscribeController controller; 11 | final int type; 12 | UserSubscribePage({this.type = 0, super.key}) 13 | : controller = Get.put( 14 | UserSubscribeController(type), 15 | tag: DateTime.now().millisecondsSinceEpoch.toString(), 16 | ); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: AppBar( 22 | title: Container( 23 | alignment: Alignment.center, 24 | padding: const EdgeInsets.only(right: 56), 25 | child: TabBar( 26 | controller: controller.tabController, 27 | isScrollable: true, 28 | tabAlignment: TabAlignment.start, 29 | labelPadding: AppStyle.edgeInsetsH24, 30 | indicatorSize: TabBarIndicatorSize.label, 31 | indicatorColor: Theme.of(context).colorScheme.primary, 32 | labelColor: Theme.of(context).colorScheme.primary, 33 | unselectedLabelColor: 34 | Get.isDarkMode ? Colors.white70 : Colors.black87, 35 | tabs: const [ 36 | Tab(text: "漫画"), 37 | Tab(text: "小说"), 38 | Tab(text: "新闻"), 39 | ], 40 | ), 41 | ), 42 | ), 43 | body: TabBarView( 44 | controller: controller.tabController, 45 | children: [ 46 | ComicSubscribeView(), 47 | NovelSubscribeView(), 48 | NewsSubscribeView(), 49 | ], 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/requests/common/custom_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_dmzj/app/log.dart'; 3 | 4 | class CustomInterceptor extends Interceptor { 5 | @override 6 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 7 | options.extra["ts"] = DateTime.now().millisecondsSinceEpoch; 8 | super.onRequest(options, handler); 9 | } 10 | 11 | @override 12 | void onError(DioException err, ErrorInterceptorHandler handler) { 13 | var time = 14 | DateTime.now().millisecondsSinceEpoch - err.requestOptions.extra["ts"]; 15 | Log.e('''【HTTP请求错误】 耗时:${time}ms 16 | Request Method:${err.requestOptions.method} 17 | Response Code:${err.response?.statusCode} 18 | Request URL:${err.requestOptions.uri} 19 | Request Query:${err.requestOptions.queryParameters} 20 | Request Data:${err.requestOptions.data} 21 | Request Headers:${err.requestOptions.headers} 22 | Response Headers:${err.response?.headers.map} 23 | Response Data:${err.response?.data}''', err.stackTrace); 24 | super.onError(err, handler); 25 | } 26 | 27 | @override 28 | void onResponse(Response response, ResponseInterceptorHandler handler) { 29 | var time = DateTime.now().millisecondsSinceEpoch - 30 | response.requestOptions.extra["ts"]; 31 | if (response.requestOptions.uri.toString().contains(".txt")) { 32 | Log.i( 33 | '''【HTTP请求响应】 耗时:${time}ms 34 | Request Method:${response.requestOptions.method} 35 | Request Code:${response.statusCode} 36 | Request URL:${response.requestOptions.uri}''', 37 | ); 38 | return super.onResponse(response, handler); 39 | } 40 | Log.i( 41 | '''【HTTP请求响应】 耗时:${time}ms 42 | Request Method:${response.requestOptions.method} 43 | Request Code:${response.statusCode} 44 | Request URL:${response.requestOptions.uri} 45 | Request Query:${response.requestOptions.queryParameters} 46 | Request Data:${response.requestOptions.data} 47 | Request Headers:${response.requestOptions.headers} 48 | Response Headers:${response.headers.map} 49 | Response Data:${response.data}''', 50 | ); 51 | super.onResponse(response, handler); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/requests/common_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_dmzj/models/version_model.dart'; 3 | 4 | /// 通用的请求 5 | class CommonRequest { 6 | Future checkUpdate() async { 7 | try { 8 | return await checkUpdateGitMirror(); 9 | } catch (e) { 10 | return await checkUpdateJsDelivr(); 11 | } 12 | } 13 | 14 | /// 检查更新 15 | Future checkUpdateGitMirror() async { 16 | var result = await Dio().get( 17 | "https://raw.gitmirror.com/xiaoyaocz/flutter_dmzj/main/document/app_version.json", 18 | queryParameters: { 19 | "ts": DateTime.now().millisecondsSinceEpoch, 20 | }, 21 | options: Options( 22 | responseType: ResponseType.json, 23 | ), 24 | ); 25 | return VersionModel.fromJson(result.data); 26 | } 27 | 28 | /// 检查更新 29 | Future checkUpdateJsDelivr() async { 30 | var result = await Dio().get( 31 | "https://cdn.jsdelivr.net/gh/xiaoyaocz/flutter_dmzj@main/document/app_version.json", 32 | queryParameters: { 33 | "ts": DateTime.now().millisecondsSinceEpoch, 34 | }, 35 | options: Options( 36 | responseType: ResponseType.json, 37 | ), 38 | ); 39 | return VersionModel.fromJson(result.data); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/routes/route_path.dart: -------------------------------------------------------------------------------- 1 | class RoutePath { 2 | /// 首页 3 | static const kIndex = "/index"; 4 | 5 | /// 空白 6 | static const kEmpty = "/empty"; 7 | 8 | /// 登录 9 | static const kUserLogin = "/user/login"; 10 | 11 | static const kTestSubRoute = "/test/sub_route"; 12 | 13 | /// WebView 14 | static const kWebView = "/other/webview"; 15 | 16 | /// 新闻详情 17 | static const kNewsDetail = "/news/detail"; 18 | 19 | /// 评论 20 | static const kComment = "/comment"; 21 | 22 | /// 漫画详情 23 | static const kComicDetail = "/comic/detail"; 24 | 25 | /// 漫画阅读 26 | static const kComicReader = "/comic/reader"; 27 | 28 | /// 漫画分类详情 29 | static const kComicCategoryDetail = "/comic/category/detail"; 30 | 31 | /// 专题详情 32 | static const kSpecialDetail = "/comic/special/detail"; 33 | 34 | /// 漫画作者详情 35 | static const kComicAuthorDetail = "/comic/author/detail"; 36 | 37 | /// 漫画搜索 38 | static const kComicSearch = "/comic/search"; 39 | 40 | /// 轻小说搜索 41 | static const kNovelSearch = "/novel/search"; 42 | 43 | /// 轻小说分类详情 44 | static const kNovelCategoryDetail = "/novel/category/detail"; 45 | 46 | /// 用户订阅 47 | static const kUserSubscribe = "/user/subscribe"; 48 | 49 | /// 用户观看记录 50 | static const kUserHistory = "/user/history"; 51 | 52 | /// 本机观看记录 53 | static const kLocalHistory = "/user/local/history"; 54 | 55 | /// 设置 56 | static const kSettings = "/user/settings"; 57 | 58 | /// 小说详情 59 | static const kNovelDetail = "/novel/detail"; 60 | 61 | /// 小说阅读 62 | static const kNovelReader = "/novel/reader"; 63 | 64 | /// 漫画下载,选择章节 65 | static const kComicDownloadSelect = "/comic/download/chapter"; 66 | 67 | /// 小说下载,选择章节 68 | static const kNovelDownloadSelect = "/novel/download/chapter"; 69 | 70 | /// 漫画下载管理 71 | static const kComicDownload = "/download/comic"; 72 | 73 | /// 漫画下载详情 74 | static const kComicDownloadDetail = "/download/comic/detail"; 75 | 76 | /// 小说下载管理 77 | static const kNovelDownload = "/download/novel"; 78 | 79 | /// 小说下载详情 80 | static const kNovelDownloadDetail = "/download/novel/chapter"; 81 | 82 | /// 添加/回复评论 83 | static const kCommentAdd = "/comment/add"; 84 | 85 | /// 用户的评论 86 | static const kUserComment = "/user/comment"; 87 | 88 | /// 本机收藏 89 | static const kLocalFavorite = "/user/local/favorite"; 90 | } 91 | -------------------------------------------------------------------------------- /lib/widgets/border_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BorderText extends StatelessWidget { 4 | final String text; 5 | final TextAlign textAlign; 6 | final Color color; 7 | final double fontSize; 8 | final double strokeWidth; 9 | const BorderText( 10 | this.text, { 11 | this.textAlign = TextAlign.left, 12 | this.color = Colors.white, 13 | this.fontSize = 16, 14 | this.strokeWidth = 2.0, 15 | Key? key, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Stack( 21 | children: [ 22 | Text( 23 | text, 24 | softWrap: false, 25 | textAlign: textAlign, 26 | style: TextStyle( 27 | fontSize: fontSize, 28 | foreground: Paint() 29 | ..style = PaintingStyle.stroke 30 | ..strokeWidth = strokeWidth 31 | ..strokeCap = StrokeCap.round 32 | ..strokeJoin = StrokeJoin.round 33 | ..color = getBorderColor(color), 34 | ), 35 | ), 36 | Text( 37 | text, 38 | softWrap: false, 39 | textAlign: textAlign, 40 | style: TextStyle( 41 | fontSize: fontSize, 42 | color: color, 43 | ), 44 | ), 45 | ], 46 | ); 47 | } 48 | 49 | Color getBorderColor(Color color) { 50 | var brightness = 51 | ((color.red * 299) + (color.green * 587) + (color.blue * 114)) / 1000; 52 | return brightness > 70 ? Colors.black : Colors.white; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/widgets/keep_alive_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class KeepAliveWrapper extends StatefulWidget { 4 | final Widget child; 5 | 6 | const KeepAliveWrapper({Key? key, required this.child}) : super(key: key); 7 | 8 | @override 9 | State createState() => _KeepAliveWrapperState(); 10 | } 11 | 12 | class _KeepAliveWrapperState extends State 13 | with AutomaticKeepAliveClientMixin { 14 | @override 15 | Widget build(BuildContext context) { 16 | super.build(context); 17 | return widget.child; 18 | } 19 | 20 | @override 21 | bool get wantKeepAlive => true; 22 | } 23 | -------------------------------------------------------------------------------- /lib/widgets/loadding.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | 4 | class LoaddingWidget extends StatelessWidget { 5 | const LoaddingWidget({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Center( 10 | child: Container( 11 | padding: AppStyle.edgeInsetsA24, 12 | decoration: BoxDecoration( 13 | color: Theme.of(context).cardColor, 14 | borderRadius: BorderRadius.circular(12), 15 | ), 16 | child: const CircularProgressIndicator(), 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/widgets/local_image.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | 6 | class LocalImage extends StatelessWidget { 7 | final String path; 8 | final double? width; 9 | final double? height; 10 | final BoxFit? fit; 11 | final double borderRadius; 12 | final bool progress; 13 | const LocalImage(this.path, 14 | {this.width, 15 | this.height, 16 | this.fit = BoxFit.cover, 17 | this.borderRadius = 0, 18 | this.progress = false, 19 | Key? key}) 20 | : super(key: key); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | if (path.isEmpty) { 25 | return Container( 26 | decoration: BoxDecoration( 27 | color: Colors.grey.withOpacity(.1), 28 | ), 29 | child: const Icon( 30 | Icons.image, 31 | color: Colors.grey, 32 | size: 24, 33 | ), 34 | ); 35 | } 36 | return ClipRRect( 37 | borderRadius: BorderRadius.circular(borderRadius), 38 | child: FutureBuilder( 39 | future: File(path).readAsBytes(), 40 | builder: (_, snap) { 41 | if (snap.hasError) { 42 | return Container( 43 | decoration: BoxDecoration( 44 | color: Colors.grey.withOpacity(.1), 45 | ), 46 | child: const Icon( 47 | Icons.broken_image, 48 | color: Colors.grey, 49 | size: 24, 50 | ), 51 | ); 52 | } 53 | if (!snap.hasData) { 54 | return const Center( 55 | child: CircularProgressIndicator(), 56 | ); 57 | } 58 | 59 | return Image.memory( 60 | snap.data as Uint8List, 61 | fit: fit, 62 | height: height, 63 | width: width, 64 | ); 65 | }, 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/widgets/refresh_until_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:remixicon/remixicon.dart'; 4 | 5 | /// 一个加载图标会旋转的加载按钮。加载图标([Remix.refresh_line])在左,文字([text])在 6 | /// 右。 7 | /// 8 | /// 在点击widget时会在执行[onRefresh]函数的同时旋转加载图标。加载图标会一直旋转直到该函数 9 | /// 返还。 10 | /// 11 | /// 加载图标会旋转不小于1秒的时间,即如果[onRefresh]函数在1秒之内执行完毕,加载图标会继续旋 12 | /// 转直到距离onRefresh函数开始执行已经过了1秒。 13 | class RefreshUntilWidget extends StatefulWidget { 14 | final Future Function() onRefresh; 15 | final String text; 16 | 17 | const RefreshUntilWidget({ 18 | super.key, 19 | required this.onRefresh, 20 | required this.text, 21 | }); 22 | 23 | @override 24 | State createState() => _RefreshUntilWidgetState(); 25 | } 26 | 27 | class _RefreshUntilWidgetState extends State 28 | with TickerProviderStateMixin { 29 | late final AnimationController _controller = AnimationController( 30 | duration: const Duration(seconds: 1), 31 | vsync: this, 32 | ); 33 | late final Animation _animation = CurvedAnimation( 34 | parent: _controller, 35 | curve: Curves.linear, 36 | ); 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return GestureDetector( 41 | onTap: () async { 42 | _controller.repeat(); 43 | // 确保在网络很好的情况下,动画不会太快结束(至少1秒) 44 | await Future.wait([ 45 | widget.onRefresh(), 46 | Future.delayed(const Duration(seconds: 1)), 47 | ]); 48 | _controller.stop(canceled: false); 49 | }, 50 | child: Row( 51 | children: [ 52 | RotationTransition( 53 | turns: _animation, 54 | child: const Icon(Remix.refresh_line, size: 18, color: Colors.grey), 55 | ), 56 | AppStyle.hGap4, 57 | Text( 58 | widget.text, 59 | style: const TextStyle(fontSize: 14, color: Colors.grey), 60 | ), 61 | ], 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/widgets/shadow_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | class ShadowCard extends StatelessWidget { 6 | final Widget child; 7 | final double radius; 8 | final Function()? onTap; 9 | final Function()? onLongPress; 10 | const ShadowCard({ 11 | required this.child, 12 | this.radius = 8.0, 13 | this.onTap, 14 | this.onLongPress, 15 | Key? key, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Container( 21 | decoration: BoxDecoration( 22 | borderRadius: BorderRadius.circular(radius), 23 | boxShadow: Get.isDarkMode 24 | ? [] 25 | : [ 26 | BoxShadow( 27 | blurRadius: 4, 28 | color: Colors.grey.withOpacity(.2), 29 | ) 30 | ], 31 | ), 32 | child: ClipRRect( 33 | borderRadius: BorderRadius.circular(radius), 34 | child: Material( 35 | color: Theme.of(context).cardColor, 36 | borderRadius: BorderRadius.circular(radius), 37 | child: InkWell( 38 | borderRadius: BorderRadius.circular(radius), 39 | onTap: onTap, 40 | onLongPress: onLongPress, 41 | child: Container( 42 | decoration: BoxDecoration( 43 | borderRadius: AppStyle.radius8, 44 | ), 45 | child: child, 46 | ), 47 | ), 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/widgets/status/app_empty_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:lottie/lottie.dart'; 4 | 5 | class AppEmptyWidget extends StatelessWidget { 6 | final Function()? onRefresh; 7 | const AppEmptyWidget({this.onRefresh, Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Center( 12 | child: GestureDetector( 13 | onTap: () { 14 | onRefresh?.call(); 15 | }, 16 | child: Padding( 17 | padding: AppStyle.edgeInsetsA12, 18 | child: Column( 19 | mainAxisSize: MainAxisSize.min, 20 | children: [ 21 | LottieBuilder.asset( 22 | 'assets/lotties/empty.json', 23 | width: 200, 24 | height: 200, 25 | repeat: false, 26 | ), 27 | const Text( 28 | "这里什么都没有", 29 | textAlign: TextAlign.center, 30 | style: TextStyle(fontSize: 12, color: Colors.grey), 31 | ), 32 | ], 33 | ), 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/widgets/status/app_error_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:flutter_dmzj/app/utils.dart'; 4 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | import 'package:lottie/lottie.dart'; 8 | 9 | class AppErrorWidget extends StatelessWidget { 10 | final Function()? onRefresh; 11 | final String errorMsg; 12 | final Error? error; 13 | const AppErrorWidget( 14 | {this.errorMsg = "", this.onRefresh, this.error, Key? key}) 15 | : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Center( 20 | child: GestureDetector( 21 | onTap: () { 22 | onRefresh?.call(); 23 | }, 24 | child: Padding( 25 | padding: AppStyle.edgeInsetsA12, 26 | child: Column( 27 | mainAxisSize: MainAxisSize.min, 28 | children: [ 29 | LottieBuilder.asset( 30 | 'assets/lotties/error.json', 31 | width: 260, 32 | repeat: false, 33 | ), 34 | Text( 35 | "$errorMsg\r\n点击刷新", 36 | textAlign: TextAlign.center, 37 | style: const TextStyle(fontSize: 12, color: Colors.grey), 38 | ), 39 | Visibility( 40 | visible: error != null, 41 | child: Padding( 42 | padding: AppStyle.edgeInsetsT12, 43 | child: OutlinedButton( 44 | style: OutlinedButton.styleFrom( 45 | textStyle: Get.textTheme.bodySmall, 46 | tapTargetSize: MaterialTapTargetSize.shrinkWrap, 47 | ), 48 | onPressed: () { 49 | Utils.copyText( 50 | "$errorMsg\n${error?.stackTrace?.toString()}"); 51 | SmartDialog.showToast("已复制详细信息"); 52 | }, 53 | child: const Text("复制详细信息"), 54 | ), 55 | ), 56 | ), 57 | ], 58 | ), 59 | ), 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/widgets/status/app_loadding_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:lottie/lottie.dart'; 4 | 5 | class AppLoaddingWidget extends StatelessWidget { 6 | const AppLoaddingWidget({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Center( 11 | child: Padding( 12 | padding: AppStyle.edgeInsetsA12, 13 | child: LottieBuilder.asset( 14 | 'assets/lotties/loadding.json', 15 | width: 200, 16 | ), 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/widgets/user_photo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dmzj/app/app_style.dart'; 3 | import 'package:flutter_dmzj/widgets/net_image.dart'; 4 | import 'package:remixicon/remixicon.dart'; 5 | 6 | class UserPhoto extends StatelessWidget { 7 | final String? url; 8 | final bool showBoder; 9 | final double size; 10 | const UserPhoto({ 11 | this.url, 12 | this.showBoder = true, 13 | this.size = 48, 14 | Key? key, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | if (url == null || (url?.isEmpty ?? true)) { 20 | return Container( 21 | width: size, 22 | height: size, 23 | decoration: BoxDecoration( 24 | border: showBoder 25 | ? Border.all( 26 | color: Colors.grey.withOpacity(.2), 27 | ) 28 | : null, 29 | color: Colors.grey.withOpacity(.2), 30 | borderRadius: AppStyle.radius32, 31 | ), 32 | child: const Icon( 33 | Remix.user_fill, 34 | color: Colors.white, 35 | size: 24, 36 | ), 37 | ); 38 | } 39 | return Container( 40 | width: size, 41 | height: size, 42 | decoration: BoxDecoration( 43 | borderRadius: BorderRadius.circular(56), 44 | border: showBoder 45 | ? Border.all( 46 | color: Colors.grey.withOpacity(.2), 47 | ) 48 | : null, 49 | ), 50 | child: NetImage( 51 | url!, 52 | width: size, 53 | height: size, 54 | borderRadius: size, 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /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 | 12 | void fl_register_plugins(FlPluginRegistry* registry) { 13 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 14 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 15 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 16 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 18 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 19 | } 20 | -------------------------------------------------------------------------------- /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 | url_launcher_linux 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /linux/packaging/deb/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: 动漫之家X 2 | package_name: dmzjx 3 | maintainer: 4 | name: xiaoyaocz 5 | email: xiaoyaocz@52uwp.com 6 | priority: optional 7 | section: x11 8 | installed_size: 24400 9 | essential: false 10 | icon: assets/images/logo.png 11 | 12 | keywords: 13 | - 动漫之家 14 | - DMZJ 15 | 16 | generic_name: 动漫之家X 17 | 18 | categories: 19 | - Media 20 | 21 | startup_notify: true -------------------------------------------------------------------------------- /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 battery_plus 9 | import connectivity_plus 10 | import file_selector_macos 11 | import package_info_plus 12 | import path_provider_foundation 13 | import share_plus 14 | import url_launcher_macos 15 | 16 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 17 | BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) 18 | ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) 19 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 20 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 21 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 22 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 23 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 24 | } 25 | -------------------------------------------------------------------------------- /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/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/macos/Runner/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/macos/Runner/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/macos/Runner/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/macos/Runner/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/macos/Runner/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/macos/Runner/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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 = flutter_dmzj 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.xycz.dmzjx 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 | com.apple.security.network.client 12 | 13 | com.apple.security.files.user-selected.read-write 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /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 | com.apple.security.network.client 8 | 9 | com.apple.security.files.user-selected.read-write 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/packaging/dmg/make_config.yaml: -------------------------------------------------------------------------------- 1 | title: 动漫之家X 2 | contents: 3 | - x: 448 4 | y: 344 5 | type: link 6 | path: "/Applications" 7 | - x: 192 8 | y: 344 9 | type: file 10 | path: 动漫之家X.app 11 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_dmzj 2 | version: 2.1.2+20102 3 | publish_to: none 4 | description: "动漫之家X" 5 | environment: 6 | sdk: '>=2.17.3 <3.0.0' 7 | 8 | dependencies: 9 | #图标ICON 10 | cupertino_icons: ^1.0.5 #Cupertino图标 11 | remixicon: ^1.0.0 #Remix图标 12 | 13 | #框架、工具 14 | get: ^4.6.6 #状态管理 15 | dio: ^5.4.1 #网络请求 16 | hive: ^2.2.3 #持久化存储 17 | hive_flutter: ^1.1.0 #持久化存储 18 | crypton: ^2.1.0 #加解密 19 | crypto: ^3.0.2 #加解密 20 | logger: ^2.0.2+1 #日志输出 21 | protobuf: ^2.1.0 #Protobuf 22 | intl: any 23 | universal_html: ^2.2.4 #HTML解析 24 | html_unescape: ^2.0.0 #HTML解码 25 | csslib: ^1.0.0 #CSS解析 26 | 27 | #Widget 28 | flutter_smart_dialog: ^4.9.6 #各种弹窗 Toast\Dialog\Popup 29 | flutter_staggered_grid_view: ^0.7.0 #瀑布流/GridView 30 | multi_split_view: ^2.4.0 #SplitView 31 | tab_indicator_styler: ^2.0.0 #Tab样式 32 | extended_image: ^8.2.0 #拓展Image 33 | flutter_swiper_view: ^1.1.8 #幻灯片 34 | easy_refresh: ^3.4.0 #下拉刷新、上拉加载 35 | lottie: ^3.0.0 #lottie动画 36 | photo_view: ^0.14.0 #图片浏览 37 | preload_page_view: ^0.2.0 #预加载PageView 38 | scrollable_positioned_list: ^0.3.8 39 | 40 | 41 | #系统交互 42 | url_launcher: ^6.2.4 #打开链接 43 | webview_flutter: ^4.7.0 #WebView 44 | package_info_plus: ^5.0.1 #包信息 45 | flutter_widget_from_html_core: ^0.14.11 #HTML转Widget 46 | share_plus: ^7.2.2 #分享 47 | permission_handler: ^11.3.0 #权限检查 48 | path_provider: ^2.1.2 #常用目录 49 | image_gallery_saver: ^2.0.3 #保存图片至相册 50 | connectivity_plus: ^5.0.2 #连接状态 51 | battery_plus: ^5.0.3 #电池状态 52 | file_selector: ^1.0.3 #文件选择 53 | windows_single_instance: ^1.0.1 #Windows单实例 54 | 55 | flutter: 56 | sdk: flutter 57 | flutter_localizations: 58 | sdk: flutter 59 | 60 | dev_dependencies: 61 | flutter_lints: ^2.0.0 62 | build_runner: ^2.3.3 63 | hive_generator: ^2.0.0 64 | msix: ^3.9.1 65 | flutter_test: 66 | sdk: flutter 67 | 68 | flutter: 69 | uses-material-design: true 70 | assets: 71 | - assets/lotties/ 72 | - assets/images/ 73 | - assets/statement.txt 74 | 75 | 76 | -------------------------------------------------------------------------------- /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 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | void RegisterPlugins(flutter::PluginRegistry* registry) { 18 | BatteryPlusWindowsPluginRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin")); 20 | ConnectivityPlusWindowsPluginRegisterWithRegistrar( 21 | registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); 22 | FileSelectorWindowsRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 24 | PermissionHandlerWindowsPluginRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); 26 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 27 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 28 | UrlLauncherWindowsRegisterWithRegistrar( 29 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 30 | WindowsSingleInstancePluginRegisterWithRegistrar( 31 | registry->GetRegistrarForPlugin("WindowsSingleInstancePlugin")); 32 | } 33 | -------------------------------------------------------------------------------- /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 | battery_plus 7 | connectivity_plus 8 | file_selector_windows 9 | permission_handler_windows 10 | share_plus 11 | url_launcher_windows 12 | windows_single_instance 13 | ) 14 | 15 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 16 | ) 17 | 18 | set(PLUGIN_BUNDLED_LIBRARIES) 19 | 20 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 22 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 25 | endforeach(plugin) 26 | 27 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 28 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 29 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 30 | endforeach(ffi_plugin) 31 | -------------------------------------------------------------------------------- /windows/packaging/msix/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: 动漫之家X 2 | publisher_display_name: xiaoyaocz 3 | identity_name: com.xycz.dmzjx 4 | logo_path: assets/images/logo.png 5 | capabilities: internetClient 6 | languages: zh-cn 7 | install_certificate: "false" -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /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"flutter_dmzj", 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/xiaoyaocz/flutter_dmzj/6e27f949f2932708f9e2794a3b9e7a6e0e7909ef/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 | --------------------------------------------------------------------------------