├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── other.yml └── workflows │ ├── pr.yaml │ └── release.yaml ├── .gitignore ├── .gitmodules ├── .metadata ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── kazumi │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-mdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xxhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ └── ic_launcher.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── bbcode │ └── BBCode.g4 ├── images │ ├── danmaku_off.svg │ ├── danmaku_setting.svg │ ├── forward_80.png │ ├── loading.png │ ├── logo │ │ ├── logo_android.png │ │ ├── logo_ios.png │ │ ├── logo_lanczos.ico │ │ ├── logo_linux.png │ │ ├── logo_rounded.png │ │ └── logo_windows.ico │ ├── noface.jpeg │ └── playing.gif ├── linux │ ├── DEBIAN │ │ ├── postinst │ │ └── postrm │ └── io.github.Predidit.Kazumi.desktop ├── plugins │ ├── DM84.json │ ├── aafun.json │ └── xfdm.json ├── shaders │ ├── Anime4K_AutoDownscalePre_x2.glsl │ ├── Anime4K_AutoDownscalePre_x4.glsl │ ├── Anime4K_Clamp_Highlights.glsl │ ├── Anime4K_Restore_CNN_M.glsl │ ├── Anime4K_Restore_CNN_S.glsl │ ├── Anime4K_Restore_CNN_VL.glsl │ ├── Anime4K_Upscale_CNN_x2_M.glsl │ ├── Anime4K_Upscale_CNN_x2_S.glsl │ ├── Anime4K_Upscale_CNN_x2_VL.glsl │ └── LICENSE └── statements │ └── statements.txt ├── devtools_options.yaml ├── fastlane └── metadata │ └── android │ ├── en-US │ ├── full_description.txt │ ├── images │ │ ├── icon.png │ │ └── phoneScreenshots │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ └── 6.png │ ├── short_description.txt │ └── title.txt │ └── zh-CN │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ ├── LaunchBackground.imageset │ │ │ ├── Contents.json │ │ │ ├── background.png │ │ │ └── darkbackground.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── LaunchImageDark.png │ │ │ ├── LaunchImageDark@2x.png │ │ │ ├── LaunchImageDark@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── app_module.dart ├── app_widget.dart ├── bbcode │ ├── README.md │ ├── bbcode_base_listener.dart │ ├── bbcode_elements.dart │ ├── bbcode_widget.dart │ └── generated │ │ ├── BBCode.tokens │ │ ├── BBCodeLexer.dart │ │ ├── BBCodeListener.dart │ │ └── BBCodeParser.dart ├── bean │ ├── appbar │ │ ├── drag_to_move_bar.dart │ │ └── sys_app_bar.dart │ ├── card │ │ ├── bangumi_card.dart │ │ ├── bangumi_history_card.dart │ │ ├── bangumi_info_card.dart │ │ ├── character_card.dart │ │ ├── character_comments_card.dart │ │ ├── comments_card.dart │ │ ├── episode_comments_card.dart │ │ ├── network_img_layer.dart │ │ ├── palette_card.dart │ │ └── staff_card.dart │ ├── dialog │ │ └── dialog_helper.dart │ ├── settings │ │ ├── color_type.dart │ │ └── theme_provider.dart │ └── widget │ │ ├── collect_button.dart │ │ ├── embedded_native_control_area.dart │ │ ├── error_widget.dart │ │ ├── scrollable_wrapper.dart │ │ └── text_display.dart ├── main.dart ├── modules │ ├── bangumi │ │ ├── bangumi_item.dart │ │ ├── bangumi_item.g.dart │ │ ├── bangumi_tag.dart │ │ ├── bangumi_tag.g.dart │ │ ├── episode_item.dart │ │ └── weekday_item.dart │ ├── character │ │ └── character_full_item.dart │ ├── characters │ │ ├── actor_item.dart │ │ ├── character_item.dart │ │ └── characters_response.dart │ ├── collect │ │ ├── collect_change_module.dart │ │ ├── collect_change_module.g.dart │ │ ├── collect_module.dart │ │ └── collect_module.g.dart │ ├── comments │ │ ├── comment_item.dart │ │ └── comment_response.dart │ ├── danmaku │ │ ├── danmaku_episode_response.dart │ │ ├── danmaku_module.dart │ │ └── danmaku_search_response.dart │ ├── history │ │ ├── history_module.dart │ │ └── history_module.g.dart │ ├── plugin │ │ └── plugin_http_module.dart │ ├── roads │ │ └── road_module.dart │ ├── search │ │ └── plugin_search_module.dart │ └── staff │ │ ├── staff_item.dart │ │ └── staff_response.dart ├── pages │ ├── about │ │ ├── about_module.dart │ │ └── about_page.dart │ ├── collect │ │ ├── collect_controller.dart │ │ ├── collect_controller.g.dart │ │ ├── collect_module.dart │ │ └── collect_page.dart │ ├── error │ │ └── storage_error_page.dart │ ├── history │ │ ├── history_controller.dart │ │ ├── history_controller.g.dart │ │ ├── history_module.dart │ │ └── history_page.dart │ ├── index_module.dart │ ├── index_page.dart │ ├── info │ │ ├── character_page.dart │ │ ├── info_controller.dart │ │ ├── info_controller.g.dart │ │ ├── info_module.dart │ │ ├── info_page.dart │ │ ├── info_tabview.dart │ │ └── source_sheet.dart │ ├── init_page.dart │ ├── logs │ │ └── logs_page.dart │ ├── menu │ │ └── menu.dart │ ├── my │ │ ├── my_controller.dart │ │ ├── my_controller.g.dart │ │ ├── my_module.dart │ │ └── my_page.dart │ ├── player │ │ ├── episode_comments_sheet.dart │ │ ├── player_controller.dart │ │ ├── player_controller.g.dart │ │ ├── player_item.dart │ │ ├── player_item_panel.dart │ │ ├── player_item_surface.dart │ │ └── smallest_player_item_panel.dart │ ├── plugin_editor │ │ ├── plugin_editor_page.dart │ │ ├── plugin_module.dart │ │ ├── plugin_shop_page.dart │ │ └── plugin_view_page.dart │ ├── popular │ │ ├── popular_controller.dart │ │ ├── popular_controller.g.dart │ │ ├── popular_module.dart │ │ └── popular_page.dart │ ├── router.dart │ ├── search │ │ ├── search_controller.dart │ │ ├── search_controller.g.dart │ │ ├── search_module.dart │ │ └── search_page.dart │ ├── settings │ │ ├── danmaku │ │ │ ├── danmaku_module.dart │ │ │ ├── danmaku_settings.dart │ │ │ ├── danmaku_settings_sheet.dart │ │ │ └── danmaku_shield_settings.dart │ │ ├── decoder_settings.dart │ │ ├── displaymode_settings.dart │ │ ├── player_settings.dart │ │ ├── settings_module.dart │ │ ├── super_resolution_settings.dart │ │ └── theme_settings_page.dart │ ├── timeline │ │ ├── timeline_controller.dart │ │ ├── timeline_controller.g.dart │ │ ├── timeline_module.dart │ │ └── timeline_page.dart │ ├── video │ │ ├── video_controller.dart │ │ ├── video_controller.g.dart │ │ ├── video_module.dart │ │ └── video_page.dart │ ├── webdav_editor │ │ ├── webdav_editor_page.dart │ │ ├── webdav_module.dart │ │ └── webdav_setting.dart │ └── webview │ │ ├── webview_controller.dart │ │ ├── webview_controller_impel │ │ ├── webview_apple_controller_impel.dart │ │ ├── webview_controller_impel.dart │ │ ├── webview_linux_controller_impel.dart │ │ └── webview_windows_controller_impel.dart │ │ ├── webview_item.dart │ │ └── webview_item_impel │ │ ├── webview_item_impel.dart │ │ ├── webview_linux_item_impel.dart │ │ └── webview_windows_item_impel.dart ├── plugins │ ├── plugin_install_time_tracker.dart │ ├── plugin_validity_tracker.dart │ ├── plugins.dart │ ├── plugins_controller.dart │ └── plugins_controller.g.dart ├── request │ ├── api.dart │ ├── bangumi.dart │ ├── damaku.dart │ ├── interceptor.dart │ ├── plugin.dart │ ├── query_manager.dart │ └── request.dart ├── shaders │ ├── shaders_controller.dart │ └── shaders_controller.g.dart └── utils │ ├── anime_season.dart │ ├── constants.dart │ ├── extension.dart │ ├── external_player.dart │ ├── logger.dart │ ├── mortis.dart │ ├── remote.dart │ ├── search_parser.dart │ ├── storage.dart │ ├── string_match.dart │ ├── syncplay.dart │ ├── utils.dart │ └── webdav.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements └── RunnerTests │ └── RunnerTests.swift ├── pubspec.lock ├── pubspec.yaml ├── static ├── screenshot │ ├── img_1.png │ ├── img_2.png │ ├── img_3.png │ ├── img_4.png │ ├── img_5.png │ └── img_6.png └── svg │ └── get_it_on_github.svg ├── test └── widget_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── external_player_utils.cpp ├── external_player_utils.h ├── flutter_window.cpp ├── flutter_window.h ├── fullscreen_utils.cpp ├── fullscreen_utils.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug 反馈 2 | description: 提交一个 Bug 反馈。 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 请详细填写以下内容~ 10 | - type: textarea 11 | id: buginfo 12 | attributes: 13 | label: 在使用的时候发生了什么 Bug ? 14 | description: 并且,还请写出您是如何触发这个 Bug 的。 15 | validations: 16 | required: true 17 | - type: dropdown 18 | id: os 19 | attributes: 20 | label: 您在使用哪个操作系统? 21 | multiple: false 22 | options: 23 | - Android 24 | - Windows 25 | - macOS / iOS 26 | - Linux 27 | validations: 28 | required: true 29 | - type: textarea 30 | id: osver 31 | attributes: 32 | label: 请具体提供设备、版本号等信息。 33 | description: 例如,“Redmi K40S,Android 13”、“Windows 10 22H2” 等。 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: hardware 38 | attributes: 39 | label: (选填)一些与 Bug 相关的硬件信息。 40 | description: (选填)例如,有视频播放问题,可以填写“显卡型号”、“显卡驱动版本”等。 41 | - type: textarea 42 | id: logs 43 | attributes: 44 | label: 日志信息 45 | description: 请在 “我的 - 关于 - 错误日志” 界面复制错误日志,并粘贴在这里。 46 | value: | 47 |
Log 48 | 49 | ```shell 50 | [在此处粘贴你的日志] 51 | ``` 52 | 53 |
54 | validations: 55 | required: true 56 | - type: checkboxes 57 | id: terms 58 | attributes: 59 | label: 提交前确认 60 | description: 在提交前,请确认以下内容 61 | options: 62 | - label: issue 列表中,没有我发现的这个 Bug 63 | required: true 64 | - label: 我正在使用最新版本的 Kazumi 65 | required: true 66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yml: -------------------------------------------------------------------------------- 1 | name: 其他 issue 2 | description: 新功能需求、问题询问等 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | 请详细填写以下内容~ 8 | - type: textarea 9 | id: buginfo 10 | attributes: 11 | label: issue 内容 12 | description: 请填写您的 issue 内容。要添加附件,请点击输入框后,直接将附件拖进输入框。 13 | validations: 14 | required: true 15 | - type: checkboxes 16 | id: terms 17 | attributes: 18 | label: 提交前确认 19 | description: 在提交前,请确认以下内容 20 | options: 21 | - label: issue 列表中,没有我的新功能需求 / 问题 22 | required: true 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | 47 | # Added after flutter 3.29 48 | /android/app/.cxx/ 49 | android/build/reports/problems/ 50 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fastlane/.flutter"] 2 | path = fastlane/.flutter 3 | url = https://github.com/flutter/flutter.git 4 | branch = stable 5 | [submodule "fastlane/.libmpv-android-video-build"] 6 | path = fastlane/.libmpv-android-video-build 7 | url = https://github.com/Predidit/libmpv-android-video-build.git 8 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "54e66469a933b60ddf175f858f82eaeb97e48c8d" 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: 54e66469a933b60ddf175f858f82eaeb97e48c8d 17 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 18 | - platform: android 19 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 20 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 21 | - platform: ios 22 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 23 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 24 | - platform: linux 25 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 26 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 27 | - platform: macos 28 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 29 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 30 | - platform: web 31 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 32 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 33 | - platform: windows 34 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 35 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.ignoreCMakeListsMissing": true, 3 | "search.exclude": { 4 | "**/fastlane": true 5 | }, 6 | "dart.analysisExcludedFolders": [ 7 | "**/fastlane/**" 8 | ], 9 | "files.associations": { 10 | "xiosbase": "cpp", 11 | "utility": "cpp", 12 | "xstring": "cpp", 13 | "xtree": "cpp", 14 | "algorithm": "cpp", 15 | "any": "cpp", 16 | "array": "cpp", 17 | "atomic": "cpp", 18 | "bit": "cpp", 19 | "cctype": "cpp", 20 | "charconv": "cpp", 21 | "chrono": "cpp", 22 | "cinttypes": "cpp", 23 | "clocale": "cpp", 24 | "cmath": "cpp", 25 | "codecvt": "cpp", 26 | "compare": "cpp", 27 | "concepts": "cpp", 28 | "condition_variable": "cpp", 29 | "coroutine": "cpp", 30 | "cstddef": "cpp", 31 | "cstdint": "cpp", 32 | "cstdio": "cpp", 33 | "cstdlib": "cpp", 34 | "cstring": "cpp", 35 | "ctime": "cpp", 36 | "cwchar": "cpp", 37 | "exception": "cpp", 38 | "filesystem": "cpp", 39 | "format": "cpp", 40 | "forward_list": "cpp", 41 | "functional": "cpp", 42 | "future": "cpp", 43 | "initializer_list": "cpp", 44 | "iomanip": "cpp", 45 | "ios": "cpp", 46 | "iosfwd": "cpp", 47 | "iostream": "cpp", 48 | "istream": "cpp", 49 | "iterator": "cpp", 50 | "limits": "cpp", 51 | "list": "cpp", 52 | "locale": "cpp", 53 | "map": "cpp", 54 | "memory": "cpp", 55 | "mutex": "cpp", 56 | "new": "cpp", 57 | "optional": "cpp", 58 | "ostream": "cpp", 59 | "ratio": "cpp", 60 | "set": "cpp", 61 | "sstream": "cpp", 62 | "stdexcept": "cpp", 63 | "stop_token": "cpp", 64 | "streambuf": "cpp", 65 | "string": "cpp", 66 | "system_error": "cpp", 67 | "thread": "cpp", 68 | "tuple": "cpp", 69 | "type_traits": "cpp", 70 | "typeinfo": "cpp", 71 | "unordered_map": "cpp", 72 | "variant": "cpp", 73 | "vector": "cpp", 74 | "xfacet": "cpp", 75 | "xhash": "cpp", 76 | "xlocale": "cpp", 77 | "xlocbuf": "cpp", 78 | "xlocinfo": "cpp", 79 | "xlocmes": "cpp", 80 | "xlocmon": "cpp", 81 | "xlocnum": "cpp", 82 | "xloctime": "cpp", 83 | "xmemory": "cpp", 84 | "xtr1common": "cpp", 85 | "xutility": "cpp" 86 | } 87 | } -------------------------------------------------------------------------------- /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 https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /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 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/kazumi/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.kazumi 2 | 3 | import android.content.Intent 4 | import android.os.Build; 5 | import android.net.Uri 6 | import android.os.Bundle 7 | import androidx.annotation.NonNull 8 | import io.flutter.embedding.engine.FlutterEngine 9 | import io.flutter.plugin.common.MethodChannel 10 | import io.flutter.embedding.android.FlutterActivity 11 | 12 | class MainActivity: FlutterActivity() { 13 | private val CHANNEL = "com.predidit.kazumi/intent" 14 | 15 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 16 | super.configureFlutterEngine(flutterEngine) 17 | MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> 18 | if (call.method == "openWithMime") { 19 | val url = call.argument("url") 20 | val mimeType = call.argument("mimeType") 21 | if (url != null && mimeType != null) { 22 | openWithMime(url, mimeType) 23 | result.success(null) 24 | } else { 25 | result.error("INVALID_ARGUMENT", "URL and MIME type required", null) 26 | } 27 | } 28 | if (call.method == "checkIfInMultiWindowMode") { 29 | val isInMultiWindow = checkIfInMultiWindowMode() 30 | result.success(isInMultiWindow) 31 | } 32 | result.notImplemented() 33 | } 34 | } 35 | 36 | private fun openWithMime(url: String, mimeType: String) { 37 | val intent = Intent() 38 | intent.action = Intent.ACTION_VIEW 39 | intent.setDataAndType(Uri.parse(url), mimeType) 40 | startActivity(intent) 41 | } 42 | 43 | private fun checkIfInMultiWindowMode(): Boolean { 44 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 45 | this.isInMultiWindowMode 46 | } else { 47 | false 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /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-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffffff 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | } 19 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "8.7.0" apply false 23 | id "org.jetbrains.kotlin.android" version "1.8.10" apply false 24 | } 25 | 26 | include ":app" 27 | -------------------------------------------------------------------------------- /assets/bbcode/BBCode.g4: -------------------------------------------------------------------------------- 1 | grammar BBCode; 2 | 3 | options { language=Dart; } 4 | 5 | document 6 | : element* EOF 7 | ; 8 | 9 | element 10 | : tag 11 | | plain 12 | | bgm 13 | | sticker 14 | ; 15 | 16 | tag 17 | : '[' tagName=STRING ('=' attr=STRING)? ']' content=element* '[/' STRING ']' 18 | ; 19 | 20 | plain 21 | : (STRING | '=' | '/' | '[' | ']' | '(' | ')')+ 22 | // workaround unless these will break tag reconginze 23 | | '[来自Bangumi for android]' 24 | | '[来自Bangumi for iOS]' 25 | ; 26 | 27 | bgm 28 | : ('(bgm' | '(BGM') id=STRING ')' 29 | ; 30 | 31 | sticker 32 | : '(=A=)' 33 | | '(=w=)' 34 | | '(-w=)' 35 | | '(S_S)' 36 | | '(=v=)' 37 | | '(@_@)' 38 | | '(=W=)' 39 | | '(TAT)' 40 | | '(T_T)' 41 | | '(=\'=)' 42 | | '(=3=)' 43 | | '(= =\')' 44 | | '(=///=)' 45 | | '(=.,=)' 46 | | '(:P)' 47 | | '(LOL)'; 48 | 49 | STRING : ~[=[\]()]+; 50 | -------------------------------------------------------------------------------- /assets/images/danmaku_off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/danmaku_setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/forward_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/assets/images/forward_80.png -------------------------------------------------------------------------------- /assets/images/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/assets/images/loading.png -------------------------------------------------------------------------------- /assets/images/logo/logo_android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/assets/images/logo/logo_android.png -------------------------------------------------------------------------------- /assets/images/logo/logo_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/assets/images/logo/logo_ios.png -------------------------------------------------------------------------------- /assets/images/logo/logo_lanczos.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/assets/images/logo/logo_lanczos.ico -------------------------------------------------------------------------------- /assets/images/logo/logo_linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/assets/images/logo/logo_linux.png -------------------------------------------------------------------------------- /assets/images/logo/logo_rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/assets/images/logo/logo_rounded.png -------------------------------------------------------------------------------- /assets/images/logo/logo_windows.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/assets/images/logo/logo_windows.ico -------------------------------------------------------------------------------- /assets/images/noface.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/assets/images/noface.jpeg -------------------------------------------------------------------------------- /assets/images/playing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/assets/images/playing.gif -------------------------------------------------------------------------------- /assets/linux/DEBIAN/postinst: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | ln -sf /opt/Kazumi/kazumi /usr/bin/kazumi 3 | chmod +x /usr/bin/kazumi 4 | update-mime-database /usr/share/mime || true 5 | update-desktop-database /usr/share/applications || true 6 | exit 0 -------------------------------------------------------------------------------- /assets/linux/DEBIAN/postrm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | rm /usr/bin/kazumi 3 | update-mime-database /usr/share/mime || true 4 | update-desktop-database /usr/share/applications || true 5 | exit 0 -------------------------------------------------------------------------------- /assets/linux/io.github.Predidit.Kazumi.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Kazumi 3 | Comment=Watch Animes online with danmaku support. 4 | Comment[zh_CN]=一款好用的追番软件 5 | Exec=kazumi 6 | Icon=io.github.Predidit.Kazumi 7 | Terminal=false 8 | Type=Application 9 | Categories=AudioVideo;Audio;Video; 10 | -------------------------------------------------------------------------------- /assets/plugins/DM84.json: -------------------------------------------------------------------------------- 1 | { 2 | "api": "1", 3 | "type": "anime", 4 | "name": "DM84", 5 | "version": "1.1", 6 | "muliSources": true, 7 | "useWebview": true, 8 | "useNativePlayer": true, 9 | "userAgent": "", 10 | "baseURL": "https://dm84.tv/", 11 | "searchURL":"https://dm84.tv/s----------.html?wd=@keyword", 12 | "searchList": "//div/div[3]/ul/li", 13 | "searchName": "//div/a[2]", 14 | "searchResult": "//div/a[2]", 15 | "chapterRoads": "//div/div[4]/div/ul", 16 | "chapterResult": "//li/a" 17 | } -------------------------------------------------------------------------------- /assets/plugins/aafun.json: -------------------------------------------------------------------------------- 1 | { 2 | "api": "4", 3 | "type": "anime", 4 | "name": "aafun", 5 | "version": "1.0", 6 | "muliSources": true, 7 | "useWebview": true, 8 | "useNativePlayer": true, 9 | "userAgent": "", 10 | "baseURL": "https://www.aafun.cc/", 11 | "searchURL": "https://www.aafun.cc/feng-s.html?wd=@keyword&submit=", 12 | "searchList": "//div/div[2]/div/div[2]/div/div/div[1]/div/div[2]/div/ul/li", 13 | "searchName": "//div/div/div[2]/div[1]/div/a", 14 | "searchResult": "//div/div/div[2]/div[1]/div/a", 15 | "chapterRoads": "//div[2]/div[2]/div/div/div[2]/div/div[1]/div[2]/div/div/div/ul", 16 | "chapterResult": "//li/a" 17 | } -------------------------------------------------------------------------------- /assets/plugins/xfdm.json: -------------------------------------------------------------------------------- 1 | { 2 | "api": "3", 3 | "type": "anime", 4 | "name": "xfdm", 5 | "version": "1.5", 6 | "muliSources": true, 7 | "useWebview": true, 8 | "useNativePlayer": true, 9 | "usePost": false, 10 | "useLegacyParser": true, 11 | "userAgent": "", 12 | "baseURL": "https://dm1.xfdm.pro/", 13 | "searchURL": "https://dm1.xfdm.pro/search.html?wd=@keyword", 14 | "searchList": "//div[contains(@class, 'search-box')]", 15 | "searchName": "//div[3]/div[1]/div[1]", 16 | "searchResult": "//div[3]/div[2]/a[1]", 17 | "chapterRoads": "//ul[contains(@class, 'anthology-list-play')]", 18 | "chapterResult": "//li/a", 19 | "referer": "" 20 | } 21 | -------------------------------------------------------------------------------- /assets/shaders/Anime4K_AutoDownscalePre_x2.glsl: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | // Anyone is free to copy, modify, publish, use, compile, sell, or 4 | // distribute this software, either in source code form or as a compiled 5 | // binary, for any purpose, commercial or non-commercial, and by any 6 | // means. 7 | 8 | // In jurisdictions that recognize copyright laws, the author or authors 9 | // of this software dedicate any and all copyright interest in the 10 | // software to the public domain. We make this dedication for the benefit 11 | // of the public at large and to the detriment of our heirs and 12 | // successors. We intend this dedication to be an overt act of 13 | // relinquishment in perpetuity of all present and future rights to this 14 | // software under copyright law. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | // OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | // For more information, please refer to 25 | 26 | //!DESC Anime4K-v4.0-AutoDownscalePre-x2 27 | //!HOOK MAIN 28 | //!BIND HOOKED 29 | //!BIND NATIVE 30 | //!WHEN OUTPUT.w NATIVE.w / 2.0 < OUTPUT.h NATIVE.h / 2.0 < * OUTPUT.w NATIVE.w / 1.2 > OUTPUT.h NATIVE.h / 1.2 > * * 31 | //!WIDTH OUTPUT.w 32 | //!HEIGHT OUTPUT.h 33 | 34 | vec4 hook() { 35 | return HOOKED_tex(HOOKED_pos); 36 | } 37 | -------------------------------------------------------------------------------- /assets/shaders/Anime4K_AutoDownscalePre_x4.glsl: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | // Anyone is free to copy, modify, publish, use, compile, sell, or 4 | // distribute this software, either in source code form or as a compiled 5 | // binary, for any purpose, commercial or non-commercial, and by any 6 | // means. 7 | 8 | // In jurisdictions that recognize copyright laws, the author or authors 9 | // of this software dedicate any and all copyright interest in the 10 | // software to the public domain. We make this dedication for the benefit 11 | // of the public at large and to the detriment of our heirs and 12 | // successors. We intend this dedication to be an overt act of 13 | // relinquishment in perpetuity of all present and future rights to this 14 | // software under copyright law. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | // OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | // For more information, please refer to 25 | 26 | //!DESC Anime4K-v3.2-AutoDownscalePre-x4 27 | //!HOOK MAIN 28 | //!BIND HOOKED 29 | //!BIND NATIVE 30 | //!WHEN OUTPUT.w NATIVE.w / 4.0 < OUTPUT.h NATIVE.h / 4.0 < * OUTPUT.w NATIVE.w / 2.4 > OUTPUT.h NATIVE.h / 2.4 > * * 31 | //!WIDTH OUTPUT.w 2 / 32 | //!HEIGHT OUTPUT.h 2 / 33 | 34 | vec4 hook() { 35 | return HOOKED_tex(HOOKED_pos); 36 | } 37 | -------------------------------------------------------------------------------- /assets/shaders/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 bloc97 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /assets/statements/statements.txt: -------------------------------------------------------------------------------- 1 | 在使用本软件之前,请您仔细阅读以下内容,并确保您充分理解并同意以下条款: 2 | 1、本软件为开源软件,您应该免费获取和使用。如果您是从第三方付费获取,建议您向其索取赔偿。 3 | 2、本软件完全基于您个人意愿使用,您应该对自己的使用行为和所有结果承担全部责任。 4 | 3、本软件仅供学习交流、科研等非商业性质的用途,严禁将本软件用于商业目的。如有任何商业行为,均与本软件无关。 5 | 4、本软件并不保证与所有操作系统或硬件设备兼容。本软件作者或贡献者不对因使用本软件而产生的任何技术或安全问题承担责任。 6 | 5、本软件作者或贡献者不承担因使用本软件而造成的任何直接、间接、特殊或后果性的损失或损害的责任,包括但不限于财产损失、商业利润损失、信息或数据丢失或损坏等。 7 | 6、本软件使用者应遵守国家相关法律法规和使用规范,不得利用本软件从事任何违法违规行为。如因使用本软件而导致的违法行为,使用者应承担相应的法律责任。 8 | 7、本软件不会收集、存储、使用任何用户的个人信息,包括但不限于姓名、地址、电子邮件地址、电话号码等。在使用本软件过程中,不会进行任何形式的个人信息采集。 9 | 8、本软件作者或贡献者保留随时修改、增加、删除本免责声明中的内容而不另行通知的权利。 10 | 9、如果本软件存在侵犯您的合法权益的情况,请及时与作者联系,作者将会及时删除有关内容。 11 | 如您不同意本免责声明中的任何内容,请勿使用本软件。使用本软件即代表您已完全理解并同意上述内容。 -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | extensions: 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | A flutter app for collecting and watching anime online with custom rules. Use up to five lines of Xpath expressions to build your own rules. Supports rule import and rule sharing. Supports danmaku. In development (~ ̄▽ ̄)~ -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- 1 | ../../../../../android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- 1 | ../../../../../../static/screenshot/img_1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- 1 | ../../../../../../static/screenshot/img_2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- 1 | ../../../../../../static/screenshot/img_3.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- 1 | ../../../../../../static/screenshot/img_4.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/5.png: -------------------------------------------------------------------------------- 1 | ../../../../../../static/screenshot/img_5.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/6.png: -------------------------------------------------------------------------------- 1 | ../../../../../../static/screenshot/img_6.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | An anime collection APP based on custom rules. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Kazumi -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/full_description.txt: -------------------------------------------------------------------------------- 1 | 使用 flutter 开发的基于自定义规则的番剧采集与在线观看程序。使用最多五行基于 Xpath 语法的选择器构建自己的规则。支持规则导入与规则分享。绝赞开发中 (~ ̄▽ ̄)~ -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/short_description.txt: -------------------------------------------------------------------------------- 1 | 基于自定义规则的番剧采集APP,支持流媒体在线观看,支持弹幕。 -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/title.txt: -------------------------------------------------------------------------------- 1 | Kazumi -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /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 | 8 | -------------------------------------------------------------------------------- /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/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "darkbackground.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "filename" : "LaunchImageDark.png", 16 | "idiom" : "universal", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "filename" : "LaunchImage@2x.png", 21 | "idiom" : "universal", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "appearances" : [ 26 | { 27 | "appearance" : "luminosity", 28 | "value" : "dark" 29 | } 30 | ], 31 | "filename" : "LaunchImageDark@2x.png", 32 | "idiom" : "universal", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "filename" : "LaunchImage@3x.png", 37 | "idiom" : "universal", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "appearances" : [ 42 | { 43 | "appearance" : "luminosity", 44 | "value" : "dark" 45 | } 46 | ], 47 | "filename" : "LaunchImageDark@3x.png", 48 | "idiom" : "universal", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "author" : "xcode", 54 | "version" : 1 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@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 | NSPhotoLibraryAddUsageDescription 6 | 7 | CADisableMinimumFrameDurationOnPhone 8 | 9 | CFBundleDevelopmentRegion 10 | $(DEVELOPMENT_LANGUAGE) 11 | CFBundleDisplayName 12 | Kazumi 13 | CFBundleExecutable 14 | $(EXECUTABLE_NAME) 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | kazumi 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | $(FLUTTER_BUILD_NAME) 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | $(FLUTTER_BUILD_NUMBER) 29 | LSApplicationQueriesSchemes 30 | 31 | vlc-x-callback 32 | 33 | LSRequiresIPhoneOS 34 | 35 | NSAppTransportSecurity 36 | 37 | NSAllowsArbitraryLoads 38 | 39 | 40 | UIApplicationSupportsIndirectInputEvents 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | UIStatusBarHidden 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/app_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_modular/flutter_modular.dart'; 2 | import 'package:kazumi/pages/index_module.dart'; 3 | 4 | class AppModule extends Module { 5 | @override 6 | void binds(i) { 7 | 8 | } 9 | 10 | @override 11 | void routes(r) { 12 | r.module("/", module: IndexModule()); 13 | } 14 | } -------------------------------------------------------------------------------- /lib/bbcode/README.md: -------------------------------------------------------------------------------- 1 | # 基于 antlr4 的 BBCode 解析 2 | 3 | ## 相关文件 4 | 5 | - [assets/bbcode/BBCode.g4](../../assets/bbcode/BBCode.g4): antlr4 语法文件 6 | - [lib/bbcode/generated](../../lib/bbcode/generated): antlr4 生成的 dart 代码所在文件夹 7 | 8 | ## 关键文件 9 | 10 | - [lib/bbcode/bbcode_elements.dart](bbcode_elements.dart): BBCode 元素 11 | - [lib/bbcode/bbcode_base_listener.dart](bbcode_base_listener.dart): BBCode 解析器的入口文件 12 | - [lib/bbcode/bbcode_widget.dart](bbcode_widget.dart): BBCode 组件 13 | 14 | ## 如何开发 15 | 16 | ### 配置环境 17 | 18 | 1. 根据[官方文档](https://github.com/antlr/antlr4/blob/dev/doc/dart-target.md)配置环境 19 | 2. 在 IDE 中安装 `antlr v4` 插件 20 | 21 | ### 开发 22 | 23 | 1. 修改 [assets/bbcode/BBCode.g4](../../assets/bbcode/BBCode.g4) 文件,通过插件的 Preview 功能确定解析是否正确 24 | 2. 通过该文件生成新的 dart 文件到 [lib/bbcode/generated](../../lib/bbcode/generated) 文件夹内,删除无用文件 25 | 3. 参考文件内的注释进行修改 26 | 27 | ### 测试 BBCode 28 | 29 | ```dart 30 | import 'package:flutter/material.dart'; 31 | import 'bbcode/bbcode_widget.dart'; 32 | 33 | void main() { 34 | runApp(MyApp()); 35 | } 36 | 37 | class MyApp extends StatelessWidget { 38 | @override 39 | Widget build(BuildContext context) { 40 | return MaterialApp( 41 | home: Scaffold( 42 | appBar: AppBar(title: const Text('BBCode Parser')), 43 | body: Card( 44 | color: Theme.of(context).colorScheme.secondaryContainer, 45 | child: const Padding( 46 | padding: EdgeInsets.all(8.0), 47 | child: Padding( 48 | padding: EdgeInsets.all(16), 49 | child: BBCodeWidget( 50 | bbcode: 51 | '[quote][b]用户[/b]说:[s]测试表情和删除线(bgm35)[/s][/quote]\n[mask]测试特殊符号[]()测试字符表情(TAT)[/mask][url=https://bangumi.tv/blog/348736]测试链接[/url][url]https://bangumi.tv/blog/348736[/url][img]https://bangumi.tv/img/rc3/logo_2x.png[/img]\n\n[color=grey][size=10][来自Bangumi for android] [url=https://bgm.tv/group/topic/350677][color=grey]获取[/color][/url][/size][/color]', 52 | ), 53 | ), 54 | ), 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /lib/bbcode/bbcode_elements.dart: -------------------------------------------------------------------------------- 1 | // 记录进入 tag 时 list 所在位置 2 | class BBCodeTag { 3 | int? bold; 4 | int? italic; 5 | int? underline; 6 | int? strikeThrough; 7 | int? masked; 8 | int? quoted; 9 | int? code; 10 | int? size; 11 | int? color; 12 | int? link; 13 | int? img; 14 | 15 | void clear() { 16 | bold = null; 17 | italic = null; 18 | underline = null; 19 | strikeThrough = null; 20 | masked = null; 21 | quoted = null; 22 | code = null; 23 | size = null; 24 | color = null; 25 | link = null; 26 | img = null; 27 | } 28 | } 29 | 30 | class BBCodeText { 31 | String text; 32 | 33 | bool bold = false; 34 | bool italic = false; 35 | bool underline = false; 36 | bool strikeThrough = false; 37 | bool masked = false; 38 | bool quoted = false; 39 | bool code = false; 40 | 41 | int size = 14; 42 | String? color; 43 | String? link; 44 | 45 | BBCodeText({ 46 | required this.text, 47 | this.bold = false, 48 | this.italic = false, 49 | this.underline = false, 50 | this.strikeThrough = false, 51 | this.masked = false, 52 | this.quoted = false, 53 | this.code = false, 54 | this.size = 14, 55 | this.color, 56 | this.link, 57 | }); 58 | } 59 | 60 | class BBCodeBgm { 61 | int id; 62 | 63 | BBCodeBgm({required this.id}); 64 | } 65 | 66 | class BBCodeSticker { 67 | int id; 68 | 69 | BBCodeSticker({required this.id}); 70 | } 71 | 72 | class BBCodeImg { 73 | String imageUrl; 74 | 75 | BBCodeImg({required this.imageUrl}); 76 | } 77 | 78 | BBCodeTag bbCodeTag = BBCodeTag(); 79 | -------------------------------------------------------------------------------- /lib/bbcode/generated/BBCode.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | T__9=10 11 | T__10=11 12 | T__11=12 13 | T__12=13 14 | T__13=14 15 | T__14=15 16 | T__15=16 17 | T__16=17 18 | T__17=18 19 | T__18=19 20 | T__19=20 21 | T__20=21 22 | T__21=22 23 | T__22=23 24 | T__23=24 25 | T__24=25 26 | T__25=26 27 | T__26=27 28 | STRING=28 29 | '['=1 30 | '='=2 31 | ']'=3 32 | '[/'=4 33 | '/'=5 34 | '('=6 35 | ')'=7 36 | '[来自Bangumi for android]'=8 37 | '[来自Bangumi for iOS]'=9 38 | '(bgm'=10 39 | '(BGM'=11 40 | '(=A=)'=12 41 | '(=w=)'=13 42 | '(-w=)'=14 43 | '(S_S)'=15 44 | '(=v=)'=16 45 | '(@_@)'=17 46 | '(=W=)'=18 47 | '(TAT)'=19 48 | '(T_T)'=20 49 | '(=\'=)'=21 50 | '(=3=)'=22 51 | '(= =\')'=23 52 | '(=///=)'=24 53 | '(=.,=)'=25 54 | '(:P)'=26 55 | '(LOL)'=27 56 | -------------------------------------------------------------------------------- /lib/bbcode/generated/BBCodeListener.dart: -------------------------------------------------------------------------------- 1 | import 'package:antlr4/antlr4.dart'; 2 | 3 | import 'BBCodeParser.dart'; 4 | 5 | /// This abstract class defines a complete listener for a parse tree produced by 6 | /// [BBCodeParser]. 7 | abstract class BBCodeListener extends ParseTreeListener { 8 | /// Enter a parse tree produced by [BBCodeParser.document]. 9 | /// [ctx] the parse tree 10 | void enterDocument(DocumentContext ctx); 11 | /// Exit a parse tree produced by [BBCodeParser.document]. 12 | /// [ctx] the parse tree 13 | void exitDocument(DocumentContext ctx); 14 | 15 | /// Enter a parse tree produced by [BBCodeParser.element]. 16 | /// [ctx] the parse tree 17 | void enterElement(ElementContext ctx); 18 | /// Exit a parse tree produced by [BBCodeParser.element]. 19 | /// [ctx] the parse tree 20 | void exitElement(ElementContext ctx); 21 | 22 | /// Enter a parse tree produced by [BBCodeParser.tag]. 23 | /// [ctx] the parse tree 24 | void enterTag(TagContext ctx); 25 | /// Exit a parse tree produced by [BBCodeParser.tag]. 26 | /// [ctx] the parse tree 27 | void exitTag(TagContext ctx); 28 | 29 | /// Enter a parse tree produced by [BBCodeParser.plain]. 30 | /// [ctx] the parse tree 31 | void enterPlain(PlainContext ctx); 32 | /// Exit a parse tree produced by [BBCodeParser.plain]. 33 | /// [ctx] the parse tree 34 | void exitPlain(PlainContext ctx); 35 | 36 | /// Enter a parse tree produced by [BBCodeParser.bgm]. 37 | /// [ctx] the parse tree 38 | void enterBgm(BgmContext ctx); 39 | /// Exit a parse tree produced by [BBCodeParser.bgm]. 40 | /// [ctx] the parse tree 41 | void exitBgm(BgmContext ctx); 42 | 43 | /// Enter a parse tree produced by [BBCodeParser.sticker]. 44 | /// [ctx] the parse tree 45 | void enterSticker(StickerContext ctx); 46 | /// Exit a parse tree produced by [BBCodeParser.sticker]. 47 | /// [ctx] the parse tree 48 | void exitSticker(StickerContext ctx); 49 | } -------------------------------------------------------------------------------- /lib/bean/appbar/drag_to_move_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/utils/utils.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:window_manager/window_manager.dart'; 4 | 5 | /// A widget for drag to move window. 6 | /// 7 | /// When you have hidden the title bar, you can add this widget to move the window position. 8 | /// 9 | /// {@tool snippet} 10 | /// 11 | /// The sample creates a red box, drag the box to move the window. 12 | /// 13 | /// ```dart 14 | /// DragToMoveArea( 15 | /// child: Container( 16 | /// width: 300, 17 | /// height: 32, 18 | /// color: Colors.red, 19 | /// ), 20 | /// ) 21 | /// ``` 22 | /// {@end-tool} 23 | class DragToMoveArea extends StatelessWidget { 24 | const DragToMoveArea({ 25 | super.key, 26 | required this.child, 27 | }); 28 | 29 | final Widget child; 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return GestureDetector( 34 | behavior: HitTestBehavior.translucent, 35 | onPanStart: (_) => (Utils.isDesktop()) ? windowManager.startDragging() : null, 36 | child: child, 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/bean/card/character_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/utils/utils.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:kazumi/modules/characters/character_item.dart'; 4 | import 'package:kazumi/pages/info/character_page.dart'; 5 | 6 | class CharacterCard extends StatelessWidget { 7 | const CharacterCard({ 8 | super.key, 9 | required this.characterItem, 10 | }); 11 | 12 | final CharacterItem characterItem; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return ListTile( 17 | leading: CircleAvatar( 18 | backgroundImage: characterItem.avator.grid.isEmpty 19 | ? NetworkImage('https://bangumi.tv/img/info_only.png') 20 | : NetworkImage(characterItem.avator.grid), 21 | ), 22 | title: Text( 23 | characterItem.name, 24 | overflow: TextOverflow.ellipsis, 25 | maxLines: 1, 26 | ), 27 | subtitle: characterItem.actorList.isNotEmpty 28 | ? Text(characterItem.actorList[0].name) 29 | : null, 30 | trailing: Text(characterItem.relation), 31 | onTap: () { 32 | showModalBottomSheet( 33 | isScrollControlled: true, 34 | constraints: BoxConstraints( 35 | maxHeight: MediaQuery.of(context).size.height * 3 / 4, 36 | maxWidth: (Utils.isDesktop() || Utils.isTablet()) 37 | ? MediaQuery.of(context).size.width * 9 / 16 38 | : MediaQuery.of(context).size.width), 39 | clipBehavior: Clip.antiAlias, 40 | context: context, 41 | builder: (context) { 42 | return CharacterPage(characterID: characterItem.id); 43 | }); 44 | }, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/bean/card/staff_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:kazumi/modules/staff/staff_item.dart'; 3 | 4 | class StaffCard extends StatelessWidget { 5 | const StaffCard({ 6 | super.key, 7 | required this.staffFullItem, 8 | }); 9 | 10 | final StaffFullItem staffFullItem; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return ListTile( 15 | leading: CircleAvatar( 16 | backgroundImage: staffFullItem.staff.images?.grid == null 17 | ? NetworkImage('https://bangumi.tv/img/info_only.png') 18 | : NetworkImage(staffFullItem.staff.images!.grid), 19 | ), 20 | title: Text( 21 | staffFullItem.staff.name, 22 | overflow: TextOverflow.ellipsis, 23 | maxLines: 1, 24 | ), 25 | subtitle: staffFullItem.staff.nameCN.isNotEmpty 26 | ? Text(staffFullItem.staff.nameCN) 27 | : null, 28 | trailing: Text(staffFullItem.positions.isNotEmpty 29 | ? (staffFullItem.positions[0].type.cn) 30 | : ''), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/bean/settings/color_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | final List> colorThemeTypes = [ 4 | {'color': Colors.green, 'label': '默认'}, 5 | {'color': Colors.teal, 'label': '青色'}, 6 | {'color': Colors.blue, 'label': '蓝色'}, 7 | {'color': Colors.indigo, 'label': '靛蓝色'}, 8 | {'color': const Color(0xff6750a4), 'label': '紫罗兰色'}, 9 | {'color': Colors.pink, 'label': '粉红色'}, 10 | {'color': Colors.yellow, 'label': '黄色'}, 11 | {'color': Colors.orange, 'label': '橙色'}, 12 | {'color': Colors.deepOrange, 'label': '深橙色'}, 13 | ]; 14 | -------------------------------------------------------------------------------- /lib/bean/settings/theme_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ThemeProvider extends ChangeNotifier { 4 | ThemeMode themeMode = ThemeMode.system; 5 | bool useDynamicColor = false; 6 | late ThemeData light; 7 | late ThemeData dark; 8 | 9 | void setTheme(ThemeData light, ThemeData dark, {bool notify = true}) { 10 | this.light = light; 11 | this.dark = dark; 12 | if (notify) notifyListeners(); 13 | } 14 | 15 | void setThemeMode(ThemeMode mode, {bool notify = true}) { 16 | themeMode = mode; 17 | if (notify) notifyListeners(); 18 | } 19 | 20 | void setDynamic(bool useDynamicColor, {bool notify = true}) { 21 | this.useDynamicColor = useDynamicColor; 22 | if (notify) notifyListeners(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/bean/widget/embedded_native_control_area.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:kazumi/utils/storage.dart'; 4 | 5 | class EmbeddedNativeControlArea extends StatefulWidget { 6 | /// The widget won't draw anything, just a placeholder for native window control. 7 | /// It only works on macOS at the moment. 8 | /// windows and linux have no way to embed native window control into flutter view. 9 | const EmbeddedNativeControlArea({ 10 | super.key, 11 | required this.child, 12 | this.requireOffset = true, 13 | }); 14 | 15 | final Widget child; 16 | final bool requireOffset; 17 | 18 | @override 19 | State createState() => _EmbeddedNativeControlAreaState(); 20 | } 21 | 22 | class _EmbeddedNativeControlAreaState extends State { 23 | bool showWindowButton = 24 | GStorage.setting.get(SettingBoxKey.showWindowButton, defaultValue: false); 25 | 26 | EdgeInsets get getInsets { 27 | if (!showWindowButton) { 28 | return EdgeInsets.zero; 29 | } 30 | if (!widget.requireOffset) { 31 | return EdgeInsets.zero; 32 | } 33 | if (Platform.isMacOS) { 34 | return const EdgeInsets.only(top: 22); 35 | } else { 36 | return EdgeInsets.zero; 37 | } 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return Padding( 43 | padding: getInsets, 44 | child: widget.child, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/bean/widget/error_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GeneralErrorWidget extends StatelessWidget { 4 | const GeneralErrorWidget({ 5 | required this.errMsg, 6 | this.actions, 7 | super.key, 8 | }); 9 | 10 | final String errMsg; 11 | final List? actions; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Column( 16 | crossAxisAlignment: CrossAxisAlignment.center, 17 | mainAxisAlignment: MainAxisAlignment.center, 18 | children: [ 19 | LayoutBuilder( 20 | builder: (BuildContext context, BoxConstraints constraints) { 21 | return ConstrainedBox( 22 | constraints: BoxConstraints( 23 | maxWidth: constraints.maxWidth * 2 / 3, 24 | ), 25 | child: Text( 26 | errMsg, 27 | textAlign: TextAlign.center, 28 | style: Theme.of(context).textTheme.titleSmall, 29 | ), 30 | ); 31 | }, 32 | ), 33 | const SizedBox(height: 20), 34 | if (actions != null) 35 | Wrap( 36 | alignment: WrapAlignment.center, 37 | spacing: 8, 38 | runSpacing: 8, 39 | children: actions!, 40 | ), 41 | ], 42 | ); 43 | } 44 | } 45 | 46 | class GeneralErrorButton extends StatelessWidget { 47 | const GeneralErrorButton({ 48 | super.key, 49 | required this.onPressed, 50 | required this.text, 51 | }); 52 | 53 | final Function() onPressed; 54 | final String text; 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | return FilledButton.tonal( 59 | onPressed: onPressed, 60 | style: ButtonStyle( 61 | backgroundColor: WidgetStateProperty.resolveWith((_) { 62 | return Theme.of(context).colorScheme.primary.withAlpha(20); 63 | }), 64 | ), 65 | child: Text( 66 | text, 67 | style: TextStyle(color: Theme.of(context).colorScheme.primary), 68 | ), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/bean/widget/scrollable_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/gestures.dart'; 3 | 4 | /// 滚动容器 5 | /// 支持鼠标滚轮滚动和拖动滚动 6 | /// 传入ListView的scrollController 7 | class ScrollableWrapper extends StatelessWidget { 8 | final Widget child; 9 | final ScrollController scrollController; 10 | 11 | const ScrollableWrapper({ 12 | super.key, 13 | required this.child, 14 | required this.scrollController, 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return MouseRegion( 20 | child: Listener( 21 | onPointerSignal: (pointerSignal) { 22 | // 鼠标滚轮滚动 23 | if (pointerSignal is PointerScrollEvent && 24 | scrollController.hasClients) { 25 | scrollController.position.moveTo( 26 | scrollController.offset + pointerSignal.scrollDelta.dy, 27 | curve: Curves.linear, 28 | ); 29 | } 30 | }, 31 | child: GestureDetector( 32 | onPanUpdate: (details) { 33 | // 拖动滚动 34 | if (scrollController.hasClients) { 35 | scrollController.position.moveTo( 36 | scrollController.offset - details.delta.dx, 37 | curve: Curves.linear, 38 | ); 39 | } 40 | }, 41 | child: child, 42 | ), 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/bean/widget/text_display.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TextDisplayWidget extends StatelessWidget { 4 | const TextDisplayWidget({super.key, required this.logLines}); 5 | 6 | final List logLines; 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return ListView.builder( 11 | itemCount: logLines.length, 12 | itemBuilder: (context, index) { 13 | return Text(logLines[index]); 14 | }, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/modules/bangumi/bangumi_item.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'bangumi_item.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class BangumiItemAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 0; 12 | 13 | @override 14 | BangumiItem 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 BangumiItem( 20 | id: fields[0] as int, 21 | type: fields[1] as int, 22 | name: fields[2] as String, 23 | nameCn: fields[3] as String, 24 | summary: fields[4] as String, 25 | airDate: fields[5] as String, 26 | airWeekday: fields[6] as int, 27 | rank: fields[7] as int, 28 | images: (fields[8] as Map).cast(), 29 | tags: fields[9] == null ? [] : (fields[9] as List).cast(), 30 | alias: fields[10] == null ? [] : (fields[10] as List).cast(), 31 | ratingScore: fields[11] == null ? 0.0 : fields[11] as double, 32 | votes: fields[12] == null ? 0 : fields[12] as int, 33 | votesCount: fields[13] == null ? [] : (fields[13] as List).cast(), 34 | ); 35 | } 36 | 37 | @override 38 | void write(BinaryWriter writer, BangumiItem obj) { 39 | writer 40 | ..writeByte(14) 41 | ..writeByte(0) 42 | ..write(obj.id) 43 | ..writeByte(1) 44 | ..write(obj.type) 45 | ..writeByte(2) 46 | ..write(obj.name) 47 | ..writeByte(3) 48 | ..write(obj.nameCn) 49 | ..writeByte(4) 50 | ..write(obj.summary) 51 | ..writeByte(5) 52 | ..write(obj.airDate) 53 | ..writeByte(6) 54 | ..write(obj.airWeekday) 55 | ..writeByte(7) 56 | ..write(obj.rank) 57 | ..writeByte(8) 58 | ..write(obj.images) 59 | ..writeByte(9) 60 | ..write(obj.tags) 61 | ..writeByte(10) 62 | ..write(obj.alias) 63 | ..writeByte(11) 64 | ..write(obj.ratingScore) 65 | ..writeByte(12) 66 | ..write(obj.votes) 67 | ..writeByte(13) 68 | ..write(obj.votesCount); 69 | } 70 | 71 | @override 72 | int get hashCode => typeId.hashCode; 73 | 74 | @override 75 | bool operator ==(Object other) => 76 | identical(this, other) || 77 | other is BangumiItemAdapter && 78 | runtimeType == other.runtimeType && 79 | typeId == other.typeId; 80 | } 81 | -------------------------------------------------------------------------------- /lib/modules/bangumi/bangumi_tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | part 'bangumi_tag.g.dart'; 4 | 5 | @HiveType(typeId: 4) 6 | class BangumiTag { 7 | @HiveField(0) 8 | final String name; 9 | @HiveField(1) 10 | final int count; 11 | @HiveField(2) 12 | final int totalCount; 13 | 14 | BangumiTag({ 15 | required this.name, 16 | required this.count, 17 | required this.totalCount, 18 | }); 19 | 20 | factory BangumiTag.fromJson(Map json) { 21 | return BangumiTag( 22 | name: json['name'] ?? '', 23 | count: json['count'] ?? 0, 24 | totalCount: json['total_cont'] ?? 0, 25 | ); 26 | } 27 | 28 | Map toJson() { 29 | return { 30 | 'name': name, 31 | 'count': count, 32 | 'total_cont': totalCount, 33 | }; 34 | } 35 | } -------------------------------------------------------------------------------- /lib/modules/bangumi/bangumi_tag.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'bangumi_tag.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class BangumiTagAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 4; 12 | 13 | @override 14 | BangumiTag 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 BangumiTag( 20 | name: fields[0] as String, 21 | count: fields[1] as int, 22 | totalCount: fields[2] as int, 23 | ); 24 | } 25 | 26 | @override 27 | void write(BinaryWriter writer, BangumiTag obj) { 28 | writer 29 | ..writeByte(3) 30 | ..writeByte(0) 31 | ..write(obj.name) 32 | ..writeByte(1) 33 | ..write(obj.count) 34 | ..writeByte(2) 35 | ..write(obj.totalCount); 36 | } 37 | 38 | @override 39 | int get hashCode => typeId.hashCode; 40 | 41 | @override 42 | bool operator ==(Object other) => 43 | identical(this, other) || 44 | other is BangumiTagAdapter && 45 | runtimeType == other.runtimeType && 46 | typeId == other.typeId; 47 | } 48 | -------------------------------------------------------------------------------- /lib/modules/bangumi/episode_item.dart: -------------------------------------------------------------------------------- 1 | class EpisodeInfo { 2 | int id; 3 | num episode; 4 | int type; 5 | String name; 6 | String nameCn; 7 | 8 | EpisodeInfo({ 9 | required this.id, 10 | required this.episode, 11 | required this.type, 12 | required this.name, 13 | required this.nameCn, 14 | }); 15 | 16 | factory EpisodeInfo.fromJson(Map json) { 17 | return EpisodeInfo( 18 | id: json['id'] ?? 0, 19 | episode: json['sort'] ?? 0, 20 | type: json['type'] ?? 0, 21 | name: json['name'] ?? '', 22 | nameCn: json['name_cn'] ?? ''); 23 | } 24 | 25 | factory EpisodeInfo.fromTemplate() { 26 | return EpisodeInfo(id: 0, episode: 0, type: 0, name: '', nameCn: ''); 27 | } 28 | 29 | void reset() { 30 | id = 0; 31 | episode = 0; 32 | type = 0; 33 | name = ''; 34 | nameCn = ''; 35 | } 36 | 37 | String readType() { 38 | switch (type) { 39 | case 0: 40 | return 'ep'; 41 | case 1: 42 | return 'sp'; 43 | case 2: 44 | return 'op'; 45 | case 3: 46 | return 'ed'; 47 | default: 48 | return ''; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/modules/bangumi/weekday_item.dart: -------------------------------------------------------------------------------- 1 | class Weekday { 2 | String? en; 3 | String? cn; 4 | String? ja; 5 | int? id; 6 | 7 | Weekday({this.en, this.cn, this.ja, this.id}); 8 | 9 | factory Weekday.fromJson(Map json) { 10 | return Weekday( 11 | en: json['en'], 12 | cn: json['cn'], 13 | ja: json['ja'], 14 | id: json['id'], 15 | ); 16 | } 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/modules/character/character_full_item.dart: -------------------------------------------------------------------------------- 1 | class CharacterFullItem { 2 | final int id; 3 | final String name; 4 | final String nameCN; 5 | final String info; 6 | final String summary; 7 | final String image; 8 | 9 | CharacterFullItem({ 10 | required this.id, 11 | required this.name, 12 | required this.nameCN, 13 | required this.info, 14 | required this.summary, 15 | required this.image, 16 | }); 17 | 18 | factory CharacterFullItem.fromJson(Map json) { 19 | return CharacterFullItem( 20 | id: json['id'] ?? 0, 21 | name: json['name'] ?? '', 22 | nameCN: json['nameCN'] ?? '', 23 | info: json['info'] ?? '', 24 | summary: json['summary'] ?? '', 25 | image: json['images']['large'] ?? '', 26 | ); 27 | } 28 | 29 | factory CharacterFullItem.fromTemplate() { 30 | return CharacterFullItem( 31 | id: 0, 32 | name: '', 33 | nameCN: '', 34 | info: '', 35 | summary: '', 36 | image: '', 37 | ); 38 | } 39 | 40 | Map toJson() { 41 | return { 42 | 'id': id, 43 | 'name': name, 44 | 'nameCN': nameCN, 45 | 'info': info, 46 | 'summary': summary, 47 | }; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/modules/characters/actor_item.dart: -------------------------------------------------------------------------------- 1 | class ActorAvator { 2 | final String small; 3 | final String medium; 4 | final String grid; 5 | final String large; 6 | 7 | ActorAvator({ 8 | required this.small, 9 | required this.medium, 10 | required this.grid, 11 | required this.large, 12 | }); 13 | 14 | factory ActorAvator.fromJson(Map json) { 15 | return ActorAvator( 16 | small: json['small'] ?? '', 17 | medium: json['medium'] ?? '', 18 | grid: json['grid'] ?? '', 19 | large: json['large'] ?? '', 20 | ); 21 | } 22 | 23 | Map toJson() { 24 | return { 25 | 'small': small, 26 | 'medium': medium, 27 | 'grid': grid, 28 | 'large': large, 29 | }; 30 | } 31 | } 32 | 33 | class ActorItem { 34 | final int id; 35 | final int type; 36 | final String name; 37 | final ActorAvator avator; 38 | 39 | ActorItem({ 40 | required this.id, 41 | required this.type, 42 | required this.name, 43 | required this.avator, 44 | }); 45 | 46 | factory ActorItem.fromJson(Map json) { 47 | return ActorItem( 48 | id: json['id'] ?? 0, 49 | type: json['type'] ?? 0, 50 | name: json['name'] ?? '', 51 | avator: ActorAvator.fromJson(json['images'] as Map), 52 | ); 53 | } 54 | 55 | Map toJson() { 56 | return { 57 | 'id': id, 58 | 'type': type, 59 | 'name': name, 60 | 'images': avator.toJson(), 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/modules/characters/character_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/modules/characters/actor_item.dart'; 2 | 3 | class CharacterAvator { 4 | final String small; 5 | final String medium; 6 | final String grid; 7 | final String large; 8 | 9 | CharacterAvator({ 10 | required this.small, 11 | required this.medium, 12 | required this.grid, 13 | required this.large, 14 | }); 15 | 16 | factory CharacterAvator.fromJson(Map json) { 17 | return CharacterAvator( 18 | small: json['small'] ?? '', 19 | medium: json['medium'] ?? '', 20 | grid: json['grid'] ?? '', 21 | large: json['large'] ?? '', 22 | ); 23 | } 24 | 25 | Map toJson() { 26 | return { 27 | 'small': small, 28 | 'medium': medium, 29 | 'grid': grid, 30 | 'large': large, 31 | }; 32 | } 33 | } 34 | 35 | class CharacterExtraInfo { 36 | String nameCn; 37 | String summary; 38 | 39 | CharacterExtraInfo({required this.nameCn, required this.summary}); 40 | 41 | factory CharacterExtraInfo.fromJson(Map json) { 42 | String nameCn = ''; 43 | final String hasNameCn = json['infobox'][0]['key']; 44 | if (hasNameCn == '简体中文名') { 45 | nameCn = json['infobox'][0]['value']; 46 | } 47 | return CharacterExtraInfo( 48 | nameCn: nameCn, 49 | summary: json['summary'] 50 | ); 51 | } 52 | } 53 | 54 | class CharacterItem { 55 | final int id; 56 | final int type; 57 | final String name; 58 | final String relation; 59 | final CharacterAvator avator; 60 | final List actorList; 61 | CharacterExtraInfo info; 62 | 63 | CharacterItem({ 64 | required this.id, 65 | required this.type, 66 | required this.name, 67 | required this.relation, 68 | required this.avator, 69 | required this.actorList, 70 | required this.info 71 | }); 72 | 73 | factory CharacterItem.fromJson(Map json) { 74 | var list = json['actors'] as List; 75 | List resActorList = 76 | list.map((i) => ActorItem.fromJson(i)).toList(); 77 | return CharacterItem( 78 | id: json['id'] ?? 0, 79 | type: json['type'] ?? 0, 80 | name: json['name'] ?? '', 81 | relation: json['relation'] ?? '未知', 82 | avator: CharacterAvator.fromJson(json['images'] as Map), 83 | actorList: resActorList, 84 | info: CharacterExtraInfo(nameCn: '', summary: '') 85 | ); 86 | } 87 | 88 | Map toJson() { 89 | return { 90 | 'id': id, 91 | 'type': type, 92 | 'name': name, 93 | 'relation': relation, 94 | 'images': avator.toJson(), 95 | 'actors': actorList.map((e) => e.toJson()).toList(), 96 | }; 97 | } 98 | } -------------------------------------------------------------------------------- /lib/modules/characters/characters_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/request/api.dart'; 2 | import 'package:kazumi/modules/characters/character_item.dart'; 3 | 4 | /// The response from [Api.bangumiInfoByID] 5 | /// It contains a list of [CharacterItem] 6 | /// It is used to show general information about seraval bangumi characters 7 | class CharactersResponse { 8 | final List charactersList; 9 | 10 | CharactersResponse({ 11 | required this.charactersList, 12 | }); 13 | 14 | factory CharactersResponse.fromJson(List list) { 15 | List resCharactersList = 16 | list.map((i) => CharacterItem.fromJson(i)).toList(); 17 | return CharactersResponse( 18 | charactersList: resCharactersList, 19 | ); 20 | } 21 | 22 | factory CharactersResponse.fromTemplate() { 23 | return CharactersResponse( 24 | charactersList: [], 25 | ); 26 | } 27 | 28 | Map toJson() { 29 | return { 30 | 'charactersList': charactersList.map((e) => e.toJson()).toList(), 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/modules/collect/collect_change_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | part 'collect_change_module.g.dart'; 4 | 5 | // The box stores the changes history of collected bangumi 6 | // The changes will be used to sync with webDav 7 | @HiveType(typeId: 5) 8 | class CollectedBangumiChange { 9 | // timestamp in seconds 10 | // hivebox has limited the length of key, the max number is 4294967295 11 | // we have to use timestamp in seconds as key to avoid key conflict and hive key limit 12 | @HiveField(0) 13 | int id; 14 | 15 | @HiveField(1) 16 | int bangumiID; 17 | 18 | // 1. add 19 | // 2. update 20 | // 3. delete 21 | @HiveField(2) 22 | int action; 23 | 24 | // 1. 在看 25 | // 2. 想看 26 | // 3. 搁置 27 | // 4. 看过 28 | // 5. 抛弃 29 | @HiveField(3) 30 | int type; 31 | 32 | 33 | @HiveField(4) 34 | int timestamp; 35 | 36 | CollectedBangumiChange(this.id, this.bangumiID, this.action,this.type, this.timestamp); 37 | 38 | @override 39 | String toString() { 40 | return 'id: $id, bangumi: $bangumiID, action: $action, time: $timestamp'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/modules/collect/collect_change_module.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'collect_change_module.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class CollectedBangumiChangeAdapter 10 | extends TypeAdapter { 11 | @override 12 | final int typeId = 5; 13 | 14 | @override 15 | CollectedBangumiChange read(BinaryReader reader) { 16 | final numOfFields = reader.readByte(); 17 | final fields = { 18 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 19 | }; 20 | return CollectedBangumiChange( 21 | fields[0] as int, 22 | fields[1] as int, 23 | fields[2] as int, 24 | fields[3] as int, 25 | fields[4] as int, 26 | ); 27 | } 28 | 29 | @override 30 | void write(BinaryWriter writer, CollectedBangumiChange obj) { 31 | writer 32 | ..writeByte(5) 33 | ..writeByte(0) 34 | ..write(obj.id) 35 | ..writeByte(1) 36 | ..write(obj.bangumiID) 37 | ..writeByte(2) 38 | ..write(obj.action) 39 | ..writeByte(3) 40 | ..write(obj.type) 41 | ..writeByte(4) 42 | ..write(obj.timestamp); 43 | } 44 | 45 | @override 46 | int get hashCode => typeId.hashCode; 47 | 48 | @override 49 | bool operator ==(Object other) => 50 | identical(this, other) || 51 | other is CollectedBangumiChangeAdapter && 52 | runtimeType == other.runtimeType && 53 | typeId == other.typeId; 54 | } 55 | -------------------------------------------------------------------------------- /lib/modules/collect/collect_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import 'package:kazumi/modules/bangumi/bangumi_item.dart'; 3 | 4 | part 'collect_module.g.dart'; 5 | 6 | @HiveType(typeId: 3) 7 | class CollectedBangumi { 8 | @HiveField(0) 9 | BangumiItem bangumiItem; 10 | 11 | @HiveField(1) 12 | DateTime time; 13 | 14 | // 1. 在看 15 | // 2. 想看 16 | // 3. 搁置 17 | // 4. 看过 18 | // 5. 抛弃 19 | @HiveField(2) 20 | int type; 21 | 22 | String get key => bangumiItem.id.toString(); 23 | 24 | CollectedBangumi(this.bangumiItem, this.time, this.type); 25 | 26 | static String getKey(BangumiItem bangumiItem) => bangumiItem.id.toString(); 27 | 28 | @override 29 | String toString() { 30 | return 'type: $type, time: $time, anime: ${bangumiItem.name}'; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/modules/collect/collect_module.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'collect_module.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class CollectedBangumiAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 3; 12 | 13 | @override 14 | CollectedBangumi 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 CollectedBangumi( 20 | fields[0] as BangumiItem, 21 | fields[1] as DateTime, 22 | fields[2] as int, 23 | ); 24 | } 25 | 26 | @override 27 | void write(BinaryWriter writer, CollectedBangumi obj) { 28 | writer 29 | ..writeByte(3) 30 | ..writeByte(0) 31 | ..write(obj.bangumiItem) 32 | ..writeByte(1) 33 | ..write(obj.time) 34 | ..writeByte(2) 35 | ..write(obj.type); 36 | } 37 | 38 | @override 39 | int get hashCode => typeId.hashCode; 40 | 41 | @override 42 | bool operator ==(Object other) => 43 | identical(this, other) || 44 | other is CollectedBangumiAdapter && 45 | runtimeType == other.runtimeType && 46 | typeId == other.typeId; 47 | } 48 | -------------------------------------------------------------------------------- /lib/modules/comments/comment_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/modules/comments/comment_item.dart'; 2 | 3 | class CommentResponse { 4 | List commentList; 5 | int total; 6 | 7 | CommentResponse({ 8 | required this.commentList, 9 | required this.total, 10 | }); 11 | 12 | factory CommentResponse.fromJson(Map json) { 13 | List? list = (json['list'] as List?) ?? (json['data'] as List?); 14 | List? resCommentList = 15 | list?.map((i) => CommentItem.fromJson(i)).toList(); 16 | return CommentResponse( 17 | commentList: resCommentList ?? [], 18 | total: json['total'], 19 | ); 20 | } 21 | 22 | factory CommentResponse.fromTemplate() { 23 | return CommentResponse( 24 | commentList: [], 25 | total: 0, 26 | ); 27 | } 28 | 29 | Map toJson() { 30 | return { 31 | 'list': commentList, 32 | 'total': total, 33 | }; 34 | } 35 | } 36 | 37 | class EpisodeCommentResponse { 38 | List commentList; 39 | 40 | EpisodeCommentResponse({ 41 | required this.commentList, 42 | }); 43 | 44 | factory EpisodeCommentResponse.fromJson(List json) { 45 | List? resCommentList = 46 | (json as List?)?.map((i) => EpisodeCommentItem.fromJson(i)).toList(); 47 | return EpisodeCommentResponse( 48 | commentList: resCommentList ?? [], 49 | ); 50 | } 51 | 52 | factory EpisodeCommentResponse.fromTemplate() { 53 | return EpisodeCommentResponse( 54 | commentList: [], 55 | ); 56 | } 57 | 58 | Map toJson() { 59 | return { 60 | 'list': commentList, 61 | }; 62 | } 63 | } 64 | 65 | class CharacterCommentResponse { 66 | List commentList; 67 | 68 | CharacterCommentResponse({ 69 | required this.commentList, 70 | }); 71 | 72 | factory CharacterCommentResponse.fromJson(List json) { 73 | List? resCommentList = 74 | (json as List?)?.map((i) => CharacterCommentItem.fromJson(i)).toList(); 75 | return CharacterCommentResponse( 76 | commentList: resCommentList ?? [], 77 | ); 78 | } 79 | 80 | factory CharacterCommentResponse.fromTemplate() { 81 | return CharacterCommentResponse( 82 | commentList: [], 83 | ); 84 | } 85 | 86 | Map toJson() { 87 | return { 88 | 'list': commentList, 89 | }; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/modules/danmaku/danmaku_episode_response.dart: -------------------------------------------------------------------------------- 1 | class DanmakuEpisode { 2 | int episodeId; 3 | String episodeTitle; 4 | 5 | DanmakuEpisode({ 6 | required this.episodeId, 7 | required this.episodeTitle, 8 | }); 9 | 10 | factory DanmakuEpisode.fromJson(Map json) { 11 | return DanmakuEpisode( 12 | episodeId: json['episodeId'], 13 | episodeTitle: json['episodeTitle'], 14 | ); 15 | } 16 | 17 | Map toJson() { 18 | return { 19 | 'episodeId': episodeId, 20 | 'episodeTitle': episodeTitle, 21 | }; 22 | } 23 | } 24 | 25 | class DanmakuEpisodeResponse { 26 | List episodes; 27 | int errorCode; 28 | bool success; 29 | String errorMessage; 30 | 31 | DanmakuEpisodeResponse({ 32 | required this.episodes, 33 | required this.errorCode, 34 | required this.success, 35 | required this.errorMessage, 36 | }); 37 | 38 | factory DanmakuEpisodeResponse.fromJson(Map json) { 39 | var list = json['bangumi']['episodes'] as List; 40 | List episodeList = 41 | list.map((i) => DanmakuEpisode.fromJson(i)).toList(); 42 | 43 | return DanmakuEpisodeResponse( 44 | episodes: episodeList, 45 | errorCode: json['errorCode'], 46 | success: json['success'], 47 | errorMessage: json['errorMessage'], 48 | ); 49 | } 50 | 51 | factory DanmakuEpisodeResponse.fromTemplate() { 52 | return DanmakuEpisodeResponse( 53 | episodes: [], 54 | errorCode: 0, 55 | success: false, 56 | errorMessage: '', 57 | ); 58 | } 59 | 60 | Map toJson() { 61 | return { 62 | 'bangumi': episodes.map((episode) => episode.toJson()).toList(), 63 | 'errorCode': errorCode, 64 | 'success': success, 65 | 'errorMessage': errorMessage, 66 | }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/modules/danmaku/danmaku_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:kazumi/utils/utils.dart'; 3 | 4 | class Danmaku { 5 | // 弹幕内容 6 | String message; 7 | // 弹幕时间 8 | double time; 9 | // 弹幕类型 (1-普通弹幕,4-底部弹幕,5-顶部弹幕) 10 | int type; 11 | // 弹幕颜色 12 | Color color; 13 | // 弹幕来源 ([BiliBili], [Gamer]) 14 | String source; 15 | 16 | Danmaku({required this.message, required this.time, required this.type, required this.color, required this.source}); 17 | 18 | factory Danmaku.fromJson(Map json) { 19 | String messageValue = json['m']; 20 | List parts = json['p'].split(','); 21 | double timeValue = double.parse(parts[0]); 22 | int typeValue = int.parse(parts[1]); 23 | Color color = Utils.generateDanmakuColor(int.parse(parts[2])); 24 | String sourceValue = parts[3]; 25 | return Danmaku(time: timeValue, message: messageValue, type: typeValue, color: color, source: sourceValue); 26 | } 27 | } -------------------------------------------------------------------------------- /lib/modules/danmaku/danmaku_search_response.dart: -------------------------------------------------------------------------------- 1 | class DanmakuAnime { 2 | int animeId; 3 | String animeTitle; 4 | String type; 5 | String typeDescription; 6 | String imageUrl; 7 | DateTime startDate; 8 | int episodeCount; 9 | double rating; 10 | bool isFavorited; 11 | 12 | DanmakuAnime({ 13 | required this.animeId, 14 | required this.animeTitle, 15 | required this.type, 16 | required this.typeDescription, 17 | required this.imageUrl, 18 | required this.startDate, 19 | required this.episodeCount, 20 | required this.rating, 21 | required this.isFavorited, 22 | }); 23 | 24 | factory DanmakuAnime.fromJson(Map json) { 25 | return DanmakuAnime( 26 | animeId: json['animeId'], 27 | animeTitle: json['animeTitle'], 28 | type: json['type'], 29 | typeDescription: json['typeDescription'], 30 | imageUrl: json['imageUrl'], 31 | startDate: DateTime.parse(json['startDate']), 32 | episodeCount: json['episodeCount'], 33 | rating: json['rating'].toDouble(), 34 | isFavorited: json['isFavorited'], 35 | ); 36 | } 37 | 38 | Map toJson() { 39 | return { 40 | 'animeId': animeId, 41 | 'animeTitle': animeTitle, 42 | 'type': type, 43 | 'typeDescription': typeDescription, 44 | 'imageUrl': imageUrl, 45 | 'startDate': startDate.toIso8601String(), 46 | 'episodeCount': episodeCount, 47 | 'rating': rating, 48 | 'isFavorited': isFavorited, 49 | }; 50 | } 51 | } 52 | 53 | class DanmakuSearchResponse { 54 | List animes; 55 | int errorCode; 56 | bool success; 57 | String errorMessage; 58 | 59 | DanmakuSearchResponse({ 60 | required this.animes, 61 | required this.errorCode, 62 | required this.success, 63 | required this.errorMessage, 64 | }); 65 | 66 | factory DanmakuSearchResponse.fromJson(Map json) { 67 | var list = json['animes'] as List; 68 | List animeList = list.map((i) => DanmakuAnime.fromJson(i)).toList(); 69 | 70 | return DanmakuSearchResponse( 71 | animes: animeList, 72 | errorCode: json['errorCode'], 73 | success: json['success'], 74 | errorMessage: json['errorMessage'], 75 | ); 76 | } 77 | 78 | Map toJson() { 79 | return { 80 | 'animes': animes.map((anime) => anime.toJson()).toList(), 81 | 'errorCode': errorCode, 82 | 'success': success, 83 | 'errorMessage': errorMessage, 84 | }; 85 | } 86 | } -------------------------------------------------------------------------------- /lib/modules/history/history_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import 'package:kazumi/modules/bangumi/bangumi_item.dart'; 3 | 4 | part 'history_module.g.dart'; 5 | 6 | @HiveType(typeId: 1) 7 | class History { 8 | @HiveField(0) 9 | Map progresses = {}; 10 | 11 | @HiveField(1) 12 | int lastWatchEpisode; 13 | 14 | @HiveField(2) 15 | String adapterName; 16 | 17 | @HiveField(3) 18 | BangumiItem bangumiItem; 19 | 20 | @HiveField(4) 21 | DateTime lastWatchTime; 22 | 23 | @HiveField(5) 24 | String lastSrc; 25 | 26 | @HiveField(6, defaultValue: '') 27 | String lastWatchEpisodeName; 28 | 29 | String get key => adapterName + bangumiItem.id.toString(); 30 | 31 | History( 32 | this.bangumiItem, this.lastWatchEpisode, this.adapterName, this.lastWatchTime, this.lastSrc, this.lastWatchEpisodeName); 33 | 34 | static String getKey(String n, BangumiItem s) => n + s.id.toString(); 35 | 36 | @override 37 | String toString() { 38 | return 'Adapter: $adapterName, anime: ${bangumiItem.name}'; 39 | } 40 | } 41 | 42 | @HiveType(typeId: 2) 43 | class Progress { 44 | @HiveField(0) 45 | int episode; 46 | 47 | @HiveField(1) 48 | int road; 49 | 50 | @HiveField(2) 51 | int _progressInMilli; 52 | 53 | Duration get progress => Duration(milliseconds: _progressInMilli); 54 | 55 | set progress(Duration d) => _progressInMilli = d.inMilliseconds; 56 | 57 | Progress(this.episode, this.road, this._progressInMilli); 58 | 59 | @override 60 | String toString() { 61 | return 'Episode ${episode.toString()}, progress $progress'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/modules/history/history_module.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'history_module.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class HistoryAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 1; 12 | 13 | @override 14 | History 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 History( 20 | fields[3] as BangumiItem, 21 | fields[1] as int, 22 | fields[2] as String, 23 | fields[4] as DateTime, 24 | fields[5] as String, 25 | fields[6] == null ? '' : fields[6] as String, 26 | )..progresses = (fields[0] as Map).cast(); 27 | } 28 | 29 | @override 30 | void write(BinaryWriter writer, History obj) { 31 | writer 32 | ..writeByte(7) 33 | ..writeByte(0) 34 | ..write(obj.progresses) 35 | ..writeByte(1) 36 | ..write(obj.lastWatchEpisode) 37 | ..writeByte(2) 38 | ..write(obj.adapterName) 39 | ..writeByte(3) 40 | ..write(obj.bangumiItem) 41 | ..writeByte(4) 42 | ..write(obj.lastWatchTime) 43 | ..writeByte(5) 44 | ..write(obj.lastSrc) 45 | ..writeByte(6) 46 | ..write(obj.lastWatchEpisodeName); 47 | } 48 | 49 | @override 50 | int get hashCode => typeId.hashCode; 51 | 52 | @override 53 | bool operator ==(Object other) => 54 | identical(this, other) || 55 | other is HistoryAdapter && 56 | runtimeType == other.runtimeType && 57 | typeId == other.typeId; 58 | } 59 | 60 | class ProgressAdapter extends TypeAdapter { 61 | @override 62 | final int typeId = 2; 63 | 64 | @override 65 | Progress read(BinaryReader reader) { 66 | final numOfFields = reader.readByte(); 67 | final fields = { 68 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 69 | }; 70 | return Progress( 71 | fields[0] as int, 72 | fields[1] as int, 73 | fields[2] as int, 74 | ); 75 | } 76 | 77 | @override 78 | void write(BinaryWriter writer, Progress obj) { 79 | writer 80 | ..writeByte(3) 81 | ..writeByte(0) 82 | ..write(obj.episode) 83 | ..writeByte(1) 84 | ..write(obj.road) 85 | ..writeByte(2) 86 | ..write(obj._progressInMilli); 87 | } 88 | 89 | @override 90 | int get hashCode => typeId.hashCode; 91 | 92 | @override 93 | bool operator ==(Object other) => 94 | identical(this, other) || 95 | other is ProgressAdapter && 96 | runtimeType == other.runtimeType && 97 | typeId == other.typeId; 98 | } 99 | -------------------------------------------------------------------------------- /lib/modules/plugin/plugin_http_module.dart: -------------------------------------------------------------------------------- 1 | class PluginHTTPItem { 2 | String name; 3 | String version; 4 | bool useNativePlayer; 5 | String author; 6 | int lastUpdate; 7 | 8 | PluginHTTPItem({ 9 | required this.name, 10 | required this.version, 11 | required this.useNativePlayer, 12 | required this.author, 13 | required this.lastUpdate, 14 | }); 15 | 16 | factory PluginHTTPItem.fromJson(Map json) { 17 | return PluginHTTPItem( 18 | name: json['name'], 19 | version: json['version'], 20 | useNativePlayer: json['useNativePlayer'], 21 | author: json['author'], 22 | lastUpdate: json['lastUpdate'] ?? 0, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/modules/roads/road_module.dart: -------------------------------------------------------------------------------- 1 | class Road { 2 | String name; 3 | List data; 4 | List identifier; 5 | 6 | Road({ 7 | required this.name, 8 | required this.data, 9 | required this.identifier, 10 | }); 11 | } -------------------------------------------------------------------------------- /lib/modules/search/plugin_search_module.dart: -------------------------------------------------------------------------------- 1 | class SearchItem { 2 | String name; 3 | String src; 4 | 5 | SearchItem({ 6 | required this.name, 7 | required this.src, 8 | }); 9 | 10 | factory SearchItem.fromJson(Map json) { 11 | return SearchItem(name: json['name'], src: json['src']); 12 | } 13 | } 14 | 15 | class PluginSearchResponse { 16 | String pluginName; 17 | List data; 18 | 19 | PluginSearchResponse({ 20 | required this.pluginName, 21 | required this.data, 22 | }); 23 | 24 | factory PluginSearchResponse.fromJson(Map json) { 25 | return PluginSearchResponse( 26 | pluginName: json['pluginName'], 27 | data: (json['data'] as List) 28 | .map((itemJson) => SearchItem.fromJson(itemJson)) 29 | .toList(), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/modules/staff/staff_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/modules/staff/staff_item.dart'; 2 | 3 | class StaffResponse { 4 | final List data; 5 | final int total; 6 | 7 | StaffResponse({ 8 | required this.data, 9 | required this.total, 10 | }); 11 | 12 | factory StaffResponse.fromJson(Map json) { 13 | return StaffResponse( 14 | data: (json['data'] as List? ?? []) 15 | .map((item) => StaffFullItem.fromJson(item as Map)) 16 | .toList(), 17 | total: json['total'] is int ? json['total'] as int : 0, 18 | ); 19 | } 20 | 21 | factory StaffResponse.fromTemplate() { 22 | return StaffResponse( 23 | data: [], 24 | total: 0, 25 | ); 26 | } 27 | 28 | Map toJson() { 29 | return { 30 | 'data': data.map((item) => item.toJson()).toList(), 31 | 'total': total, 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/pages/about/about_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:kazumi/request/api.dart'; 3 | import 'package:kazumi/pages/about/about_page.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:kazumi/pages/logs/logs_page.dart'; 6 | 7 | class AboutModule extends Module { 8 | @override 9 | void binds(i) {} 10 | 11 | @override 12 | void routes(r) { 13 | r.child("/", child: (_) => const AboutPage()); 14 | r.child("/logs", child: (_) => const LogsPage()); 15 | r.child( 16 | "/license", 17 | child: (_) => const LicensePage( 18 | applicationName: 'Kazumi', 19 | applicationVersion: Api.version, 20 | applicationLegalese: '开源许可证', 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/pages/collect/collect_controller.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'collect_controller.dart'; 4 | 5 | // ************************************************************************** 6 | // StoreGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers 10 | 11 | mixin _$CollectController on _CollectController, Store { 12 | late final _$collectiblesAtom = 13 | Atom(name: '_CollectController.collectibles', context: context); 14 | 15 | @override 16 | ObservableList get collectibles { 17 | _$collectiblesAtom.reportRead(); 18 | return super.collectibles; 19 | } 20 | 21 | @override 22 | set collectibles(ObservableList value) { 23 | _$collectiblesAtom.reportWrite(value, super.collectibles, () { 24 | super.collectibles = value; 25 | }); 26 | } 27 | 28 | @override 29 | String toString() { 30 | return ''' 31 | collectibles: ${collectibles} 32 | '''; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/pages/collect/collect_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/collect/collect_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | 4 | class CollectModule extends Module { 5 | @override 6 | void routes(r) { 7 | r.child("/", child: (_) => const CollectPage()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/pages/error/storage_error_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:kazumi/bean/widget/error_widget.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | 6 | class StorageErrorPage extends StatelessWidget { 7 | const StorageErrorPage({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | appBar: AppBar( 13 | title: const Text('内部错误'), 14 | ), 15 | body: Center( 16 | child: FutureBuilder( 17 | future: getApplicationSupportDirectory(), 18 | builder: (context, snapshot) { 19 | if (snapshot.connectionState == ConnectionState.done) { 20 | final supportDir = snapshot.data; 21 | final path = supportDir != null ? '$supportDir' : '未知路径'; 22 | return GeneralErrorWidget( 23 | errMsg: '存储初始化错误 \n 当前储存位置 $path \n 尝试删除该目录以重置本地存储', 24 | actions: [ 25 | GeneralErrorButton( 26 | onPressed: () { 27 | exit(0); 28 | }, 29 | text: '退出程序', 30 | ), 31 | ], 32 | ); 33 | } else { 34 | return const CircularProgressIndicator(); 35 | } 36 | }, 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/pages/history/history_controller.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'history_controller.dart'; 4 | 5 | // ************************************************************************** 6 | // StoreGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers 10 | 11 | mixin _$HistoryController on _HistoryController, Store { 12 | late final _$historiesAtom = 13 | Atom(name: '_HistoryController.histories', context: context); 14 | 15 | @override 16 | ObservableList get histories { 17 | _$historiesAtom.reportRead(); 18 | return super.histories; 19 | } 20 | 21 | @override 22 | set histories(ObservableList value) { 23 | _$historiesAtom.reportWrite(value, super.histories, () { 24 | super.histories = value; 25 | }); 26 | } 27 | 28 | @override 29 | String toString() { 30 | return ''' 31 | histories: ${histories} 32 | '''; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/pages/history/history_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/history/history_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | 4 | class HistoryModule extends Module { 5 | @override 6 | void routes(r) { 7 | r.child("/", child: (_) => const HistoryPage()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/pages/index_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/index_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | import 'package:kazumi/pages/router.dart'; 4 | import 'package:kazumi/pages/init_page.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:kazumi/pages/popular/popular_controller.dart'; 7 | import 'package:kazumi/plugins/plugins_controller.dart'; 8 | import 'package:kazumi/pages/video/video_controller.dart'; 9 | import 'package:kazumi/pages/timeline/timeline_controller.dart'; 10 | import 'package:kazumi/pages/collect/collect_controller.dart'; 11 | import 'package:kazumi/pages/my/my_controller.dart'; 12 | import 'package:kazumi/pages/history/history_controller.dart'; 13 | import 'package:kazumi/pages/video/video_module.dart'; 14 | import 'package:kazumi/pages/info/info_module.dart'; 15 | import 'package:kazumi/pages/settings/settings_module.dart'; 16 | import 'package:kazumi/shaders/shaders_controller.dart'; 17 | import 'package:kazumi/pages/search/search_module.dart'; 18 | 19 | class IndexModule extends Module { 20 | @override 21 | List get imports => menu.moduleList; 22 | 23 | @override 24 | void binds(i) { 25 | i.addSingleton(PopularController.new); 26 | i.addSingleton(PluginsController.new); 27 | i.addSingleton(VideoPageController.new); 28 | i.addSingleton(TimelineController.new); 29 | i.addSingleton(CollectController.new); 30 | i.addSingleton(HistoryController.new); 31 | i.addSingleton(MyController.new); 32 | i.addSingleton(ShadersController.new); 33 | } 34 | 35 | @override 36 | void routes(r) { 37 | r.child("/", 38 | child: (_) => const InitPage(), 39 | children: [ 40 | ChildRoute( 41 | "/error", 42 | child: (_) => Scaffold( 43 | appBar: AppBar(title: const Text("Kazumi")), 44 | body: const Center(child: Text("初始化失败")), 45 | ), 46 | ), 47 | ], 48 | transition: TransitionType.noTransition); 49 | r.child( 50 | "/tab", 51 | child: (_) { 52 | return const IndexPage(); 53 | }, 54 | children: menu.routes, 55 | transition: TransitionType.fadeIn, 56 | duration: Duration(milliseconds: 70), 57 | ); 58 | r.module("/video", module: VideoModule()); 59 | /// The route need [ BangumiItem ] as argument. 60 | r.module("/info", module: InfoModule()); 61 | r.module("/settings", module: SettingsModule()); 62 | r.module("/search", module: SearchModule()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/pages/index_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:kazumi/pages/menu/menu.dart'; 3 | 4 | 5 | class IndexPage extends StatefulWidget { 6 | //const IndexPage({super.key}); 7 | const IndexPage({super.key}); 8 | 9 | @override 10 | State createState() => _IndexPageState(); 11 | } 12 | 13 | class _IndexPageState extends State with WidgetsBindingObserver { 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return const ScaffoldMenu(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/pages/info/info_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/info/info_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | 4 | class InfoModule extends Module { 5 | @override 6 | void routes(r) { 7 | r.child("/", child: (_) => const InfoPage()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/pages/logs/logs_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:kazumi/bean/dialog/dialog_helper.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:kazumi/bean/appbar/sys_app_bar.dart'; 7 | 8 | class LogsPage extends StatefulWidget { 9 | const LogsPage({super.key}); 10 | 11 | @override 12 | State createState() => _LogsPageState(); 13 | } 14 | 15 | class _LogsPageState extends State { 16 | String fileContent = ''; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | _loadLogs(); 22 | } 23 | 24 | Future _loadLogs() async { 25 | final file = await _getLogsFile(); 26 | if (await file.exists()) { 27 | final content = await file.readAsString(); 28 | setState(() { 29 | fileContent = content; 30 | }); 31 | } 32 | } 33 | 34 | Future _getLogsFile() async { 35 | final directory = await getApplicationSupportDirectory(); 36 | final path = directory.path; 37 | return File('$path/logs/kazumi_logs.log'); 38 | } 39 | 40 | Future _clearLogs() async { 41 | final file = await _getLogsFile(); 42 | await file.writeAsString(''); 43 | setState(() { 44 | fileContent = ''; 45 | }); 46 | } 47 | 48 | Future _copyLogs() async { 49 | await Clipboard.setData(ClipboardData(text: fileContent)); 50 | KazumiDialog.showToast(message: '已复制到剪贴板'); 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | return SelectionArea( 56 | child: Scaffold( 57 | appBar: const SysAppBar( 58 | title: Text('日志'), 59 | ), 60 | body: fileContent.isEmpty 61 | ? const Center(child: Text('没有数据')) 62 | : SingleChildScrollView( 63 | padding: const EdgeInsets.all(16.0), 64 | child: Text(fileContent), 65 | ), 66 | floatingActionButton: Row( 67 | mainAxisAlignment: MainAxisAlignment.end, 68 | children: [ 69 | FloatingActionButton( 70 | heroTag: null, 71 | onPressed: _clearLogs, 72 | child: const Icon(Icons.clear_all), 73 | ), 74 | const SizedBox(width: 15), 75 | FloatingActionButton( 76 | heroTag: null, 77 | onPressed: _copyLogs, 78 | child: const Icon(Icons.copy), 79 | ), 80 | ], 81 | ), 82 | )); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/pages/my/my_controller.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'my_controller.dart'; 4 | 5 | // ************************************************************************** 6 | // StoreGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers 10 | 11 | mixin _$MyController on _MyController, Store { 12 | late final _$shieldListAtom = 13 | Atom(name: '_MyController.shieldList', context: context); 14 | 15 | @override 16 | ObservableList get shieldList { 17 | _$shieldListAtom.reportRead(); 18 | return super.shieldList; 19 | } 20 | 21 | @override 22 | set shieldList(ObservableList value) { 23 | _$shieldListAtom.reportWrite(value, super.shieldList, () { 24 | super.shieldList = value; 25 | }); 26 | } 27 | 28 | @override 29 | String toString() { 30 | return ''' 31 | shieldList: ${shieldList} 32 | '''; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/pages/my/my_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/my/my_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | 4 | class MyModule extends Module { 5 | @override 6 | void routes(r) { 7 | r.child("/", child: (_) => const MyPage()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/pages/player/player_item_surface.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mobx/flutter_mobx.dart'; 3 | import 'package:media_kit_video/media_kit_video.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:kazumi/pages/player/player_controller.dart'; 6 | 7 | class PlayerItemSurface extends StatefulWidget { 8 | const PlayerItemSurface({super.key}); 9 | 10 | @override 11 | State createState() => _PlayerItemSurfaceState(); 12 | } 13 | 14 | class _PlayerItemSurfaceState extends State { 15 | final PlayerController playerController = Modular.get(); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Observer(builder: (context) { 20 | return Video( 21 | controller: playerController.videoController, 22 | controls: NoVideoControls, 23 | fit: playerController.aspectRatioType == 1 24 | ? BoxFit.contain 25 | : playerController.aspectRatioType == 2 26 | ? BoxFit.cover 27 | : BoxFit.fill, 28 | subtitleViewConfiguration: SubtitleViewConfiguration( 29 | style: TextStyle( 30 | color: Colors.pink, 31 | fontSize: 48.0, 32 | background: Paint()..color = Colors.transparent, 33 | decoration: TextDecoration.none, 34 | fontWeight: FontWeight.bold, 35 | shadows: const [ 36 | Shadow( 37 | offset: Offset(1.0, 1.0), 38 | blurRadius: 3.0, 39 | color: Color.fromARGB(255, 255, 255, 255), 40 | ), 41 | Shadow( 42 | offset: Offset(-1.0, -1.0), 43 | blurRadius: 3.0, 44 | color: Color.fromARGB(125, 255, 255, 255), 45 | ), 46 | ], 47 | ), 48 | textAlign: TextAlign.center, 49 | padding: const EdgeInsets.all(24.0), 50 | ), 51 | ); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/pages/plugin_editor/plugin_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/plugin_editor/plugin_view_page.dart'; 2 | import 'package:kazumi/pages/plugin_editor/plugin_editor_page.dart'; 3 | import 'package:kazumi/pages/plugin_editor/plugin_shop_page.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | 6 | class PluginModule extends Module { 7 | @override 8 | void binds(i) {} 9 | 10 | @override 11 | void routes(r) { 12 | r.child("/", child: (_) => const PluginViewPage()); 13 | r.child("/shop", child: (_) => const PluginShopPage()); 14 | r.child("/editor", 15 | child: (_) => const PluginEditorPage(), 16 | transition: TransitionType.defaultTransition); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/pages/popular/popular_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:kazumi/request/bangumi.dart'; 4 | import 'package:kazumi/modules/bangumi/bangumi_item.dart'; 5 | import 'package:mobx/mobx.dart'; 6 | 7 | part 'popular_controller.g.dart'; 8 | 9 | class PopularController = _PopularController with _$PopularController; 10 | 11 | abstract class _PopularController with Store { 12 | final ScrollController scrollController = ScrollController(); 13 | 14 | @observable 15 | String currentTag = ''; 16 | 17 | @observable 18 | ObservableList bangumiList = ObservableList.of([]); 19 | 20 | @observable 21 | ObservableList trendList = ObservableList.of([]); 22 | 23 | double scrollOffset = 0.0; 24 | 25 | @observable 26 | bool isLoadingMore = false; 27 | 28 | @observable 29 | bool isTimeOut = false; 30 | 31 | void setCurrentTag(String s) { 32 | currentTag = s; 33 | } 34 | 35 | void clearBangumiList() { 36 | bangumiList.clear(); 37 | } 38 | 39 | Future queryBangumiByTrend({String type = 'add'}) async { 40 | if (type == 'init') { 41 | trendList.clear(); 42 | } 43 | isLoadingMore = true; 44 | var result = 45 | await BangumiHTTP.getBangumiTrendsList(offset: trendList.length); 46 | trendList.addAll(result); 47 | isLoadingMore = false; 48 | isTimeOut = trendList.isEmpty; 49 | } 50 | 51 | Future queryBangumiByTag({String type = 'add'}) async { 52 | if (type == 'init') { 53 | bangumiList.clear(); 54 | } 55 | isLoadingMore = true; 56 | int randomNumber = Random().nextInt(8000) + 1; 57 | var tag = currentTag; 58 | var result = await BangumiHTTP.getBangumiList(rank: randomNumber, tag: tag); 59 | bangumiList.addAll(result); 60 | isLoadingMore = false; 61 | isTimeOut = bangumiList.isEmpty; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/pages/popular/popular_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/popular/popular_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | 4 | class PopularModule extends Module { 5 | @override 6 | void routes(r) { 7 | r.child("/", child: (_) => const PopularPage()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/pages/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_modular/flutter_modular.dart'; 2 | import 'package:kazumi/pages/popular/popular_module.dart'; 3 | import 'package:kazumi/pages/my/my_module.dart'; 4 | import 'package:kazumi/pages/timeline/timeline_module.dart'; 5 | import 'package:kazumi/pages/collect/collect_module.dart'; 6 | 7 | class MenuRouteItem { 8 | final String path; 9 | final Module module; 10 | 11 | const MenuRouteItem({ 12 | required this.path, 13 | required this.module, 14 | }); 15 | } 16 | 17 | class MenuRoute { 18 | final List menuList; 19 | 20 | const MenuRoute(this.menuList); 21 | 22 | int get size => menuList.length; 23 | 24 | List get moduleList { 25 | return menuList.map((e) => e.module).toList(); 26 | } 27 | 28 | List get routes { 29 | return menuList.map((e) => ModuleRoute(e.path, module: e.module)).toList(); 30 | } 31 | 32 | getPath(int index) { 33 | return menuList[index].path; 34 | } 35 | } 36 | 37 | final MenuRoute menu = MenuRoute([ 38 | MenuRouteItem( 39 | path: "/popular", 40 | module: PopularModule(), 41 | ), 42 | MenuRouteItem( 43 | path: "/timeline", 44 | module: TimelineModule(), 45 | ), 46 | MenuRouteItem( 47 | path: "/collect", 48 | module: CollectModule(), 49 | ), 50 | MenuRouteItem( 51 | path: "/my", 52 | module: MyModule(), 53 | ), 54 | ]); 55 | -------------------------------------------------------------------------------- /lib/pages/search/search_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:mobx/mobx.dart'; 2 | import 'package:kazumi/modules/bangumi/bangumi_item.dart'; 3 | import 'package:kazumi/request/bangumi.dart'; 4 | import 'package:kazumi/utils/search_parser.dart'; 5 | 6 | part 'search_controller.g.dart'; 7 | 8 | class SearchPageController = _SearchPageController with _$SearchPageController; 9 | 10 | abstract class _SearchPageController with Store { 11 | @observable 12 | bool isLoading = false; 13 | 14 | @observable 15 | bool isTimeOut = false; 16 | 17 | @observable 18 | ObservableList bangumiList = ObservableList.of([]); 19 | 20 | /// Avaliable sort parameters: 21 | /// 1. heat 22 | /// 2. match 23 | /// 3. rank 24 | /// 4. score 25 | String attachSortParams(String input, String sort) { 26 | SearchParser parser = SearchParser(input); 27 | String newInput = parser.updateSort(sort); 28 | return newInput; 29 | } 30 | 31 | Future searchBangumi(String input, {String type = 'add'}) async { 32 | if (type != 'add') { 33 | bangumiList.clear(); 34 | } 35 | isLoading = true; 36 | isTimeOut = false; 37 | SearchParser parser = SearchParser(input); 38 | String? idString = parser.parseId(); 39 | String? tag = parser.parseTag(); 40 | String? sort = parser.parseSort(); 41 | String keywords = parser.parseKeywords(); 42 | if (idString != null) { 43 | final id = int.tryParse(idString); 44 | if (id != null) { 45 | final BangumiItem? item = await BangumiHTTP.getBangumiInfoByID(id); 46 | if (item != null) { 47 | bangumiList.add(item); 48 | } 49 | return; 50 | } 51 | } 52 | var result = 53 | await BangumiHTTP.bangumiSearch(keywords, tags: [if (tag != null) tag], offset: bangumiList.length, sort: sort ?? 'heat'); 54 | bangumiList.addAll(result); 55 | isLoading = false; 56 | isTimeOut = bangumiList.isEmpty; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/pages/search/search_controller.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'search_controller.dart'; 4 | 5 | // ************************************************************************** 6 | // StoreGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers 10 | 11 | mixin _$SearchPageController on _SearchPageController, Store { 12 | late final _$isLoadingAtom = 13 | Atom(name: '_SearchPageController.isLoading', context: context); 14 | 15 | @override 16 | bool get isLoading { 17 | _$isLoadingAtom.reportRead(); 18 | return super.isLoading; 19 | } 20 | 21 | @override 22 | set isLoading(bool value) { 23 | _$isLoadingAtom.reportWrite(value, super.isLoading, () { 24 | super.isLoading = value; 25 | }); 26 | } 27 | 28 | late final _$isTimeOutAtom = 29 | Atom(name: '_SearchPageController.isTimeOut', context: context); 30 | 31 | @override 32 | bool get isTimeOut { 33 | _$isTimeOutAtom.reportRead(); 34 | return super.isTimeOut; 35 | } 36 | 37 | @override 38 | set isTimeOut(bool value) { 39 | _$isTimeOutAtom.reportWrite(value, super.isTimeOut, () { 40 | super.isTimeOut = value; 41 | }); 42 | } 43 | 44 | late final _$bangumiListAtom = 45 | Atom(name: '_SearchPageController.bangumiList', context: context); 46 | 47 | @override 48 | ObservableList get bangumiList { 49 | _$bangumiListAtom.reportRead(); 50 | return super.bangumiList; 51 | } 52 | 53 | @override 54 | set bangumiList(ObservableList value) { 55 | _$bangumiListAtom.reportWrite(value, super.bangumiList, () { 56 | super.bangumiList = value; 57 | }); 58 | } 59 | 60 | @override 61 | String toString() { 62 | return ''' 63 | isLoading: ${isLoading}, 64 | isTimeOut: ${isTimeOut}, 65 | bangumiList: ${bangumiList} 66 | '''; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/pages/search/search_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_modular/flutter_modular.dart'; 2 | import 'package:kazumi/pages/search/search_page.dart'; 3 | 4 | class SearchModule extends Module { 5 | @override 6 | void binds(i) {} 7 | 8 | @override 9 | void routes(r) { 10 | r.child("/:tag", child: (_) { 11 | return SearchPage(inputTag: r.args.params['tag']); 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/pages/settings/danmaku/danmaku_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/settings/danmaku/danmaku_settings.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | import 'package:kazumi/pages/settings/danmaku/danmaku_shield_settings.dart'; 4 | 5 | class DanmakuModule extends Module { 6 | @override 7 | void binds(i) {} 8 | 9 | @override 10 | void routes(r) { 11 | r.child("/", child: (_) => const DanmakuSettingsPage()); 12 | r.child("/shield", child: (_) => const DanmakuShieldSettings()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/pages/settings/decoder_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hive/hive.dart'; 3 | import 'package:kazumi/bean/appbar/sys_app_bar.dart'; 4 | import 'package:kazumi/utils/storage.dart'; 5 | import 'package:kazumi/utils/constants.dart'; 6 | import 'package:card_settings_ui/card_settings_ui.dart'; 7 | 8 | class DecoderSettings extends StatefulWidget { 9 | const DecoderSettings({super.key}); 10 | 11 | @override 12 | State createState() => _DecoderSettingsState(); 13 | } 14 | 15 | class _DecoderSettingsState extends State { 16 | late final Box setting = GStorage.setting; 17 | late final ValueNotifier decoder = ValueNotifier( 18 | setting.get(SettingBoxKey.hardwareDecoder, defaultValue: 'auto-safe'), 19 | ); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: const SysAppBar( 25 | title: Text('硬件解码器'), 26 | ), 27 | body: Center( 28 | child: SizedBox( 29 | width: (MediaQuery.of(context).size.width > 1000) ? 1000 : null, 30 | child: SettingsList( 31 | sections: [ 32 | SettingsSection( 33 | title: const Text('选择不受支持的解码器将回退到软件解码'), 34 | tiles: hardwareDecodersList.entries 35 | .map((e) => SettingsTile.radioTile( 36 | title: Text(e.key), 37 | description: Text(e.value), 38 | radioValue: e.key, 39 | groupValue: decoder.value, 40 | onChanged: (String? value) { 41 | if (value != null) { 42 | setting.put(SettingBoxKey.hardwareDecoder, value); 43 | setState(() { 44 | decoder.value = value; 45 | }); 46 | } 47 | }, 48 | )) 49 | .toList(), 50 | ), 51 | ], 52 | ), 53 | ), 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/pages/settings/settings_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/settings/danmaku/danmaku_module.dart'; 2 | import 'package:kazumi/pages/about/about_module.dart'; 3 | import 'package:kazumi/pages/plugin_editor/plugin_module.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:kazumi/pages/history/history_module.dart'; 6 | import 'package:kazumi/pages/settings/theme_settings_page.dart'; 7 | import 'package:kazumi/pages/settings/player_settings.dart'; 8 | import 'package:kazumi/pages/settings/displaymode_settings.dart'; 9 | import 'package:kazumi/pages/settings/decoder_settings.dart'; 10 | import 'package:kazumi/pages/settings/super_resolution_settings.dart'; 11 | import 'package:kazumi/pages/webdav_editor/webdav_module.dart'; 12 | 13 | class SettingsModule extends Module { 14 | @override 15 | void routes(r) { 16 | r.child("/theme", child: (_) => const ThemeSettingsPage()); 17 | r.child( 18 | "/theme/display", 19 | child: (_) => const SetDisplayMode(), 20 | ); 21 | r.child("/player", child: (_) => const PlayerSettingsPage()); 22 | r.child("/player/decoder", child: (_) => const DecoderSettings()); 23 | // r.child("/other", child: (_) => const OtherSettingsPage()); 24 | r.child("/player/super", child: (_) => const SuperResolutionSettings()); 25 | r.module("/webdav", module: WebDavModule()); 26 | r.module("/about", module: AboutModule()); 27 | r.module("/plugin", module: PluginModule()); 28 | r.module("/history", module: HistoryModule()); 29 | r.module("/danmaku", module: DanmakuModule()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/pages/timeline/timeline_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/modules/bangumi/bangumi_item.dart'; 2 | import 'package:kazumi/request/bangumi.dart'; 3 | import 'package:kazumi/utils/anime_season.dart'; 4 | import 'package:mobx/mobx.dart'; 5 | 6 | part 'timeline_controller.g.dart'; 7 | 8 | class TimelineController = _TimelineController with _$TimelineController; 9 | 10 | abstract class _TimelineController with Store { 11 | @observable 12 | ObservableList> bangumiCalendar = ObservableList>(); 13 | 14 | @observable 15 | String seasonString = ''; 16 | 17 | late DateTime selectedDate; 18 | 19 | void init() { 20 | selectedDate = DateTime.now(); 21 | seasonString = AnimeSeason(selectedDate).toString(); 22 | getSchedules(); 23 | } 24 | 25 | Future getSchedules() async { 26 | final resBangumiCalendar = await BangumiHTTP.getCalendar(); 27 | bangumiCalendar.clear(); 28 | bangumiCalendar.addAll(resBangumiCalendar); 29 | } 30 | 31 | Future getSchedulesBySeason() async { 32 | // 4次获取,每次最多20部 33 | var time = 0; 34 | const maxTime = 4; 35 | const limit = 20; 36 | var resBangumiCalendar = List.generate(7, (_) => []); 37 | for (time = 0; time < maxTime; time++) { 38 | final offset = time * limit; 39 | var newList = await BangumiHTTP.getCalendarBySearch( 40 | AnimeSeason(selectedDate).toSeasonStartAndEnd(), limit, offset); 41 | for (int i = 0; i < resBangumiCalendar.length; ++i) { 42 | resBangumiCalendar[i].addAll(newList[i]); 43 | } 44 | bangumiCalendar.clear(); 45 | bangumiCalendar.addAll(resBangumiCalendar); 46 | } 47 | } 48 | 49 | void tryEnterSeason(DateTime date) { 50 | selectedDate = date; 51 | seasonString = "加载中 ٩(◦`꒳´◦)۶"; 52 | } 53 | } -------------------------------------------------------------------------------- /lib/pages/timeline/timeline_controller.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'timeline_controller.dart'; 4 | 5 | // ************************************************************************** 6 | // StoreGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers 10 | 11 | mixin _$TimelineController on _TimelineController, Store { 12 | late final _$bangumiCalendarAtom = 13 | Atom(name: '_TimelineController.bangumiCalendar', context: context); 14 | 15 | @override 16 | ObservableList> get bangumiCalendar { 17 | _$bangumiCalendarAtom.reportRead(); 18 | return super.bangumiCalendar; 19 | } 20 | 21 | @override 22 | set bangumiCalendar(ObservableList> value) { 23 | _$bangumiCalendarAtom.reportWrite(value, super.bangumiCalendar, () { 24 | super.bangumiCalendar = value; 25 | }); 26 | } 27 | 28 | late final _$seasonStringAtom = 29 | Atom(name: '_TimelineController.seasonString', context: context); 30 | 31 | @override 32 | String get seasonString { 33 | _$seasonStringAtom.reportRead(); 34 | return super.seasonString; 35 | } 36 | 37 | @override 38 | set seasonString(String value) { 39 | _$seasonStringAtom.reportWrite(value, super.seasonString, () { 40 | super.seasonString = value; 41 | }); 42 | } 43 | 44 | @override 45 | String toString() { 46 | return ''' 47 | bangumiCalendar: ${bangumiCalendar}, 48 | seasonString: ${seasonString} 49 | '''; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/pages/timeline/timeline_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/timeline/timeline_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | 4 | class TimelineModule extends Module { 5 | @override 6 | void routes(r) { 7 | r.child("/", child: (_) => const TimelinePage()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/pages/video/video_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/video/video_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | import 'package:kazumi/pages/webview/webview_controller.dart'; 4 | import 'package:kazumi/pages/player/player_controller.dart'; 5 | 6 | class VideoModule extends Module { 7 | @override 8 | void routes(r) { 9 | r.child("/", child: (_) => const VideoPage()); 10 | } 11 | 12 | @override 13 | void binds(i) { 14 | i.addSingleton(PlayerController.new); 15 | i.addSingleton(WebviewItemControllerFactory.getController); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/pages/webdav_editor/webdav_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:kazumi/pages/webdav_editor/webdav_editor_page.dart'; 2 | import 'package:kazumi/pages/webdav_editor/webdav_setting.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | 5 | class WebDavModule extends Module { 6 | @override 7 | void binds(i) {} 8 | 9 | @override 10 | void routes(r) { 11 | r.child("/", child: (_) => const WebDavSettingsPage()); 12 | r.child("/editor", 13 | child: (_) => const WebDavEditorPage(),); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/pages/webview/webview_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:async'; 3 | 4 | import 'package:kazumi/pages/webview/webview_controller_impel/webview_controller_impel.dart'; 5 | import 'package:kazumi/pages/webview/webview_controller_impel/webview_windows_controller_impel.dart'; 6 | import 'package:kazumi/pages/webview/webview_controller_impel/webview_linux_controller_impel.dart'; 7 | import 'package:kazumi/pages/webview/webview_controller_impel/webview_apple_controller_impel.dart'; 8 | 9 | abstract class WebviewItemController { 10 | // Webview controller 11 | T? webviewController; 12 | 13 | // Retry count 14 | int count = 0; 15 | // Last watched position 16 | int offset = 0; 17 | bool isIframeLoaded = false; 18 | bool isVideoSourceLoaded = false; 19 | 20 | /// Webview initialization method 21 | Future init(); 22 | 23 | final StreamController initEventController = 24 | StreamController.broadcast(); 25 | 26 | // Stream to notify when the webview is initialized 27 | Stream get onInitialized => initEventController.stream; 28 | 29 | final StreamController logEventController = 30 | StreamController.broadcast(); 31 | 32 | // Stream to subscribe to webview logs 33 | Stream get onLog => logEventController.stream; 34 | 35 | final StreamController videoLoadingEventController = 36 | StreamController.broadcast(); 37 | 38 | // Stream to notify when the video source is loaded 39 | Stream get onVideoLoading => videoLoadingEventController.stream; 40 | 41 | // Stream to notify video source URL when the video source is loaded 42 | // The first parameter is the video source URL and the second parameter is the video offset (start position) 43 | final StreamController<(String, int)> videoParserEventController = 44 | StreamController<(String, int)>.broadcast(); 45 | 46 | Stream<(String, int)> get onVideoURLParser => videoParserEventController.stream; 47 | 48 | /// Webview load URL method 49 | Future loadUrl(String url, bool useNativePlayer, bool useLegacyParser, 50 | {int offset = 0}); 51 | 52 | /// Webview unload page method 53 | Future unloadPage(); 54 | 55 | /// Webview dispose method 56 | void dispose(); 57 | } 58 | 59 | class WebviewItemControllerFactory { 60 | static WebviewItemController getController() { 61 | if (Platform.isWindows) { 62 | return WebviewWindowsItemControllerImpel(); 63 | } 64 | if (Platform.isLinux) { 65 | return WebviewLinuxItemControllerImpel(); 66 | } 67 | if (Platform.isMacOS || Platform.isIOS) { 68 | return WebviewAppleItemControllerImpel(); 69 | } 70 | return WebviewItemControllerImpel(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/pages/webview/webview_item.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:kazumi/pages/webview/webview_item_impel/webview_item_impel.dart'; 4 | import 'package:kazumi/pages/webview/webview_item_impel/webview_windows_item_impel.dart'; 5 | import 'package:kazumi/pages/webview/webview_item_impel/webview_linux_item_impel.dart'; 6 | 7 | class WebviewItem extends StatefulWidget { 8 | const WebviewItem({ 9 | super.key 10 | }); 11 | 12 | @override 13 | State createState() => _WebviewItemState(); 14 | } 15 | 16 | class _WebviewItemState extends State { 17 | @override 18 | Widget build(BuildContext context) { 19 | return webviewUniversal; 20 | } 21 | } 22 | 23 | Widget get webviewUniversal { 24 | if (Platform.isWindows) { 25 | return const WebviewWindowsItemImpel(); 26 | } 27 | if (Platform.isLinux) { 28 | return const WebviewLinuxItemImpel(); 29 | } 30 | return const WebviewItemImpel(); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /lib/pages/webview/webview_item_impel/webview_item_impel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:webview_flutter/webview_flutter.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:kazumi/pages/webview/webview_controller.dart'; 5 | 6 | class WebviewItemImpel extends StatefulWidget { 7 | const WebviewItemImpel({super.key}); 8 | 9 | @override 10 | State createState() => _WebviewItemImpelState(); 11 | } 12 | 13 | class _WebviewItemImpelState extends State { 14 | final webviewItemController = Modular.get(); 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | initPlatformState(); 20 | } 21 | 22 | @override 23 | void dispose() { 24 | webviewItemController.dispose(); 25 | super.dispose(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return compositeView; 31 | } 32 | 33 | Future initPlatformState() async { 34 | // 初始化Webview 35 | if (webviewItemController.webviewController == null) { 36 | await webviewItemController.init(); 37 | } 38 | if (!mounted) return; 39 | setState(() {}); 40 | } 41 | 42 | Widget get compositeView { 43 | if (webviewItemController.webviewController == null) { 44 | return const Text( 45 | 'Not Initialized', 46 | style: TextStyle( 47 | fontSize: 24.0, 48 | fontWeight: FontWeight.w900, 49 | ), 50 | ); 51 | } else { 52 | return WebViewWidget(controller: webviewItemController.webviewController); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/pages/webview/webview_item_impel/webview_linux_item_impel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | import 'package:kazumi/pages/webview/webview_controller.dart'; 4 | 5 | class WebviewLinuxItemImpel extends StatefulWidget { 6 | const WebviewLinuxItemImpel({super.key}); 7 | 8 | @override 9 | State createState() => _WebviewLinuxItemImpelState(); 10 | } 11 | 12 | class _WebviewLinuxItemImpelState extends State { 13 | final webviewLinuxItemController = Modular.get(); 14 | 15 | @override 16 | void initState() { 17 | super.initState(); 18 | webviewLinuxItemController.init(); 19 | } 20 | 21 | @override 22 | void dispose() { 23 | webviewLinuxItemController.dispose(); 24 | super.dispose(); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Container( 30 | height: MediaQuery.of(context).size.width * 9.0 / (16.0), 31 | width: MediaQuery.of(context).size.width, 32 | color: Colors.black, 33 | child: const Center(child: Text('此平台不支持Webview规则'))); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/pages/webview/webview_item_impel/webview_windows_item_impel.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:webview_windows/webview_windows.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:window_manager/window_manager.dart'; 6 | import 'package:kazumi/pages/video/video_controller.dart'; 7 | import 'package:kazumi/pages/webview/webview_controller.dart'; 8 | 9 | class WebviewWindowsItemImpel extends StatefulWidget { 10 | const WebviewWindowsItemImpel({super.key}); 11 | 12 | @override 13 | State createState() => 14 | _WebviewWindowsItemImpelState(); 15 | } 16 | 17 | class _WebviewWindowsItemImpelState extends State { 18 | final List _subscriptions = []; 19 | final webviewDesktopItemController = Modular.get(); 20 | final VideoPageController videoPageController = 21 | Modular.get(); 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | initPlatformState(); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | for (var s in _subscriptions) { 32 | try { 33 | s.cancel(); 34 | } catch (_) {} 35 | } 36 | webviewDesktopItemController.dispose(); 37 | super.dispose(); 38 | } 39 | 40 | Future initPlatformState() async { 41 | // 初始化Webview 42 | if (webviewDesktopItemController.webviewController == null) { 43 | await webviewDesktopItemController.init(); 44 | } 45 | // 接受全屏事件 46 | _subscriptions.add(webviewDesktopItemController 47 | .webviewController.containsFullScreenElementChanged 48 | .listen((flag) { 49 | videoPageController.isFullscreen = flag; 50 | windowManager.setFullScreen(flag); 51 | })); 52 | if (!mounted) return; 53 | 54 | setState(() {}); 55 | } 56 | 57 | Widget get compositeView { 58 | if (webviewDesktopItemController.webviewController == null) { 59 | return const Text( 60 | 'Not Initialized', 61 | style: TextStyle( 62 | fontSize: 24.0, 63 | fontWeight: FontWeight.w900, 64 | ), 65 | ); 66 | } else { 67 | return Webview(webviewDesktopItemController.webviewController); 68 | } 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | return compositeView; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/plugins/plugin_install_time_tracker.dart: -------------------------------------------------------------------------------- 1 | // 记录规则安装时间 2 | // 使用文件修改时间作为规则的安装时间 3 | class PluginInstallTimeTracker { 4 | // 记录规则安装时间的映射 5 | final Map _installTimes = {}; 6 | 7 | // 设置规则的安装时间 8 | void setInstallTime(String pluginName, int timestamp) { 9 | _installTimes[pluginName] = timestamp; 10 | } 11 | 12 | // 获取规则的安装时间,如果不存在返回0 13 | int getInstallTime(String pluginName) { 14 | return _installTimes[pluginName] ?? 0; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/plugins/plugin_validity_tracker.dart: -------------------------------------------------------------------------------- 1 | // 记录规则有效性状态 2 | // 目前仅追踪搜索有效性:在本次启动后,规则是否成功返回过搜索结果 3 | class PluginValidityTracker { 4 | // 记录搜索有效的规则集合 5 | final Set _searchValidPlugins = {}; 6 | 7 | // 标记规则搜索有效(成功返回过搜索结果) 8 | void markSearchValid(String pluginName) { 9 | _searchValidPlugins.add(pluginName); 10 | } 11 | 12 | // 检查规则搜索是否有效(是否成功返回过搜索结果) 13 | bool isSearchValid(String pluginName) { 14 | return _searchValidPlugins.contains(pluginName); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/plugins/plugins_controller.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'plugins_controller.dart'; 4 | 5 | // ************************************************************************** 6 | // StoreGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers 10 | 11 | mixin _$PluginsController on _PluginsController, Store { 12 | late final _$pluginListAtom = 13 | Atom(name: '_PluginsController.pluginList', context: context); 14 | 15 | @override 16 | ObservableList get pluginList { 17 | _$pluginListAtom.reportRead(); 18 | return super.pluginList; 19 | } 20 | 21 | @override 22 | set pluginList(ObservableList value) { 23 | _$pluginListAtom.reportWrite(value, super.pluginList, () { 24 | super.pluginList = value; 25 | }); 26 | } 27 | 28 | late final _$pluginHTTPListAtom = 29 | Atom(name: '_PluginsController.pluginHTTPList', context: context); 30 | 31 | @override 32 | ObservableList get pluginHTTPList { 33 | _$pluginHTTPListAtom.reportRead(); 34 | return super.pluginHTTPList; 35 | } 36 | 37 | @override 38 | set pluginHTTPList(ObservableList value) { 39 | _$pluginHTTPListAtom.reportWrite(value, super.pluginHTTPList, () { 40 | super.pluginHTTPList = value; 41 | }); 42 | } 43 | 44 | @override 45 | String toString() { 46 | return ''' 47 | pluginList: ${pluginList}, 48 | pluginHTTPList: ${pluginHTTPList} 49 | '''; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/request/api.dart: -------------------------------------------------------------------------------- 1 | class Api { 2 | // 当前版本 3 | static const String version = '1.7.2'; 4 | // 规则API级别 5 | static const int apiLevel = 4; 6 | // 项目主页 7 | static const String sourceUrl = "https://github.com/Predidit/Kazumi"; 8 | // 图标作者 9 | static const String iconUrl = "https://www.pixiv.net/users/66219277"; 10 | // 规则仓库 11 | static const String pluginShop = 'https://raw.githubusercontent.com/Predidit/KazumiRules/main/'; 12 | // 在线升级 13 | static const String latestApp = 14 | 'https://api.github.com/repos/Predidit/Kazumi/releases/latest'; 15 | // Github镜像 16 | static const String gitMirror = 'https://ghfast.top/'; 17 | // 每日放送 18 | static const String bangumiCalendar = 'https://next.bgm.tv/p1/calendar'; 19 | // Bangumi 主页 20 | static const String bangumiIndex = 'https://bangumi.tv/'; 21 | // 番剧检索 (弃用) 22 | static const String bangumiSearch = 'https://api.bgm.tv/search/subject/'; 23 | // 条目搜索 24 | static const String bangumiRankSearch = 'https://api.bgm.tv/v0/search/subjects?limit={0}&offset={1}'; 25 | // 从条目ID获取详细信息 26 | static const String bangumiInfoByID = 'https://api.bgm.tv/v0/subjects/'; 27 | // 从条目ID获取剧集ID 28 | static const String bangumiEpisodeByID = 'https://api.bgm.tv/v0/episodes'; 29 | // Next条目API 30 | static const String bangumiTrendsNext = 'https://next.bgm.tv/p1/trending/subjects'; 31 | static const String bangumiInfoByIDNext = 'https://next.bgm.tv/p1/subjects/'; 32 | static const String characterInfoByCharacterIDNext = 'https://next.bgm.tv/p1/characters/{0}'; 33 | static const String bangumiEpisodeByIDNext = 'https://next.bgm.tv/p1/episodes/'; 34 | static const String bangumiCharacterByIDNext = 'https://next.bgm.tv/p1/characters/'; 35 | static const String bangumiStaffByIDNext = 'https://next.bgm.tv/p1/subjects/{0}/staffs/persons'; 36 | // 弹弹Play 37 | static const String dandanIndex = 'https://www.dandanplay.com/'; 38 | static const String dandanAPIDomain = 'https://api.dandanplay.net'; 39 | static const String dandanAPIComment = "/api/v2/comment/"; 40 | static const String dandanAPISearch = "/api/v2/search/anime"; 41 | static const String dandanAPIInfo = "/api/v2/bangumi/"; 42 | 43 | static String formatUrl(String url, List params) { 44 | for (int i = 0; i < params.length; i++) { 45 | url = url.replaceAll('{$i}', params[i].toString()); 46 | } 47 | return url; 48 | } 49 | } -------------------------------------------------------------------------------- /lib/request/plugin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:convert'; 3 | import 'package:kazumi/request/api.dart'; 4 | import 'package:kazumi/request/request.dart'; 5 | import 'package:kazumi/plugins/plugins.dart'; 6 | import 'package:kazumi/modules/plugin/plugin_http_module.dart'; 7 | 8 | class PluginHTTP { 9 | static Future> getPluginList() async { 10 | List pluginHTTPItemList = []; 11 | try { 12 | var res = await Request().get('${Api.pluginShop}index.json'); 13 | final jsonData = json.decode(res.data); 14 | // debugPrint('${jsonData.toString()}'); 15 | for (dynamic pluginJsonItem in jsonData) { 16 | try { 17 | PluginHTTPItem pluginHTTPItem = PluginHTTPItem.fromJson(pluginJsonItem); 18 | pluginHTTPItemList.add(pluginHTTPItem); 19 | } catch (_) {} 20 | } 21 | } catch (e) { 22 | debugPrint('获取插件仓库错误${e.toString()}'); 23 | } 24 | return pluginHTTPItemList; 25 | } 26 | 27 | static Future getPlugin(String name) async { 28 | Plugin? plugin; 29 | try { 30 | var res = await Request().get('${Api.pluginShop}$name.json'); 31 | final jsonData = json.decode(res.data); 32 | plugin = Plugin.fromJson(jsonData); 33 | } catch(e) { 34 | debugPrint('获取插件 $name 错误 ${e.toString()}'); 35 | } 36 | return plugin; 37 | } 38 | } -------------------------------------------------------------------------------- /lib/shaders/shaders_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:mobx/mobx.dart'; 3 | import 'package:flutter/services.dart' show rootBundle, AssetManifest; 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:logger/logger.dart'; 6 | import 'package:path/path.dart' as path; 7 | import 'package:kazumi/utils/logger.dart'; 8 | 9 | part 'shaders_controller.g.dart'; 10 | 11 | class ShadersController = _ShadersController with _$ShadersController; 12 | 13 | abstract class _ShadersController with Store { 14 | late Directory shadersDirectory; 15 | 16 | Future copyShadersToExternalDirectory() async { 17 | final assetManifest = await AssetManifest.loadFromAssetBundle(rootBundle); 18 | final assets = assetManifest.listAssets(); 19 | final directory = await getApplicationSupportDirectory(); 20 | shadersDirectory = Directory(path.join(directory.path, 'anime_shaders')); 21 | 22 | if (!await shadersDirectory.exists()) { 23 | await shadersDirectory.create(recursive: true); 24 | KazumiLogger() 25 | .log(Level.info, 'Create GLSL Shader: ${shadersDirectory.path}'); 26 | } 27 | 28 | final shaderFiles = assets.where((String asset) => 29 | asset.startsWith('assets/shaders/') && asset.endsWith('.glsl')); 30 | 31 | int copiedFilesCount = 0; 32 | 33 | for (var filePath in shaderFiles) { 34 | final fileName = filePath.split('/').last; 35 | final targetFile = File(path.join(shadersDirectory.path, fileName)); 36 | if (await targetFile.exists()) { 37 | KazumiLogger() 38 | .log(Level.info, 'GLSL Shader exists, skip: ${targetFile.path}'); 39 | continue; 40 | } 41 | 42 | try { 43 | final data = await rootBundle.load(filePath); 44 | final List bytes = data.buffer.asUint8List(); 45 | await targetFile.writeAsBytes(bytes); 46 | copiedFilesCount++; 47 | KazumiLogger().log(Level.info, 'Copy: ${targetFile.path}'); 48 | } catch (e) { 49 | KazumiLogger().log(Level.fatal, 'Copy: ($filePath): $e'); 50 | } 51 | } 52 | 53 | KazumiLogger().log(Level.info, 54 | '$copiedFilesCount GLSL files copied to ${shadersDirectory.path}'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/shaders/shaders_controller.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'shaders_controller.dart'; 4 | 5 | // ************************************************************************** 6 | // StoreGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers 10 | 11 | mixin _$ShadersController on _ShadersController, Store { 12 | @override 13 | String toString() { 14 | return ''' 15 | 16 | '''; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/utils/anime_season.dart: -------------------------------------------------------------------------------- 1 | /// This class asks for DateTime to get a string to indicate seasonal anime 2 | class AnimeSeason { 3 | late DateTime _date; 4 | final _seasons = ['冬季', '春季', '夏季', '秋季']; 5 | 6 | AnimeSeason(DateTime date) { 7 | _date = date; 8 | } 9 | 10 | List _getYearAndSeason(DateTime dt) { 11 | int year = dt.year; 12 | int month = dt.month; 13 | 14 | int season; 15 | if ((month == 1) || (month == 2) || (month == 3)) { 16 | season = 0; 17 | } else if ((month == 4) || (month == 5) || (month == 6)) { 18 | season = 1; 19 | } else if ((month == 7) || (month == 8) || (month == 9)) { 20 | season = 2; 21 | } else { 22 | season = 3; 23 | } 24 | 25 | return [year, season]; 26 | } 27 | 28 | // Convert the DateTime to a List containing two strings (the start of the season -1 and the end of the season -1 ) eg: 2024-09-23 -> ['2024-06-01', '2024-09-01'] 29 | // why -1? because the air date is the launch date of the anime, it is usually a few days before the start of the season 30 | List toSeasonStartAndEnd() { 31 | var yas = _getYearAndSeason(_date); 32 | int year = yas[0]; 33 | int season = yas[1]; 34 | 35 | var end = DateTime(year, (season + 1) * 3, 1); 36 | 37 | int startMonth = season * 3; 38 | if (startMonth == 0) { 39 | startMonth = 12; 40 | year--; 41 | } 42 | 43 | var start = DateTime(year, startMonth, 1); 44 | return [start.toString(), end.toString()]; 45 | } 46 | 47 | @override 48 | String toString() { 49 | var yas = _getYearAndSeason(_date); 50 | 51 | return '${yas[0]}年${_seasons[yas[1]]}新番'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/utils/extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | extension ImageExtension on num { 4 | int cacheSize(BuildContext context) { 5 | return (this * MediaQuery.of(context).devicePixelRatio).round(); 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /lib/utils/external_player.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:logger/logger.dart'; 3 | import 'package:kazumi/utils/logger.dart'; 4 | 5 | class ExternalPlayer { 6 | // 注意:仍需开发 iOS/Linux 设备的外部播放功能。 7 | // 在 Windows 设备上,对于其他可能的实现,使用 scheme 的方案没有效果。VLC / PotPlayer 等主流播放器更倾向于使用 CLI 命令。 8 | // 可行的 iOS 处理代码,请参见 ios/Runner/AppDelegate.swift 的注释部分。 9 | static const platform = MethodChannel('com.predidit.kazumi/intent'); 10 | 11 | static Future launchURLWithMIME(String url, String mimeType) async { 12 | try { 13 | await platform.invokeMethod( 14 | 'openWithMime', {'url': url, 'mimeType': mimeType}); 15 | return true; 16 | } on PlatformException catch (e) { 17 | KazumiLogger() 18 | .log(Level.error, "Failed to open with mime: '${e.message}'."); 19 | return false; 20 | } 21 | } 22 | 23 | static Future launchURLWithReferer(String url, String referer) async { 24 | try { 25 | await platform.invokeMethod( 26 | 'openWithReferer', {'url': url, 'referer': referer}); 27 | return true; 28 | } on PlatformException catch (e) { 29 | KazumiLogger() 30 | .log(Level.error, "Failed to open with referer: '${e.message}'."); 31 | return false; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/utils/mortis.dart: -------------------------------------------------------------------------------- 1 | // The file contains some interesting imformation about dandan public api. 2 | // Unusual name to avoid github global search, it's just test code and will be replaced in github actions. 3 | const Map mortis = { 4 | 'id': 'kvpx7qkqjh', 5 | 'value': 'rABUaBLqdz7aCSi3fe88ZDj2gwga9Vax', 6 | }; -------------------------------------------------------------------------------- /lib/utils/search_parser.dart: -------------------------------------------------------------------------------- 1 | class SearchParser { 2 | final String query; 3 | final RegExp _idRegExp = RegExp(r'id:(\d+)', caseSensitive: false); 4 | final RegExp _tagRegExp = RegExp(r'tag:([\w\u4e00-\u9fa5\u30A0-\u30FF\.\-]+)', caseSensitive: false); 5 | final RegExp _sortRegExp = RegExp(r'sort:([\w\-]+)', caseSensitive: false); 6 | 7 | SearchParser(this.query); 8 | 9 | String? parseId() { 10 | final match = _idRegExp.firstMatch(query); 11 | return match != null ? match.group(1) : null; 12 | } 13 | 14 | String? parseTag() { 15 | final match = _tagRegExp.firstMatch(query); 16 | return match != null ? match.group(1) : null; 17 | } 18 | 19 | String? parseSort() { 20 | final match = _sortRegExp.firstMatch(query); 21 | return match != null ? match.group(1) : null; 22 | } 23 | 24 | String parseKeywords() { 25 | String cleaned = query.replaceAll(_idRegExp, ''); 26 | cleaned = cleaned.replaceAll(_tagRegExp, ''); 27 | cleaned = cleaned.replaceAll(_sortRegExp, ''); 28 | return cleaned.trim(); 29 | } 30 | 31 | bool hasSortSyntax() { 32 | return _sortRegExp.hasMatch(query); 33 | } 34 | 35 | String removeSort() { 36 | return query.replaceAll(_sortRegExp, '').trim(); 37 | } 38 | 39 | String updateSort(String sortValue) { 40 | if (hasSortSyntax()) { 41 | return query.replaceAllMapped(_sortRegExp, (match) => 'sort:$sortValue'); 42 | } else { 43 | return '${query.trim()} sort:$sortValue'.trim(); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /lib/utils/string_match.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | int levenshteinDistance(String s1, String s2) { 4 | if (s1 == s2) return 0; 5 | if (s1.isEmpty) return s2.length; 6 | if (s2.isEmpty) return s1.length; 7 | 8 | List v0 = List.generate(s2.length + 1, (i) => i); 9 | List v1 = List.filled(s2.length + 1, 0); 10 | 11 | for (int i = 0; i < s1.length; i++) { 12 | v1[0] = i + 1; 13 | 14 | for (int j = 0; j < s2.length; j++) { 15 | int cost = (s1[i] == s2[j]) ? 0 : 1; 16 | v1[j + 1] = min(v1[j] + 1, min(v0[j + 1] + 1, v0[j] + cost)); 17 | } 18 | 19 | for (int j = 0; j < v0.length; j++) { 20 | v0[j] = v1[j]; 21 | } 22 | } 23 | 24 | return v1[s2.length]; 25 | } 26 | 27 | // 计算相似度百分比 28 | double calculateSimilarity(String s1, String s2) { 29 | int maxLength = max(s1.length, s2.length); 30 | if (maxLength == 0) return 1.0; 31 | if (s1 == s2) return 1.0; 32 | return (1.0 - levenshteinDistance(s1, s2) / maxLength); 33 | } 34 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /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 | desktop_webview_window 7 | dynamic_color 8 | flutter_volume_controller 9 | media_kit_libs_linux 10 | media_kit_video 11 | screen_retriever_linux 12 | tray_manager 13 | url_launcher_linux 14 | window_manager 15 | ) 16 | 17 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 18 | ) 19 | 20 | set(PLUGIN_BUNDLED_LIBRARIES) 21 | 22 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 24 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 27 | endforeach(plugin) 28 | 29 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 30 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 31 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 32 | endforeach(ffi_plugin) 33 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import connectivity_plus 9 | import device_info_plus 10 | import dynamic_color 11 | import flutter_volume_controller 12 | import media_kit_libs_macos_video 13 | import media_kit_video 14 | import package_info_plus 15 | import path_provider_foundation 16 | import screen_pixel 17 | import screen_retriever_macos 18 | import sqflite_darwin 19 | import tray_manager 20 | import url_launcher_macos 21 | import wakelock_plus 22 | import webview_flutter_wkwebview 23 | import window_manager 24 | 25 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 26 | ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) 27 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 28 | DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) 29 | FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin")) 30 | MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) 31 | MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) 32 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 33 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 34 | ScreenPixelPlugin.register(with: registry.registrar(forPlugin: "ScreenPixelPlugin")) 35 | ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) 36 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 37 | TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) 38 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 39 | WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) 40 | WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) 41 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) 42 | } 43 | -------------------------------------------------------------------------------- /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 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | }, 6 | "images": [ 7 | { 8 | "size": "16x16", 9 | "idiom": "mac", 10 | "filename": "app_icon_16.png", 11 | "scale": "1x" 12 | }, 13 | { 14 | "size": "16x16", 15 | "idiom": "mac", 16 | "filename": "app_icon_32.png", 17 | "scale": "2x" 18 | }, 19 | { 20 | "size": "32x32", 21 | "idiom": "mac", 22 | "filename": "app_icon_32.png", 23 | "scale": "1x" 24 | }, 25 | { 26 | "size": "32x32", 27 | "idiom": "mac", 28 | "filename": "app_icon_64.png", 29 | "scale": "2x" 30 | }, 31 | { 32 | "size": "128x128", 33 | "idiom": "mac", 34 | "filename": "app_icon_128.png", 35 | "scale": "1x" 36 | }, 37 | { 38 | "size": "128x128", 39 | "idiom": "mac", 40 | "filename": "app_icon_256.png", 41 | "scale": "2x" 42 | }, 43 | { 44 | "size": "256x256", 45 | "idiom": "mac", 46 | "filename": "app_icon_256.png", 47 | "scale": "1x" 48 | }, 49 | { 50 | "size": "256x256", 51 | "idiom": "mac", 52 | "filename": "app_icon_512.png", 53 | "scale": "2x" 54 | }, 55 | { 56 | "size": "512x512", 57 | "idiom": "mac", 58 | "filename": "app_icon_512.png", 59 | "scale": "1x" 60 | }, 61 | { 62 | "size": "512x512", 63 | "idiom": "mac", 64 | "filename": "app_icon_1024.png", 65 | "scale": "2x" 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = kazumi 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.kazumi 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2024 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 | 14 | 15 | -------------------------------------------------------------------------------- /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 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | NSHumanReadableCopyright 31 | $(PRODUCT_COPYRIGHT) 32 | NSMainNibFile 33 | MainMenu 34 | NSPrincipalClass 35 | NSApplication 36 | 37 | 38 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 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.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /static/screenshot/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/static/screenshot/img_1.png -------------------------------------------------------------------------------- /static/screenshot/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/static/screenshot/img_2.png -------------------------------------------------------------------------------- /static/screenshot/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/static/screenshot/img_3.png -------------------------------------------------------------------------------- /static/screenshot/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/static/screenshot/img_4.png -------------------------------------------------------------------------------- /static/screenshot/img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/static/screenshot/img_5.png -------------------------------------------------------------------------------- /static/screenshot/img_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/static/screenshot/img_6.png -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | 11 | void main() { 12 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | kazumi 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kazumi", 3 | "short_name": "kazumi", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | void RegisterPlugins(flutter::PluginRegistry* registry) { 22 | ConnectivityPlusWindowsPluginRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); 24 | DynamicColorPluginCApiRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); 26 | FlutterVolumeControllerPluginCApiRegisterWithRegistrar( 27 | registry->GetRegistrarForPlugin("FlutterVolumeControllerPluginCApi")); 28 | MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( 29 | registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); 30 | MediaKitVideoPluginCApiRegisterWithRegistrar( 31 | registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); 32 | ScreenPixelPluginCApiRegisterWithRegistrar( 33 | registry->GetRegistrarForPlugin("ScreenPixelPluginCApi")); 34 | ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( 35 | registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); 36 | TrayManagerPluginRegisterWithRegistrar( 37 | registry->GetRegistrarForPlugin("TrayManagerPlugin")); 38 | UrlLauncherWindowsRegisterWithRegistrar( 39 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 40 | WebviewWindowsPluginRegisterWithRegistrar( 41 | registry->GetRegistrarForPlugin("WebviewWindowsPlugin")); 42 | WindowManagerPluginRegisterWithRegistrar( 43 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 44 | } 45 | -------------------------------------------------------------------------------- /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 | connectivity_plus 7 | dynamic_color 8 | flutter_volume_controller 9 | media_kit_libs_windows_video 10 | media_kit_video 11 | screen_pixel 12 | screen_retriever_windows 13 | tray_manager 14 | url_launcher_windows 15 | webview_windows 16 | window_manager 17 | ) 18 | 19 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 20 | ) 21 | 22 | set(PLUGIN_BUNDLED_LIBRARIES) 23 | 24 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 26 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 28 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 29 | endforeach(plugin) 30 | 31 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 32 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 33 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 34 | endforeach(ffi_plugin) 35 | -------------------------------------------------------------------------------- /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 | "external_player_utils.cpp" 14 | "fullscreen_utils.cpp" 15 | "win32_window.cpp" 16 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 17 | "Runner.rc" 18 | "runner.exe.manifest" 19 | ) 20 | 21 | # Apply the standard set of build settings. This can be removed for applications 22 | # that need different build settings. 23 | apply_standard_settings(${BINARY_NAME}) 24 | 25 | # Add preprocessor definitions for the build version. 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 29 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 30 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 31 | 32 | # Disable Windows macros that collide with C++ standard library functions. 33 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 34 | 35 | # Add dependency libraries and include directories. Add any application-specific 36 | # dependencies here. 37 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 38 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 39 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 40 | 41 | # Run the Flutter tool portions of the build. This must not be removed. 42 | add_dependencies(${BINARY_NAME} flutter_assemble) 43 | 44 | # Remove the .exp and .lib files generated by the linker. 45 | # These files are not needed for the application and can be safely removed. 46 | add_custom_command(TARGET ${BINARY_NAME} 47 | POST_BUILD 48 | COMMAND ${CMAKE_COMMAND} -E remove "${CMAKE_INSTALL_PREFIX}/${BINARY_NAME}.exp" 49 | COMMAND ${CMAKE_COMMAND} -E remove "${CMAKE_INSTALL_PREFIX}/${BINARY_NAME}.lib" 50 | ) 51 | -------------------------------------------------------------------------------- /windows/runner/external_player_utils.cpp: -------------------------------------------------------------------------------- 1 | // This file is a part of Kazumi 2 | // (https://github.com/Predidit/Kazumi). 3 | // 4 | // Copyright © 2024 Predidit 5 | // All rights reserved. 6 | // Use of this source code is governed by GPLv3 license that can be found in the 7 | // LICENSE file. 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "external_player_utils.h" 16 | 17 | void ExternalPlayerUtils::OpenWithPlayer(const char* url) { 18 | // temp file path 19 | wchar_t tempPath[MAX_PATH]; 20 | GetTempPathW(MAX_PATH, tempPath); 21 | 22 | // Generate a random file name 23 | std::wstring randomFileName = L"kazumi_stream_"; 24 | std::random_device rd; 25 | std::mt19937 eng(rd()); 26 | std::uniform_int_distribution<> distr(10000000, 99999999); 27 | 28 | randomFileName += std::to_wstring(distr(eng)) + L".m3u8"; 29 | 30 | wchar_t tempFile[MAX_PATH]; 31 | wcscpy_s(tempFile, tempPath); 32 | wcscat_s(tempFile, randomFileName.c_str()); 33 | 34 | // write the URL to the temp file 35 | std::wofstream outFile(tempFile); 36 | if (outFile.is_open()) { 37 | outFile << L"#EXTM3U\n"; 38 | outFile << std::wstring(url, url + strlen(url)); 39 | outFile.close(); 40 | } else { 41 | return; 42 | } 43 | 44 | SHELLEXECUTEINFO execInfo = {0}; 45 | execInfo.cbSize = sizeof(SHELLEXECUTEINFO); 46 | execInfo.fMask = SEE_MASK_INVOKEIDLIST; 47 | execInfo.lpVerb = L"openas"; 48 | execInfo.lpFile = tempFile; 49 | execInfo.nShow = SW_SHOWNORMAL; 50 | 51 | ShellExecuteEx(&execInfo); 52 | 53 | // DeleteFileW(tempFile); 54 | } -------------------------------------------------------------------------------- /windows/runner/external_player_utils.h: -------------------------------------------------------------------------------- 1 | // This file is a part of Kazumi 2 | // (https://github.com/Predidit/Kazumi). 3 | // 4 | // Copyright © 2024 Predidit 5 | // All rights reserved. 6 | // Use of this source code is governed by GPLv3 license that can be found in the 7 | // LICENSE file. 8 | 9 | #ifndef EXTERNAL_PLAYER_UTILS_H_ 10 | #define EXTERNAL_PLAYER_UTILS_H_ 11 | 12 | #include 13 | 14 | #include 15 | 16 | class ExternalPlayerUtils { 17 | public: 18 | static void OpenWithPlayer(const char* url); 19 | }; 20 | 21 | #endif // EXTERNAL_PLAYER_UTILS_H_ 22 | -------------------------------------------------------------------------------- /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 | // Register Intent MethodChannel 33 | void RegisterIntentChannel(); 34 | }; 35 | 36 | #endif // RUNNER_FLUTTER_WINDOW_H_ 37 | -------------------------------------------------------------------------------- /windows/runner/fullscreen_utils.h: -------------------------------------------------------------------------------- 1 | // This file is a part of media_kit 2 | // (https://github.com/media-kit/media-kit). 3 | // 4 | // Copyright © 2021 & onwards, Hitesh Kumar Saini . 5 | // All rights reserved. 6 | // Use of this source code is governed by MIT license that can be found in the 7 | // LICENSE file. 8 | 9 | #ifndef FULLSCREEN_UTILS_H_ 10 | #define FULLSCREEN_UTILS_H_ 11 | 12 | #include 13 | 14 | #include 15 | 16 | class FullscreenUtils { 17 | public: 18 | static void EnterNativeFullscreen(HWND window); 19 | 20 | static void ExitNativeFullscreen(HWND window); 21 | 22 | private: 23 | static constexpr auto kFlutterViewWindowClassName = L"FLUTTERVIEW"; 24 | 25 | static bool fullscreen_; 26 | static RECT rect_before_fullscreen_; 27 | }; 28 | 29 | #endif // FULLSCREEN_UTILS_H_ 30 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | // recommended by NVIDIA to enable high-performance GPU 9 | extern "C" 10 | { 11 | __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; 12 | } 13 | 14 | // recommended by AMD to enable high-performance GPU 15 | extern "C" 16 | { 17 | __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; 18 | } 19 | 20 | HANDLE mutex = NULL; 21 | 22 | bool isSingleInstance() 23 | { 24 | if (mutex != NULL) 25 | { 26 | return true; 27 | } 28 | std::wstring mutex_str = L"kazumi.win.mutex"; 29 | mutex = ::CreateMutex(NULL, TRUE, mutex_str.c_str()); 30 | if (mutex == NULL || GetLastError() == ERROR_ALREADY_EXISTS) 31 | { 32 | CloseHandle(mutex); 33 | mutex = NULL; 34 | return false; 35 | } 36 | return true; 37 | } 38 | 39 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 40 | _In_ wchar_t *command_line, _In_ int show_command) 41 | { 42 | // Make sure the application is a single instance. 43 | // This is important for the application to work correctly with the local storage. 44 | if (!isSingleInstance()) 45 | { 46 | MessageBox(NULL, L"Another instance is already running.", L"Error", MB_OK | MB_ICONERROR); 47 | return EXIT_FAILURE; 48 | } 49 | // Attach to console when present (e.g., 'flutter run') or create a 50 | // new console when running with a debugger. 51 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) 52 | { 53 | CreateAndAttachConsole(); 54 | } 55 | 56 | // Initialize COM, so that it is available for use in the library and/or 57 | // plugins. 58 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 59 | 60 | flutter::DartProject project(L"data"); 61 | 62 | std::vector command_line_arguments = 63 | GetCommandLineArguments(); 64 | 65 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 66 | 67 | FlutterWindow window(project); 68 | Win32Window::Point origin(10, 10); 69 | Win32Window::Size size(1280, 720); 70 | if (!window.Create(L"kazumi", origin, size)) 71 | { 72 | if (mutex) { 73 | CloseHandle(mutex); 74 | } 75 | return EXIT_FAILURE; 76 | } 77 | window.SetQuitOnClose(true); 78 | 79 | ::MSG msg; 80 | while (::GetMessage(&msg, nullptr, 0, 0)) 81 | { 82 | ::TranslateMessage(&msg); 83 | ::DispatchMessage(&msg); 84 | } 85 | 86 | ::CoUninitialize(); 87 | if (mutex) { 88 | CloseHandle(mutex); 89 | } 90 | return EXIT_SUCCESS; 91 | } 92 | -------------------------------------------------------------------------------- /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/Predidit/Kazumi/193c5e25d219817ad17277202ae86712ec39da67/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 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length <= 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------