├── .github └── workflows │ └── release.yaml ├── .gitignore ├── .metadata ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── bilineo │ │ │ │ └── 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 ├── images │ ├── big-vip.png │ ├── live.png │ ├── loading.png │ ├── logo │ │ └── logo_android_2.png │ ├── lv │ │ ├── lv0.png │ │ ├── lv1.png │ │ ├── lv2.png │ │ ├── lv3.png │ │ ├── lv4.png │ │ ├── lv5.png │ │ └── lv6.png │ └── noface.jpeg ├── logo │ ├── logo_android.png │ └── logo_ios.png └── plugins │ └── girigirilove.json ├── devtools_options.yaml ├── 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 │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── app_module.dart ├── app_widget.dart ├── bean │ ├── bangumi │ │ ├── bangumi_info.dart │ │ └── bangumi_list.dart │ ├── danmaku │ │ ├── dm.pb.dart │ │ ├── dm.pbenum.dart │ │ ├── dm.pbjson.dart │ │ ├── dm.pbserver.dart │ │ └── dm.proto │ └── settings │ │ └── settings.dart ├── main.dart ├── pages │ ├── card │ │ ├── bangumi_card.dart │ │ ├── bangumi_panel.dart │ │ ├── network_img_layer.dart │ │ └── pbadge.dart │ ├── danmaku │ │ └── controller.dart │ ├── error │ │ └── http_error.dart │ ├── index_module.dart │ ├── index_page.dart │ ├── init_page.dart │ ├── menu │ │ └── menu.dart │ ├── my │ │ ├── my_controller.dart │ │ ├── my_controller.g.dart │ │ ├── my_module.dart │ │ ├── my_page.dart │ │ ├── user_info.dart │ │ └── user_info.g.dart │ ├── player │ │ ├── play_quality.dart │ │ ├── player_controller.dart │ │ ├── player_controller.g.dart │ │ ├── player_datasource.dart │ │ ├── player_item.dart │ │ └── player_url.dart │ ├── popular │ │ ├── popular_controller.dart │ │ ├── popular_controller.g.dart │ │ ├── popular_module.dart │ │ └── popular_page.dart │ ├── rating │ │ ├── rating_module.dart │ │ └── rating_page.dart │ ├── router.dart │ ├── search │ │ ├── search_controller.dart │ │ ├── search_controller.g.dart │ │ ├── search_module.dart │ │ ├── search_page.dart │ │ ├── search_result.dart │ │ ├── search_suggest.dart │ │ └── search_type.dart │ ├── search_result │ │ ├── results_controller.dart │ │ ├── results_controller.g.dart │ │ ├── results_item.dart │ │ ├── results_module.dart │ │ └── results_page.dart │ ├── video │ │ ├── video_controller.dart │ │ ├── video_controller.g.dart │ │ ├── video_module.dart │ │ └── video_page.dart │ ├── webview │ │ ├── webview_controller.dart │ │ ├── webview_controller.g.dart │ │ ├── webview_module.dart │ │ └── webview_page.dart │ └── webview_desktop │ │ ├── webview_desktop_module.dart │ │ └── webview_desktop_page.dart ├── request │ ├── api.dart │ ├── bangumi.dart │ ├── constants.dart │ ├── cookie.dart │ ├── danmaku.dart │ ├── interceptor.dart │ ├── request.dart │ ├── search.dart │ ├── user.dart │ └── video.dart └── utils │ ├── constans.dart │ ├── em.dart │ ├── extension.dart │ ├── id.dart │ ├── storage.dart │ ├── utils.dart │ ├── video.dart │ └── wbisign.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 ├── test └── widget_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /.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: "41456452f29d64e8deb623a3c927524bcf9f111b" 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: 41456452f29d64e8deb623a3c927524bcf9f111b 17 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 18 | - platform: android 19 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 20 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 21 | - platform: ios 22 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 23 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 24 | - platform: linux 25 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 26 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 27 | - platform: macos 28 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 29 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 30 | - platform: web 31 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 32 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 33 | - platform: windows 34 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 35 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 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/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "bilineo", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "bilineo (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "bilineo (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": false, 3 | "java.configuration.updateBuildConfiguration": "interactive" 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bilineo 2 | 3 | 又一个 Bilibili 第三方客户端,仅支持番剧相关功能,项目使用 flutter 构建。本项目目的为个人学习与测试 Flutter 开发,所用API为B站公开API的封装,无任何破解行为。 4 | 5 | ## 支持平台 6 | 7 | - Android 10 及以上 8 | - Windows 10 1809 及以上 9 | - Linux (实验性) 10 | 11 | ## 功能 / 开发计划 12 | 13 | - [x] 番剧目录 14 | - [x] 番剧搜索 15 | - [x] 番剧字幕 16 | - [x] 番剧弹幕 17 | - [x] 港澳台番剧 18 | - [x] 视频播放器 19 | - [x] 硬件加速 20 | - [x] 在线更新 21 | - [x] 倍速播放 22 | - [ ] 新番动态 23 | - [ ] 番剧时间表 24 | - [ ] 追番列表 25 | - [ ] 番剧下载 26 | - [ ] 番剧评论 27 | - [ ] 还有更多 (/・ω・\) 28 | 29 | ## Q&A 30 | 31 | #### Q: 为什么我找不到 xxx 番剧? 为什么新番缺了那么多? 32 | A: 由于众所周知的问题,B站近年即使在港澳台区,购买番剧版权的策略也倾向于保守,如果没有您要找的番剧,可以试试作者的另一个项目 [oneAnime](https://github.com/Predidit/oneAnime) 33 | 34 | #### Q: 为什么 xxx 番剧没有字幕? 35 | A: B站部分番剧使用外挂字幕而非内嵌字幕,对应接口只支持包含有效用户签名的调用,对于此类番剧,需要您登录账号以获得字幕。 36 | 37 | #### Q: 我在尝试自行编译该项目,但是编译不通过。 38 | 39 | A: flutter 项目编译需要良好的网络环境,如果您位于中国大陆,可能需要设置恰当的镜像地址。如果您在编译 `Linux` 版本, 需要运行如下命令来安装编译需要的依赖。

40 | `sudo apt-get install -y clang cmake libgtk-3-dev ninja-build libayatana-appindicator3-dev mpv libmpv-dev libasound2-dev` 41 | 42 | ## 致谢 43 | 44 | 特别感谢 [pilipala](https://github.com/guozhigq/pilipala) 本项目使用了来自 pilipala 的代码。 45 | 46 | 感谢 [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect) 该项目收集的公开API使第三方客户端成为可能。 47 | 48 | 感谢 [bilibili-helper](https://github.com/ipcjs/bilibili-helper) 提供了解析 Bilibili 港澳台的相关思路。 49 | -------------------------------------------------------------------------------- /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/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "com.example.bilineo" 27 | compileSdkVersion flutter.compileSdkVersion 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "com.predidit.bilineo" 46 | // You can update the following values to match your application needs. 47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 48 | minSdkVersion flutter.minSdkVersion 49 | targetSdkVersion flutter.targetSdkVersion 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | buildTypes { 55 | release { 56 | // TODO: Add your own signing config for the release build. 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.debug 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies {} 68 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/bilineo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.bilineo 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffffff 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | tasks.register("clean", Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /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-7.5-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 | plugins { 20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 21 | } 22 | } 23 | 24 | plugins { 25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 26 | id "com.android.application" version "7.3.0" apply false 27 | } 28 | 29 | include ":app" 30 | -------------------------------------------------------------------------------- /assets/images/big-vip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/big-vip.png -------------------------------------------------------------------------------- /assets/images/live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/live.png -------------------------------------------------------------------------------- /assets/images/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/loading.png -------------------------------------------------------------------------------- /assets/images/logo/logo_android_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/logo/logo_android_2.png -------------------------------------------------------------------------------- /assets/images/lv/lv0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/lv/lv0.png -------------------------------------------------------------------------------- /assets/images/lv/lv1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/lv/lv1.png -------------------------------------------------------------------------------- /assets/images/lv/lv2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/lv/lv2.png -------------------------------------------------------------------------------- /assets/images/lv/lv3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/lv/lv3.png -------------------------------------------------------------------------------- /assets/images/lv/lv4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/lv/lv4.png -------------------------------------------------------------------------------- /assets/images/lv/lv5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/lv/lv5.png -------------------------------------------------------------------------------- /assets/images/lv/lv6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/lv/lv6.png -------------------------------------------------------------------------------- /assets/images/noface.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/images/noface.jpeg -------------------------------------------------------------------------------- /assets/logo/logo_android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/logo/logo_android.png -------------------------------------------------------------------------------- /assets/logo/logo_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/assets/logo/logo_ios.png -------------------------------------------------------------------------------- /assets/plugins/girigirilove.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GiriGiriLove", 3 | "version": "0.0.1", 4 | "muliSources": "false", 5 | "useWebview": "false", 6 | "userAgent": "", 7 | "searchURL":"https://anime.girigirilove.com/search/-------------/?wd=@keyword", 8 | "searchList": "//div[4]/div[1]/div[1]/div[1]", 9 | "searchName": ".thumb-txt cor4 hide", 10 | "searchResult": ".a@href", 11 | "chapterRoads": "//div[5]/div[2]/div[1]/div[1]", 12 | "chapterResult": ".anthology-list-play size" 13 | } -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | extensions: 2 | -------------------------------------------------------------------------------- /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.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /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/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Bilineo 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | bilineo 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /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:bilineo/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/app_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_localizations/flutter_localizations.dart'; 5 | import 'package:flutter_modular/flutter_modular.dart'; 6 | import 'package:adaptive_theme/adaptive_theme.dart'; 7 | import 'package:flutter_displaymode/flutter_displaymode.dart'; 8 | 9 | class AppWidget extends StatefulWidget { 10 | const AppWidget({super.key}); 11 | 12 | @override 13 | State createState() => _AppWidgetState(); 14 | } 15 | 16 | class _AppWidgetState extends State { 17 | @override 18 | Widget build(BuildContext context) { 19 | var app = AdaptiveTheme( 20 | light: ThemeData( 21 | useMaterial3: true, 22 | brightness: Brightness.light, 23 | ), 24 | dark: ThemeData( 25 | useMaterial3: true, 26 | brightness: Brightness.dark, 27 | ), 28 | initial: AdaptiveThemeMode.system, 29 | builder: (theme, darkTheme) => MaterialApp.router( 30 | title: "BiliNeo", 31 | localizationsDelegates: GlobalMaterialLocalizations.delegates, 32 | supportedLocales: const [ 33 | Locale.fromSubtags( 34 | languageCode: 'zh', scriptCode: 'Hans', countryCode: "CN") 35 | ], 36 | locale: const Locale.fromSubtags( 37 | languageCode: 'zh', scriptCode: 'Hans', countryCode: "CN"), 38 | theme: theme, 39 | darkTheme: darkTheme, 40 | routerConfig: Modular.routerConfig, 41 | builder: FlutterSmartDialog.init(), 42 | // navigatorObservers: [Asuka.asukaHeroController], 43 | ), 44 | ); 45 | Modular.setObservers([FlutterSmartDialog.observer]); 46 | 47 | // 强制设置高帧率 48 | if (Platform.isAndroid) { 49 | try { 50 | late List modes; 51 | FlutterDisplayMode.supported.then((value) { 52 | modes = value; 53 | DisplayMode f = DisplayMode.auto; 54 | DisplayMode preferred = modes.toList().firstWhere((el) => el == f); 55 | FlutterDisplayMode.setPreferredMode(preferred); 56 | }); 57 | } catch (e) { 58 | debugPrint('高帧率设置失败 ${e.toString()}'); 59 | } 60 | } 61 | 62 | return app; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/bean/bangumi/bangumi_list.dart: -------------------------------------------------------------------------------- 1 | class BangumiListDataModel { 2 | BangumiListDataModel({ 3 | this.hasNext, 4 | this.list, 5 | this.num, 6 | this.size, 7 | this.total, 8 | }); 9 | 10 | int? hasNext; 11 | List? list; 12 | int? num; 13 | int? size; 14 | int? total; 15 | 16 | BangumiListDataModel.fromJson(Map json) { 17 | hasNext = json['has_next']; 18 | list = json['list'] != null 19 | ? json['list'] 20 | .map((e) => BangumiListItemModel.fromJson(e)) 21 | .toList() 22 | : []; 23 | num = json['num']; 24 | size = json['size']; 25 | total = json['total']; 26 | } 27 | } 28 | 29 | class BangumiListItemModel { 30 | BangumiListItemModel({ 31 | this.badge, 32 | this.badgeType, 33 | this.cover, 34 | // this.firstEp, 35 | this.indexShow, 36 | this.isFinish, 37 | this.link, 38 | this.mediaId, 39 | this.order, 40 | this.orderType, 41 | this.score, 42 | this.seasonId, 43 | this.seaconStatus, 44 | this.seasonType, 45 | this.subTitle, 46 | this.title, 47 | this.titleIcon, 48 | this.progress, 49 | }); 50 | 51 | String? badge; 52 | int? badgeType; 53 | String? cover; 54 | String? indexShow; 55 | int? isFinish; 56 | String? link; 57 | int? mediaId; 58 | String? order; 59 | String? orderType; 60 | String? score; 61 | int? seasonId; 62 | int? seaconStatus; 63 | int? seasonType; 64 | String? subTitle; 65 | String? title; 66 | String? titleIcon; 67 | 68 | String? progress; 69 | 70 | BangumiListItemModel.fromJson(Map json) { 71 | badge = json['badge'] == '' ? null : json['badge']; 72 | badgeType = json['badge_type']; 73 | cover = json['cover']; 74 | indexShow = json['index_show']; 75 | isFinish = json['is_finish']; 76 | link = json['link']; 77 | mediaId = json['media_id']; 78 | order = json['order']; 79 | orderType = json['order_type']; 80 | score = json['score']; 81 | seasonId = json['season_id']; 82 | seaconStatus = json['seacon_status']; 83 | seasonType = json['season_type']; 84 | subTitle = json['sub_title']; 85 | title = json['title']; 86 | titleIcon = json['title_icon']; 87 | 88 | progress = json['progress']; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/bean/danmaku/dm.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: dm.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name, avoid_renaming_method_parameters 7 | 8 | import 'dart:async' as $async; 9 | 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | import 'dart:core' as $core; 13 | import 'dm.pb.dart' as $0; 14 | import 'dm.pbjson.dart'; 15 | 16 | export 'dm.pb.dart'; 17 | 18 | abstract class DMServiceBase extends $pb.GeneratedService { 19 | $async.Future<$0.DmSegMobileReply> dmSegMobile( 20 | $pb.ServerContext ctx, $0.DmSegMobileReq request); 21 | $async.Future<$0.DmViewReply> dmView( 22 | $pb.ServerContext ctx, $0.DmViewReq request); 23 | $async.Future<$0.Response> dmPlayerConfig( 24 | $pb.ServerContext ctx, $0.DmPlayerConfigReq request); 25 | $async.Future<$0.DmSegOttReply> dmSegOtt( 26 | $pb.ServerContext ctx, $0.DmSegOttReq request); 27 | $async.Future<$0.DmSegSDKReply> dmSegSDK( 28 | $pb.ServerContext ctx, $0.DmSegSDKReq request); 29 | $async.Future<$0.DmExpoReportRes> dmExpoReport( 30 | $pb.ServerContext ctx, $0.DmExpoReportReq request); 31 | 32 | $pb.GeneratedMessage createRequest($core.String method) { 33 | switch (method) { 34 | case 'DmSegMobile': 35 | return $0.DmSegMobileReq(); 36 | case 'DmView': 37 | return $0.DmViewReq(); 38 | case 'DmPlayerConfig': 39 | return $0.DmPlayerConfigReq(); 40 | case 'DmSegOtt': 41 | return $0.DmSegOttReq(); 42 | case 'DmSegSDK': 43 | return $0.DmSegSDKReq(); 44 | case 'DmExpoReport': 45 | return $0.DmExpoReportReq(); 46 | default: 47 | throw $core.ArgumentError('Unknown method: $method'); 48 | } 49 | } 50 | 51 | $async.Future<$pb.GeneratedMessage> handleCall($pb.ServerContext ctx, 52 | $core.String method, $pb.GeneratedMessage request) { 53 | switch (method) { 54 | case 'DmSegMobile': 55 | return this.dmSegMobile(ctx, request as $0.DmSegMobileReq); 56 | case 'DmView': 57 | return this.dmView(ctx, request as $0.DmViewReq); 58 | case 'DmPlayerConfig': 59 | return this.dmPlayerConfig(ctx, request as $0.DmPlayerConfigReq); 60 | case 'DmSegOtt': 61 | return this.dmSegOtt(ctx, request as $0.DmSegOttReq); 62 | case 'DmSegSDK': 63 | return this.dmSegSDK(ctx, request as $0.DmSegSDKReq); 64 | case 'DmExpoReport': 65 | return this.dmExpoReport(ctx, request as $0.DmExpoReportReq); 66 | default: 67 | throw $core.ArgumentError('Unknown method: $method'); 68 | } 69 | } 70 | 71 | $core.Map<$core.String, $core.dynamic> get $json => DMServiceBase$json; 72 | $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>> 73 | get $messageJson => DMServiceBase$messageJson; 74 | } 75 | -------------------------------------------------------------------------------- /lib/bean/settings/settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 3 | import 'package:hive/hive.dart'; 4 | import 'package:bilineo/utils/storage.dart'; 5 | import 'package:bilineo/utils/utils.dart'; 6 | 7 | class SetSwitchItem extends StatefulWidget { 8 | final String? title; 9 | final String? subTitle; 10 | final String? setKey; 11 | final bool? defaultVal; 12 | final Function? callFn; 13 | final bool? needReboot; 14 | 15 | const SetSwitchItem({ 16 | this.title, 17 | this.subTitle, 18 | this.setKey, 19 | this.defaultVal, 20 | this.callFn, 21 | this.needReboot, 22 | Key? key, 23 | }) : super(key: key); 24 | 25 | @override 26 | State createState() => _SetSwitchItemState(); 27 | } 28 | 29 | class _SetSwitchItemState extends State { 30 | // ignore: non_constant_identifier_names 31 | Box Setting = GStorage.setting; 32 | late bool val; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | val = Setting.get(widget.setKey, defaultValue: widget.defaultVal ?? false); 38 | } 39 | 40 | void switchChange(value) async { 41 | val = value ?? !val; 42 | await Setting.put(widget.setKey, val); 43 | if (widget.setKey == SettingBoxKey.autoUpdate && value == true) { 44 | Utils.checkUpdata(); 45 | } 46 | if (widget.callFn != null) { 47 | widget.callFn!.call(val); 48 | } 49 | if (widget.needReboot != null && widget.needReboot!) { 50 | SmartDialog.showToast('重启生效'); 51 | } 52 | setState(() {}); 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; 58 | TextStyle subTitleStyle = Theme.of(context) 59 | .textTheme 60 | .labelMedium! 61 | .copyWith(color: Theme.of(context).colorScheme.outline); 62 | return ListTile( 63 | enableFeedback: true, 64 | onTap: () => switchChange(null), 65 | title: Text(widget.title!, style: titleStyle), 66 | subtitle: widget.subTitle != null 67 | ? Text(widget.subTitle!, style: subTitleStyle) 68 | : null, 69 | trailing: Transform.scale( 70 | alignment: Alignment.centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大 71 | scale: 0.8, 72 | child: Switch( 73 | value: val, 74 | onChanged: (val) => switchChange(val), 75 | ), 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:bilineo/app_module.dart'; 5 | import 'package:bilineo/app_widget.dart'; 6 | import 'package:flutter_modular/flutter_modular.dart'; 7 | import 'package:media_kit/media_kit.dart'; 8 | import 'package:bilineo/utils/storage.dart'; 9 | import 'package:bilineo/request/request.dart'; 10 | import 'package:window_manager/window_manager.dart'; 11 | // import 'package:webview_flutter/webview_flutter.dart'; 12 | 13 | void main() async { 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | if (Platform.isWindows || Platform.isLinux) { 16 | await windowManager.ensureInitialized(); 17 | WindowOptions windowOptions = const WindowOptions( 18 | size: Size(1280, 830), 19 | center: true, 20 | // backgroundColor: Colors.white, 21 | skipTaskbar: false, 22 | titleBarStyle: TitleBarStyle.normal, 23 | ); 24 | windowManager.waitUntilReadyToShow(windowOptions, () async { 25 | await windowManager.show(); 26 | await windowManager.focus(); 27 | }); 28 | // windowManager.setMaximizable(false); 29 | } 30 | MediaKit.ensureInitialized(); 31 | await GStorage.init(); 32 | Request(); 33 | await Request.setCookie(); 34 | runApp(ModularApp( 35 | module: AppModule(), 36 | child: const AppWidget(), 37 | )); 38 | } 39 | -------------------------------------------------------------------------------- /lib/pages/card/pbadge.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PBadge extends StatelessWidget { 4 | final String? text; 5 | final double? top; 6 | final double? right; 7 | final double? bottom; 8 | final double? left; 9 | final String? type; 10 | final String? size; 11 | final String? stack; 12 | final double? fs; 13 | 14 | const PBadge({ 15 | super.key, 16 | this.text, 17 | this.top, 18 | this.right, 19 | this.bottom, 20 | this.left, 21 | this.type = 'primary', 22 | this.size = 'medium', 23 | this.stack = 'position', 24 | this.fs = 11, 25 | }); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | ColorScheme t = Theme.of(context).colorScheme; 30 | // 背景色 31 | Color bgColor = t.primary; 32 | // 前景色 33 | Color color = t.onPrimary; 34 | // 边框色 35 | Color borderColor = Colors.transparent; 36 | if (type == 'gray') { 37 | bgColor = Colors.black54.withOpacity(0.4); 38 | color = Colors.white; 39 | } 40 | if (type == 'color') { 41 | bgColor = t.primaryContainer.withOpacity(0.6); 42 | color = t.primary; 43 | } 44 | if (type == 'line') { 45 | bgColor = Colors.transparent; 46 | color = t.primary; 47 | borderColor = t.primary; 48 | } 49 | 50 | EdgeInsets paddingStyle = 51 | const EdgeInsets.symmetric(vertical: 1, horizontal: 6); 52 | double fontSize = 11; 53 | BorderRadius br = BorderRadius.circular(4); 54 | 55 | if (size == 'small') { 56 | paddingStyle = const EdgeInsets.symmetric(vertical: 0, horizontal: 3); 57 | fontSize = 11; 58 | br = BorderRadius.circular(3); 59 | } 60 | 61 | Widget content = Container( 62 | padding: paddingStyle, 63 | decoration: BoxDecoration( 64 | borderRadius: br, 65 | color: bgColor, 66 | border: Border.all(color: borderColor), 67 | ), 68 | child: Text( 69 | text!, 70 | style: TextStyle(fontSize: fs ?? fontSize, color: color), 71 | ), 72 | ); 73 | if (stack == 'position') { 74 | return Positioned( 75 | top: top, 76 | left: left, 77 | right: right, 78 | bottom: bottom, 79 | child: content, 80 | ); 81 | } else { 82 | return Padding( 83 | padding: const EdgeInsets.only(right: 5), 84 | child: content, 85 | ); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/pages/danmaku/controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/request/danmaku.dart'; 2 | import 'package:bilineo/bean/danmaku/dm.pb.dart'; 3 | 4 | class PlDanmakuController { 5 | PlDanmakuController(this.cid); 6 | final int cid; 7 | Map> dmSegMap = {}; 8 | // 已请求的段落标记 9 | List requestedSeg = []; 10 | 11 | bool get initiated => requestedSeg.isNotEmpty; 12 | 13 | static int segmentLength = 60 * 6 * 1000; 14 | 15 | void initiate(int videoDuration, int progress) { 16 | if (requestedSeg.isEmpty) { 17 | int segCount = (videoDuration / segmentLength).ceil(); 18 | requestedSeg = List.generate(segCount, (index) => false); 19 | } 20 | queryDanmaku(calcSegment(progress)); 21 | } 22 | 23 | void dispose() { 24 | dmSegMap.clear(); 25 | requestedSeg.clear(); 26 | } 27 | 28 | int calcSegment(int progress) { 29 | return progress ~/ segmentLength; 30 | } 31 | 32 | void queryDanmaku(int segmentIndex) async { 33 | assert(requestedSeg[segmentIndex] == false); 34 | requestedSeg[segmentIndex] = true; 35 | final DmSegMobileReply result = await DanmakaHttp.queryDanmaku( 36 | cid: cid, segmentIndex: segmentIndex + 1); 37 | if (result.elems.isNotEmpty) { 38 | for (var element in result.elems) { 39 | int pos = element.progress ~/ 100; //每0.1秒存储一次 40 | if (dmSegMap[pos] == null) { 41 | dmSegMap[pos] = []; 42 | } 43 | dmSegMap[pos]!.add(element); 44 | } 45 | } 46 | } 47 | 48 | List? getCurrentDanmaku(int progress) { 49 | int segmentIndex = calcSegment(progress); 50 | if (!requestedSeg[segmentIndex]) { 51 | queryDanmaku(segmentIndex); 52 | } 53 | return dmSegMap[progress ~/ 100]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/pages/error/http_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HttpError extends StatelessWidget { 4 | const HttpError( 5 | {required this.errMsg, required this.fn, this.btnText, super.key}); 6 | 7 | final String? errMsg; 8 | final Function()? fn; 9 | final String? btnText; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return SliverToBoxAdapter( 14 | child: SizedBox( 15 | height: 400, 16 | child: Column( 17 | crossAxisAlignment: CrossAxisAlignment.center, 18 | mainAxisAlignment: MainAxisAlignment.center, 19 | children: [ 20 | const SizedBox(height: 30), 21 | Text( 22 | errMsg ?? '请求异常', 23 | textAlign: TextAlign.center, 24 | style: Theme.of(context).textTheme.titleSmall, 25 | ), 26 | const SizedBox(height: 20), 27 | FilledButton.tonal( 28 | onPressed: () { 29 | fn!(); 30 | }, 31 | style: ButtonStyle( 32 | backgroundColor: MaterialStateProperty.resolveWith((states) { 33 | return Theme.of(context).colorScheme.primary.withAlpha(20); 34 | }), 35 | ), 36 | child: Text( 37 | btnText ?? '点击重试', 38 | style: TextStyle(color: Theme.of(context).colorScheme.primary), 39 | ), 40 | ), 41 | ], 42 | ), 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/pages/index_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/pages/index_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | import 'package:bilineo/pages/router.dart'; 4 | import 'package:bilineo/pages/init_page.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:bilineo/pages/popular/popular_controller.dart'; 7 | import 'package:bilineo/pages/video/video_controller.dart'; 8 | import 'package:bilineo/pages/my/my_controller.dart'; 9 | import 'package:bilineo/pages/my/user_info.dart'; 10 | import 'package:bilineo/pages/webview/webview_controller.dart'; 11 | import 'package:bilineo/pages/search/search_controller.dart'; 12 | import 'package:bilineo/pages/search_result/results_controller.dart'; 13 | 14 | 15 | class IndexModule extends Module { 16 | @override 17 | List get imports => menu.moduleList; 18 | 19 | @override 20 | void binds(i) { 21 | i.addSingleton(PopularController.new); 22 | i.addSingleton(VideoController.new); 23 | i.addSingleton(MyController.new); 24 | i.addSingleton(UserInfoData.new); 25 | i.addSingleton(WebviewController.new); 26 | i.addSingleton(MySearchController.new); 27 | i.addSingleton(SearchResultController.new); 28 | // i.addSingleton(GStorage.new); 29 | // i.addSingleton(PlayerController.new); 30 | } 31 | 32 | @override 33 | void routes(r) { 34 | r.child("/", 35 | child: (_) => const InitPage(), 36 | children: [ 37 | ChildRoute( 38 | "/error", 39 | child: (_) => Scaffold( 40 | appBar: AppBar (title: const Text("BiliNeo")), 41 | body: const Center(child: Text("初始化失败")), 42 | ), 43 | ), 44 | ], 45 | transition: TransitionType.noTransition); 46 | r.child("/tab", child: (_) { 47 | return const IndexPage(); 48 | }, children: menu.routes, transition: TransitionType.noTransition); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/pages/index_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | import 'package:bilineo/pages/menu/menu.dart'; 4 | 5 | 6 | class IndexPage extends StatefulWidget { 7 | //const IndexPage({super.key}); 8 | const IndexPage({Key? key}) : super(key: key); 9 | 10 | @override 11 | State createState() => _IndexPageState(); 12 | } 13 | 14 | class _IndexPageState extends State with WidgetsBindingObserver { 15 | 16 | final PageController _page = PageController(); 17 | 18 | /// 统一处理前后台改变 19 | void appListener(bool state) { 20 | if (state) { 21 | print("应用前台"); 22 | } else { 23 | print("应用后台"); 24 | } 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | // return Row(children: [ 30 | // AppDrawer(page: _page), 31 | // Expanded( 32 | // child: PageView.builder( 33 | // controller: _page, 34 | // itemCount: menu.size, 35 | // onPageChanged: (i) => Modular.to.navigate("/tab${menu.getPath(i)}/"), 36 | // itemBuilder: (_, __) => const RouterOutlet(), 37 | // ), 38 | // ) 39 | // ]); 40 | return const BottomMenu(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/pages/init_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | 6 | 7 | 8 | class InitPage extends StatefulWidget { 9 | const InitPage({super.key}); 10 | 11 | @override 12 | State createState() => _InitPageState(); 13 | } 14 | 15 | class _InitPageState extends State { 16 | 17 | @override 18 | void initState() { 19 | _init(); 20 | super.initState(); 21 | } 22 | 23 | _init() { 24 | Modular.to.navigate('/tab/popular/'); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return const RouterOutlet(); 30 | } 31 | } 32 | 33 | class LoadingWidget extends StatelessWidget { 34 | const LoadingWidget({super.key, required this.value}); 35 | 36 | final double value; 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | var size = MediaQuery.of(context).size; 41 | return Scaffold( 42 | appBar: AppBar(title: const Text("BiliNeo")), 43 | body: Center( 44 | child: SizedBox( 45 | height: 200, 46 | child: Flex( 47 | direction: Axis.vertical, 48 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 49 | children: [ 50 | SizedBox( 51 | width: size.width * 0.6, 52 | child: LinearProgressIndicator( 53 | value: value, 54 | backgroundColor: Colors.black12, 55 | minHeight: 10, 56 | ), 57 | ), 58 | const Text("初始化中"), 59 | ], 60 | ), 61 | ), 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /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 _$userLoginAtom = 13 | Atom(name: '_MyController.userLogin', context: context); 14 | 15 | @override 16 | bool get userLogin { 17 | _$userLoginAtom.reportRead(); 18 | return super.userLogin; 19 | } 20 | 21 | @override 22 | set userLogin(bool value) { 23 | _$userLoginAtom.reportWrite(value, super.userLogin, () { 24 | super.userLogin = value; 25 | }); 26 | } 27 | 28 | late final _$userFaceAtom = 29 | Atom(name: '_MyController.userFace', context: context); 30 | 31 | @override 32 | String get userFace { 33 | _$userFaceAtom.reportRead(); 34 | return super.userFace; 35 | } 36 | 37 | @override 38 | set userFace(String value) { 39 | _$userFaceAtom.reportWrite(value, super.userFace, () { 40 | super.userFace = value; 41 | }); 42 | } 43 | 44 | late final _$unameAtom = Atom(name: '_MyController.uname', context: context); 45 | 46 | @override 47 | String get uname { 48 | _$unameAtom.reportRead(); 49 | return super.uname; 50 | } 51 | 52 | @override 53 | set uname(String value) { 54 | _$unameAtom.reportWrite(value, super.uname, () { 55 | super.uname = value; 56 | }); 57 | } 58 | 59 | late final _$currentLevelAtom = 60 | Atom(name: '_MyController.currentLevel', context: context); 61 | 62 | @override 63 | int get currentLevel { 64 | _$currentLevelAtom.reportRead(); 65 | return super.currentLevel; 66 | } 67 | 68 | @override 69 | set currentLevel(int value) { 70 | _$currentLevelAtom.reportWrite(value, super.currentLevel, () { 71 | super.currentLevel = value; 72 | }); 73 | } 74 | 75 | @override 76 | String toString() { 77 | return ''' 78 | userLogin: ${userLogin}, 79 | userFace: ${userFace}, 80 | uname: ${uname}, 81 | currentLevel: ${currentLevel} 82 | '''; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/pages/my/my_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/pages/my/my_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | // import 'package:bilineo/pages/webview/webview_controller.dart'; 4 | // import 'package:bilineo/pages/my/my_controller.dart'; 5 | 6 | class MyModule extends Module { 7 | @override 8 | void binds(i) { 9 | // i.addSingleton(WebviewController.new); 10 | // i.addSingleton(MyController.new); 11 | } 12 | 13 | @override 14 | void routes(r) { 15 | r.child("/", child: (_) => const MyPage()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/pages/player/play_quality.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | enum VideoQuality { 4 | speed240, 5 | flunt360, 6 | clear480, 7 | high720, 8 | high72060, 9 | high1080, 10 | high1080plus, 11 | high108060, 12 | super4K, 13 | hdr, 14 | dolbyVision, 15 | super8k 16 | } 17 | 18 | extension VideoQualityCode on VideoQuality { 19 | static final List _codeList = [ 20 | 6, 21 | 16, 22 | 32, 23 | 64, 24 | 74, 25 | 80, 26 | 112, 27 | 116, 28 | 120, 29 | 125, 30 | 126, 31 | 127, 32 | ]; 33 | int get code => _codeList[index]; 34 | 35 | static VideoQuality? fromCode(int code) { 36 | final index = _codeList.indexOf(code); 37 | if (index != -1) { 38 | return VideoQuality.values[index]; 39 | } 40 | return null; 41 | } 42 | } 43 | 44 | extension VideoQualityDesc on VideoQuality { 45 | static final List _descList = [ 46 | '240P 极速', 47 | '360P 流畅', 48 | '480P 清晰', 49 | '720P 高清', 50 | '720P60 高帧率', 51 | '1080P 高清', 52 | '1080P+ 高码率', 53 | '1080P60 高帧率', 54 | '4K 超清', 55 | 'HDR 真彩色', 56 | '杜比视界', 57 | '8K 超高清' 58 | ]; 59 | get description => _descList[index]; 60 | } 61 | 62 | /// 63 | enum AudioQuality { k64, k132, k192, dolby, hiRes } 64 | 65 | extension AudioQualityCode on AudioQuality { 66 | static final List _codeList = [ 67 | 30216, 68 | 30232, 69 | 30280, 70 | 30250, 71 | 30251, 72 | ]; 73 | int get code => _codeList[index]; 74 | 75 | static AudioQuality? fromCode(int code) { 76 | final index = _codeList.indexOf(code); 77 | if (index != -1) { 78 | return AudioQuality.values[index]; 79 | } 80 | return null; 81 | } 82 | } 83 | 84 | extension AudioQualityDesc on AudioQuality { 85 | static final List _descList = [ 86 | '64K', 87 | '132K', 88 | '192K', 89 | '杜比全景声', 90 | 'Hi-Res无损', 91 | ]; 92 | get description => _descList[index]; 93 | } 94 | 95 | enum VideoDecodeFormats { 96 | DVH1, 97 | AV1, 98 | HEVC, 99 | AVC, 100 | } 101 | 102 | extension VideoDecodeFormatsDesc on VideoDecodeFormats { 103 | static final List _descList = ['DVH1', 'AV1', 'HEVC', 'AVC']; 104 | get description => _descList[index]; 105 | } 106 | 107 | extension VideoDecodeFormatsCode on VideoDecodeFormats { 108 | static final List _codeList = ['dvh1', 'av01', 'hev1', 'avc1']; 109 | get code => _codeList[index]; 110 | 111 | static VideoDecodeFormats? fromCode(String code) { 112 | final index = _codeList.indexOf(code); 113 | if (index != -1) { 114 | return VideoDecodeFormats.values[index]; 115 | } 116 | return null; 117 | } 118 | 119 | static VideoDecodeFormats? fromString(String val) { 120 | var result = VideoDecodeFormats.values.first; 121 | for (var i in _codeList) { 122 | if (val.startsWith(i)) { 123 | result = VideoDecodeFormats.values[_codeList.indexOf(i)]; 124 | break; 125 | } 126 | } 127 | return result; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/pages/player/player_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | /// The way in which the video was originally loaded. 4 | /// 5 | /// This has nothing to do with the video's file type. It's just the place 6 | /// from which the video is fetched from. 7 | enum DataSourceType { 8 | /// The video was included in the app's asset files. 9 | asset, 10 | 11 | /// The video was downloaded from the internet. 12 | network, 13 | 14 | /// The video was loaded off of the local filesystem. 15 | file, 16 | 17 | /// The video is available via contentUri. Android only. 18 | contentUri, 19 | } 20 | 21 | class DataSource { 22 | File? file; 23 | String? videoSource; 24 | String? audioSource; 25 | String? subFiles; 26 | DataSourceType type; 27 | Map? httpHeaders; // for headers 28 | DataSource({ 29 | this.file, 30 | this.videoSource, 31 | this.audioSource, 32 | this.subFiles, 33 | required this.type, 34 | this.httpHeaders, 35 | }) : assert((type == DataSourceType.file && file != null) || 36 | videoSource != null); 37 | 38 | DataSource copyWith({ 39 | File? file, 40 | String? videoSource, 41 | String? audioSource, 42 | String? subFiles, 43 | DataSourceType? type, 44 | Map? httpHeaders, 45 | }) { 46 | return DataSource( 47 | file: file ?? this.file, 48 | videoSource: videoSource ?? this.videoSource, 49 | audioSource: audioSource ?? this.audioSource, 50 | subFiles: subFiles ?? this.subFiles, 51 | type: type ?? this.type, 52 | httpHeaders: httpHeaders ?? this.httpHeaders, 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/pages/player/player_item.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:bilineo/pages/player/player_controller.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_mobx/flutter_mobx.dart'; 6 | import 'package:media_kit/media_kit.dart'; 7 | import 'package:media_kit_video/media_kit_video.dart'; 8 | import 'package:flutter_modular/flutter_modular.dart'; 9 | import 'package:bilineo/pages/video/video_controller.dart' as videoPage; 10 | 11 | class PlayerItem extends StatefulWidget { 12 | const PlayerItem({super.key}); 13 | 14 | @override 15 | State createState() => _PlayerItemState(); 16 | } 17 | 18 | class _PlayerItemState extends State { 19 | final PlayerController playerController = Modular.get(); 20 | final videoPage.VideoController videoPageController = Modular.get(); 21 | @override 22 | void initState() { 23 | super.initState(); 24 | // debugPrint('在小部件中初始化'); 25 | // playerController.init; 26 | } 27 | 28 | @override 29 | void dispose() { 30 | //player.dispose(); 31 | super.dispose(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | debugPrint('播放器获取到的bv是 ${playerController.bvid}'); 37 | return Column( 38 | children: [ 39 | // const Text('Video Player Test'), 40 | Observer(builder: (context) { 41 | return SizedBox( 42 | width: Platform.isWindows || Platform.isLinux 43 | ? MediaQuery.of(context).size.width 44 | : ((!videoPageController.androidFullscreen) ? MediaQuery.of(context).size.width : (MediaQuery.of(context).size.height * 16.0 / 9.0)), 45 | height: Platform.isWindows || Platform.isLinux 46 | ? (MediaQuery.of(context).size.width * 9.0 / (16.0)) 47 | : ((!videoPageController.androidFullscreen) ? MediaQuery.of(context).size.width * 9.0 / 16.0 : MediaQuery.of(context).size.height), 48 | child: playerController.dataStatus == 'loaded' 49 | ? Video( 50 | controller: playerController.videoController, 51 | subtitleViewConfiguration: SubtitleViewConfiguration( 52 | style: TextStyle( 53 | color: Colors.pink, // 深粉色字体 54 | fontSize: 48.0, // 较大的字号 55 | background: Paint()..color = Colors.transparent, // 背景透明 56 | decoration: TextDecoration.none, // 无下划线 57 | fontWeight: FontWeight.bold, // 字体加粗 58 | shadows: const [ 59 | // 显眼的包边 60 | Shadow( 61 | offset: Offset(1.0, 1.0), 62 | blurRadius: 3.0, 63 | color: Color.fromARGB(255, 255, 255, 255), 64 | ), 65 | Shadow( 66 | offset: Offset(-1.0, -1.0), 67 | blurRadius: 3.0, 68 | color: Color.fromARGB(125, 255, 255, 255), 69 | ), 70 | ], 71 | ), 72 | textAlign: TextAlign.center, 73 | padding: const EdgeInsets.all(24.0), 74 | ), 75 | ) 76 | : SizedBox( 77 | child: Center( 78 | child: CircularProgressIndicator(), 79 | ), 80 | height: Platform.isWindows || Platform.isLinux 81 | ? MediaQuery.of(context).size.width * 9.0 / 32.0 82 | : MediaQuery.of(context).size.width * 9.0 / 16.0, 83 | width: Platform.isWindows || Platform.isLinux 84 | ? MediaQuery.of(context).size.width / 2 85 | : MediaQuery.of(context).size.width, 86 | ), 87 | ); 88 | }), 89 | ], 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/pages/popular/popular_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_modular/flutter_modular.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:bilineo/request/bangumi.dart'; 4 | import 'package:bilineo/bean/bangumi/bangumi_list.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 | ObservableList bangumiList = ObservableList.of([BangumiListItemModel()]); 16 | 17 | List _items = []; 18 | List get items => _items; 19 | List get listValue => bangumiList.toList(); 20 | 21 | int _currentPage = 1; 22 | double scrollOffset = 0.0; 23 | bool isLoadingMore = true; 24 | 25 | @action 26 | Future queryBangumiListFeed({type = 'init'}) async { 27 | if (type == 'init') { 28 | _currentPage = 1; 29 | } 30 | var result = await BangumiHttp.bangumiList(page: _currentPage); 31 | if (result['status']) { 32 | if (type == 'init') { 33 | bangumiList.clear(); 34 | } 35 | bangumiList.addAll(result['data'].list); 36 | _currentPage += 1; 37 | } else {} 38 | isLoadingMore = false; 39 | return result; 40 | } 41 | 42 | Future onLoad() async { 43 | debugPrint('Popular正在加载更多内容'); 44 | queryBangumiListFeed(type: 'onLoad'); 45 | } 46 | } -------------------------------------------------------------------------------- /lib/pages/popular/popular_controller.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'popular_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 _$PopularController on _PopularController, Store { 12 | late final _$bangumiListAtom = 13 | Atom(name: '_PopularController.bangumiList', context: context); 14 | 15 | @override 16 | ObservableList get bangumiList { 17 | _$bangumiListAtom.reportRead(); 18 | return super.bangumiList; 19 | } 20 | 21 | @override 22 | set bangumiList(ObservableList value) { 23 | _$bangumiListAtom.reportWrite(value, super.bangumiList, () { 24 | super.bangumiList = value; 25 | }); 26 | } 27 | 28 | late final _$queryBangumiListFeedAsyncAction = 29 | AsyncAction('_PopularController.queryBangumiListFeed', context: context); 30 | 31 | @override 32 | Future queryBangumiListFeed({dynamic type = 'init'}) { 33 | return _$queryBangumiListFeedAsyncAction 34 | .run(() => super.queryBangumiListFeed(type: type)); 35 | } 36 | 37 | @override 38 | String toString() { 39 | return ''' 40 | bangumiList: ${bangumiList} 41 | '''; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/pages/popular/popular_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/pages/popular/popular_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | import 'package:bilineo/pages/popular/popular_controller.dart'; 4 | 5 | class PopularModule extends Module { 6 | @override 7 | void binds(i) { 8 | //i.addSingleton(PopularController.new); 9 | } 10 | 11 | @override 12 | void routes(r) { 13 | r.child("/", child: (_) => const PopularPage()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/pages/rating/rating_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/pages/rating/rating_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | 4 | class RatingModule extends Module { 5 | @override 6 | void routes(r) { 7 | r.child("/", child: (_) => const RatingPage()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/pages/rating/rating_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | 4 | class RatingPage extends StatefulWidget { 5 | const RatingPage({super.key}); 6 | 7 | @override 8 | State createState() => _RatingPageState(); 9 | } 10 | 11 | class _RatingPageState extends State { 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | // This method is rerun every time setState is called, for instance as done 16 | // by the _incrementCounter method above. 17 | // 18 | // The Flutter framework has been optimized to make rerunning build methods 19 | // fast, so that you can just rebuild anything that needs updating rather 20 | // than having to individually change instances of widgets. 21 | return Scaffold( 22 | appBar: AppBar(title: const Text('BiliNeo Rating Test Page')), 23 | body: Center( 24 | child: TextButton( 25 | onPressed: () { 26 | 27 | }, 28 | child: const Text('测试'), 29 | ), 30 | ), 31 | ); 32 | } 33 | } -------------------------------------------------------------------------------- /lib/pages/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/pages/my/my_module.dart'; 2 | import 'package:bilineo/pages/rating/rating_module.dart'; 3 | import 'package:bilineo/pages/popular/popular_module.dart'; 4 | import 'package:bilineo/pages/video/video_module.dart'; 5 | import 'package:flutter_modular/flutter_modular.dart'; 6 | import 'package:bilineo/pages/webview/webview_module.dart'; 7 | import 'package:bilineo/pages/webview_desktop/webview_desktop_module.dart'; 8 | import 'package:bilineo/pages/search/search_module.dart'; 9 | import 'package:bilineo/pages/search_result/results_module.dart'; 10 | 11 | class MenuRouteItem { 12 | final String path; 13 | final Module module; 14 | 15 | const MenuRouteItem({ 16 | required this.path, 17 | required this.module, 18 | }); 19 | } 20 | 21 | class MenuRoute { 22 | final List menuList; 23 | 24 | const MenuRoute(this.menuList); 25 | 26 | int get size => menuList.length; 27 | 28 | List get moduleList { 29 | return menuList.map((e) => e.module).toList(); 30 | } 31 | 32 | List get routes { 33 | return menuList.map((e) => ModuleRoute(e.path, module: e.module)).toList(); 34 | } 35 | 36 | getPath(int index) { 37 | return menuList[index].path; 38 | } 39 | } 40 | 41 | final MenuRoute menu = MenuRoute([ 42 | MenuRouteItem( 43 | path: "/popular", 44 | module: PopularModule(), 45 | ), 46 | MenuRouteItem( 47 | path: "/rating", 48 | module: RatingModule(), 49 | ), 50 | MenuRouteItem( 51 | path: "/my", 52 | module: MyModule(), 53 | ), 54 | MenuRouteItem( 55 | path: "/video", 56 | module: VideoModule(), 57 | ), 58 | MenuRouteItem( 59 | path: '/webview', 60 | module: WebviewMoudle(), 61 | ), 62 | MenuRouteItem( 63 | path: '/webviewdesktop', 64 | module: WebviewDesktopMoudle(), 65 | ), 66 | MenuRouteItem( 67 | path: '/search', 68 | module: SearchModule(), 69 | ), 70 | MenuRouteItem( 71 | path: '/searchresult', 72 | module: SearchResultModule(), 73 | ), 74 | ]); 75 | -------------------------------------------------------------------------------- /lib/pages/search/search_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/pages/search/search_suggest.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | import 'package:mobx/mobx.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:bilineo/pages/search/search_controller.dart'; 6 | import 'package:bilineo/request/search.dart'; 7 | import 'package:bilineo/pages/search_result/results_controller.dart'; 8 | 9 | part 'search_controller.g.dart'; 10 | 11 | class MySearchController = _MySearchController with _$MySearchController; 12 | 13 | abstract class _MySearchController with Store { 14 | @observable 15 | String searchKeyWord = ''; 16 | 17 | @observable 18 | List searchSuggestList = [SearchSuggestItem()]; 19 | 20 | @observable 21 | TextEditingController controller = TextEditingController(); 22 | 23 | void onChange(value) { 24 | searchKeyWord = value; 25 | debugPrint('搜索框内容变动,当前内容 $value'); 26 | if (value == '') { 27 | searchSuggestList = []; 28 | return; 29 | } 30 | querySearchSuggest(value); 31 | } 32 | 33 | onSelect(word) { 34 | searchKeyWord = word; 35 | controller.text = word; 36 | submit(); 37 | } 38 | 39 | Future querySearchSuggest(String value) async { 40 | var result = await SearchHttp.searchSuggest(term: value); 41 | if (result['status']) { 42 | if (result['data'] is SearchSuggestModel) { 43 | debugPrint('来自服务器的搜索建议响应 ${result['data'].tag}'); 44 | searchSuggestList = result['data'].tag; 45 | // return searchSuggestList; 46 | } 47 | } 48 | } 49 | 50 | void onClickKeyword(String keyword) { 51 | searchKeyWord = keyword; 52 | controller.text = keyword; 53 | // 移动光标 54 | controller.selection = TextSelection.fromPosition( 55 | TextPosition(offset: controller.value.text.length), 56 | ); 57 | submit(); 58 | } 59 | 60 | void submit() { 61 | // ignore: unrelated_type_equality_checks 62 | if (searchKeyWord == '') { 63 | return; 64 | } 65 | debugPrint('Todo 搜索结果 $searchKeyWord'); 66 | // final MySearchController mySearchController = 67 | // Modular.get(); 68 | // final SearchResultController searchResultController = 69 | // Modular.get(); 70 | // searchResultController.searchKeyWord = mySearchController.searchKeyWord; 71 | // searchResultController.onSearch(); 72 | Modular.to.navigate('/tab/searchresult/'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /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 _$MySearchController on _MySearchController, Store { 12 | late final _$searchKeyWordAtom = 13 | Atom(name: '_MySearchController.searchKeyWord', context: context); 14 | 15 | @override 16 | String get searchKeyWord { 17 | _$searchKeyWordAtom.reportRead(); 18 | return super.searchKeyWord; 19 | } 20 | 21 | @override 22 | set searchKeyWord(String value) { 23 | _$searchKeyWordAtom.reportWrite(value, super.searchKeyWord, () { 24 | super.searchKeyWord = value; 25 | }); 26 | } 27 | 28 | late final _$searchSuggestListAtom = 29 | Atom(name: '_MySearchController.searchSuggestList', context: context); 30 | 31 | @override 32 | List get searchSuggestList { 33 | _$searchSuggestListAtom.reportRead(); 34 | return super.searchSuggestList; 35 | } 36 | 37 | @override 38 | set searchSuggestList(List value) { 39 | _$searchSuggestListAtom.reportWrite(value, super.searchSuggestList, () { 40 | super.searchSuggestList = value; 41 | }); 42 | } 43 | 44 | late final _$controllerAtom = 45 | Atom(name: '_MySearchController.controller', context: context); 46 | 47 | @override 48 | TextEditingController get controller { 49 | _$controllerAtom.reportRead(); 50 | return super.controller; 51 | } 52 | 53 | @override 54 | set controller(TextEditingController value) { 55 | _$controllerAtom.reportWrite(value, super.controller, () { 56 | super.controller = value; 57 | }); 58 | } 59 | 60 | @override 61 | String toString() { 62 | return ''' 63 | searchKeyWord: ${searchKeyWord}, 64 | searchSuggestList: ${searchSuggestList}, 65 | controller: ${controller} 66 | '''; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/pages/search/search_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/pages/search/search_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | 4 | class SearchModule extends Module { 5 | @override 6 | void routes(r) { 7 | r.child("/", child: (_) => const SearchPage()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/pages/search/search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mobx/flutter_mobx.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:bilineo/pages/search/search_controller.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:bilineo/pages/menu/menu.dart'; 7 | 8 | class SearchPage extends StatefulWidget { 9 | const SearchPage({super.key}); 10 | 11 | @override 12 | State createState() => _SearchPageState(); 13 | } 14 | 15 | class _SearchPageState extends State { 16 | bool isDark = false; 17 | final MySearchController mySearchController = 18 | Modular.get(); 19 | late Iterable _lastOptions = []; 20 | FocusNode barFocusNode = FocusNode(); 21 | FocusNode suggestFocusNode = FocusNode(); 22 | 23 | void onBackPressed(BuildContext context) { 24 | final navigationBarState = Provider.of(context, listen: false); 25 | navigationBarState.showNavigate(); 26 | navigationBarState.updateSelectedIndex(0); 27 | Modular.to.navigate('/tab/popular/'); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return PopScope( 33 | canPop: false, 34 | onPopInvoked: (bool didPop) async { 35 | onBackPressed(context); 36 | }, 37 | child: Scaffold( 38 | appBar: AppBar(title: const Text('')), 39 | body: Observer(builder: (context) { 40 | String _searchingWithQuery = mySearchController.searchKeyWord; 41 | return Center( 42 | child: SearchAnchor( 43 | builder: (BuildContext context, SearchController controller) { 44 | return SearchBar( 45 | focusNode: barFocusNode, 46 | controller: controller, 47 | padding: const MaterialStatePropertyAll( 48 | EdgeInsets.symmetric(horizontal: 16.0)), 49 | onTap: () { 50 | debugPrint('搜索框点击事件'); 51 | // Panic, maybe due to Focus 52 | controller.openView(); 53 | }, 54 | // onChanged: (value) { 55 | // debugPrint('搜索建议获得焦点,当前值为 $value'); 56 | // mySearchController.querySearchSuggest(controller.text); 57 | // // barFocusNode.requestFocus(); 58 | // // suggestFocusNode.requestFocus(); 59 | // }, 60 | onSubmitted: (value) { 61 | debugPrint('提交 ${controller.text}'); 62 | mySearchController.onSelect(controller.text); 63 | }, 64 | leading: const Icon(Icons.search), 65 | ); 66 | }, suggestionsBuilder: 67 | (BuildContext context, SearchController controller) async { 68 | _searchingWithQuery = controller.text; 69 | if (_searchingWithQuery == '') { 70 | debugPrint('搜索框内容为空'); 71 | return []; 72 | } 73 | debugPrint('提交到搜索建议API的搜索内容为 $_searchingWithQuery'); 74 | await mySearchController.querySearchSuggest(_searchingWithQuery); 75 | 76 | if (_searchingWithQuery != controller.text) { 77 | return _lastOptions; 78 | } 79 | 80 | _lastOptions = List.generate( 81 | mySearchController.searchSuggestList.length, (int index) { 82 | // debugPrint('搜索框获得焦点'); 83 | // barFocusNode.requestFocus(); 84 | return ListTile( 85 | focusNode: suggestFocusNode, 86 | title: mySearchController.searchSuggestList[index].textRich, 87 | onTap: () { 88 | controller.text = 89 | mySearchController.searchSuggestList[index].term ?? ''; 90 | mySearchController.onSelect(controller.text); 91 | }, 92 | ); 93 | }); 94 | 95 | return _lastOptions; 96 | }), 97 | ); 98 | }), 99 | ), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/pages/search/search_suggest.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SearchSuggestModel { 4 | SearchSuggestModel({ 5 | this.tag, 6 | this.term, 7 | }); 8 | 9 | List? tag; 10 | String? term; 11 | 12 | SearchSuggestModel.fromJson(Map json) { 13 | tag = json['tag'] 14 | .map( 15 | (e) => SearchSuggestItem.fromJson(e, json['term'])) 16 | .toList(); 17 | } 18 | } 19 | 20 | class SearchSuggestItem { 21 | SearchSuggestItem({ 22 | this.value, 23 | this.term, 24 | this.spid, 25 | this.textRich, 26 | }); 27 | 28 | String? value; 29 | String? term; 30 | int? spid; 31 | Widget? textRich; 32 | 33 | SearchSuggestItem.fromJson(Map json, String inputTerm) { 34 | value = json['value']; 35 | term = json['term']; 36 | textRich = highlightText(json['name']); 37 | } 38 | } 39 | 40 | Widget highlightText(String str) { 41 | // 创建正则表达式,匹配 ... 格式的文本 42 | RegExp regex = RegExp(r'(.*?)<\/em>'); 43 | 44 | // 用于存储每个匹配项的列表 45 | List children = []; 46 | 47 | // 获取所有匹配项 48 | Iterable matches = regex.allMatches(str); 49 | 50 | // 当前索引位置 51 | int currentIndex = 0; 52 | 53 | // 遍历每个匹配项 54 | for (var match in matches) { 55 | // 获取当前匹配项之前的普通文本部分 56 | String normalText = str.substring(currentIndex, match.start); 57 | 58 | // 获取需要高亮显示的文本部分 59 | String highlightedText = match.group(1)!; 60 | 61 | // 如果普通文本部分不为空,则将其添加到 children 列表中 62 | if (normalText.isNotEmpty) { 63 | children.add(TextSpan( 64 | text: normalText, 65 | )); 66 | } 67 | 68 | // 将需要高亮显示的文本部分添加到 children 列表中,并设置相应样式 69 | children.add(TextSpan( 70 | text: highlightedText, 71 | style: TextStyle( 72 | fontWeight: FontWeight.bold, 73 | ), 74 | )); 75 | 76 | // 更新当前索引位置 77 | currentIndex = match.end; 78 | } 79 | 80 | // 如果当前索引位置小于文本长度,表示还有剩余的普通文本部分 81 | if (currentIndex < str.length) { 82 | String remainingText = str.substring(currentIndex); 83 | 84 | // 将剩余的普通文本部分添加到 children 列表中 85 | children.add(TextSpan( 86 | text: remainingText, 87 | )); 88 | } 89 | 90 | // 使用 Text.rich 创建包含高亮显示的富文本小部件,并返回 91 | return Text.rich(TextSpan(children: children)); 92 | } 93 | -------------------------------------------------------------------------------- /lib/pages/search/search_type.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | enum SearchType { 3 | // 视频:video 4 | video, 5 | // 番剧:media_bangumi, 6 | media_bangumi, 7 | // 影视:media_ft 8 | // media_ft, 9 | // 直播间及主播:live 10 | // live, 11 | // 直播间:live_room 12 | live_room, 13 | // 主播:live_user 14 | // live_user, 15 | // 话题:topic 16 | // topic, 17 | // 用户:bili_user 18 | bili_user, 19 | // 专栏:article 20 | article, 21 | // 相簿:photo 22 | // photo 23 | } 24 | 25 | extension SearchTypeExtension on SearchType { 26 | String get type => 27 | ['video', 'media_bangumi', 'live_room', 'bili_user', 'article'][index]; 28 | String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index]; 29 | } 30 | 31 | // 搜索类型为视频、专栏及相簿时 32 | enum ArchiveFilterType { 33 | totalrank, 34 | click, 35 | pubdate, 36 | dm, 37 | stow, 38 | scores, 39 | // 专栏 40 | // attention, 41 | } 42 | 43 | extension ArchiveFilterTypeExtension on ArchiveFilterType { 44 | String get description => 45 | ['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index]; 46 | } 47 | -------------------------------------------------------------------------------- /lib/pages/search_result/results_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:mobx/mobx.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:bilineo/request/search.dart'; 4 | import 'package:bilineo/pages/search/search_type.dart'; 5 | import 'package:bilineo/utils/utils.dart'; 6 | import 'package:bilineo/utils/id.dart'; 7 | import 'package:flutter_modular/flutter_modular.dart'; 8 | import 'package:bilineo/pages/video/video_controller.dart'; 9 | 10 | part 'results_controller.g.dart'; 11 | 12 | class SearchResultController = _SearchResultController 13 | with _$SearchResultController; 14 | 15 | abstract class _SearchResultController with Store { 16 | ScrollController scrollController = ScrollController(); 17 | final VideoController videoController = Modular.get(); 18 | 19 | @observable 20 | String searchKeyWord = ''; 21 | 22 | @observable 23 | int page = 1; 24 | 25 | @observable 26 | List resultList = []; 27 | 28 | SearchType searchType = SearchType.media_bangumi; 29 | 30 | Future onSearch({type = 'init'}) async { 31 | if (type == 'init') { 32 | page = 1; 33 | } 34 | var result = await SearchHttp.searchByType( 35 | searchType: searchType, 36 | keyword: searchKeyWord, 37 | page: page, 38 | order: null, 39 | duration: null); 40 | if (result['status']) { 41 | if (type == 'onRefresh' || type == 'init') { 42 | resultList = result['data'].list; 43 | } else { 44 | resultList.addAll(result['data'].list); 45 | } 46 | page++; 47 | onPushDetail(searchKeyWord, resultList); 48 | } 49 | return result; 50 | } 51 | 52 | Future onRefresh() async { 53 | page = 1; 54 | await onSearch(type: 'onRefresh'); 55 | } 56 | 57 | void onPushDetail(keyword, resultList) async { 58 | // 匹配输入内容,如果是AV、BV号且有结果 直接跳转详情页 59 | Map matchRes = IdUtils.matchAvorBv(input: keyword); 60 | List matchKeys = matchRes.keys.toList(); 61 | String? bvid; 62 | try { 63 | bvid = resultList.first.bvid; 64 | } catch (_) { 65 | bvid = null; 66 | } 67 | // keyword 可能输入纯数字 68 | int? aid; 69 | try { 70 | aid = resultList.first.aid; 71 | } catch (_) { 72 | aid = null; 73 | } 74 | if (matchKeys.isNotEmpty && searchType == SearchType.video || 75 | aid.toString() == keyword) { 76 | String heroTag = Utils.makeHeroTag(bvid); 77 | int cid = await SearchHttp.ab2c(aid: aid, bvid: bvid); 78 | if (matchKeys.isNotEmpty && 79 | matchKeys.first == 'BV' && 80 | matchRes[matchKeys.first] == bvid || 81 | matchKeys.isNotEmpty && 82 | matchKeys.first == 'AV' && 83 | matchRes[matchKeys.first] == aid || 84 | aid.toString() == keyword) { 85 | // Get.toNamed( 86 | // '/video?bvid=$bvid&cid=$cid', 87 | // arguments: {'videoItem': resultList.first, 'heroTag': heroTag}, 88 | // ); 89 | videoController.bvid = bvid ?? ''; 90 | videoController.cid = cid; 91 | videoController.heroTag = heroTag; 92 | videoController.videoType = SearchType.media_bangumi; 93 | // videoController.bangumiItem = res['data']; 94 | Modular.to.navigate('/tab/video/'); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/pages/search_result/results_controller.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'results_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 _$SearchResultController on _SearchResultController, Store { 12 | late final _$searchKeyWordAtom = 13 | Atom(name: '_SearchResultController.searchKeyWord', context: context); 14 | 15 | @override 16 | String get searchKeyWord { 17 | _$searchKeyWordAtom.reportRead(); 18 | return super.searchKeyWord; 19 | } 20 | 21 | @override 22 | set searchKeyWord(String value) { 23 | _$searchKeyWordAtom.reportWrite(value, super.searchKeyWord, () { 24 | super.searchKeyWord = value; 25 | }); 26 | } 27 | 28 | late final _$pageAtom = 29 | Atom(name: '_SearchResultController.page', context: context); 30 | 31 | @override 32 | int get page { 33 | _$pageAtom.reportRead(); 34 | return super.page; 35 | } 36 | 37 | @override 38 | set page(int value) { 39 | _$pageAtom.reportWrite(value, super.page, () { 40 | super.page = value; 41 | }); 42 | } 43 | 44 | late final _$resultListAtom = 45 | Atom(name: '_SearchResultController.resultList', context: context); 46 | 47 | @override 48 | List get resultList { 49 | _$resultListAtom.reportRead(); 50 | return super.resultList; 51 | } 52 | 53 | @override 54 | set resultList(List value) { 55 | _$resultListAtom.reportWrite(value, super.resultList, () { 56 | super.resultList = value; 57 | }); 58 | } 59 | 60 | @override 61 | String toString() { 62 | return ''' 63 | searchKeyWord: ${searchKeyWord}, 64 | page: ${page}, 65 | resultList: ${resultList} 66 | '''; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/pages/search_result/results_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/pages/search_result/results_page.dart'; 2 | import 'package:flutter_modular/flutter_modular.dart'; 3 | 4 | class SearchResultModule extends Module { 5 | @override 6 | void routes(r) { 7 | r.child("/", child: (_) => const SearchResultPage()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/pages/search_result/results_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:bilineo/pages/search/search_type.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:bilineo/pages/search/search_controller.dart'; 5 | import 'package:bilineo/pages/search_result/results_item.dart'; 6 | import 'package:bilineo/pages/search_result/results_controller.dart'; 7 | import 'package:bilineo/pages/menu/menu.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class SearchResultPage extends StatefulWidget { 11 | const SearchResultPage({super.key}); 12 | 13 | @override 14 | State createState() => _SearchResultPageState(); 15 | } 16 | 17 | class _SearchResultPageState extends State 18 | with TickerProviderStateMixin { 19 | final MySearchController mySearchController = 20 | Modular.get(); 21 | 22 | final SearchResultController searchResultController = Modular.get(); 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | // searchResultController.resultList = []; 32 | // searchResultController.searchKeyWord = ''; 33 | // debugPrint('搜索缓存已经清空'); 34 | super.dispose(); 35 | } 36 | 37 | void onBackPressed(BuildContext context) { 38 | final navigationBarState = Provider.of(context, listen: false); 39 | navigationBarState.showNavigate(); 40 | navigationBarState.updateSelectedIndex(1); 41 | Modular.to.navigate('/tab/search/'); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return PopScope( 47 | canPop: false, 48 | onPopInvoked: (bool didPop) async { 49 | onBackPressed(context); 50 | }, 51 | child: Scaffold( 52 | appBar: AppBar( 53 | title: Text(mySearchController.searchKeyWord), 54 | leading: IconButton( 55 | icon: const Icon(Icons.arrow_back), 56 | onPressed: () { 57 | Modular.to.navigate('/tab/search/'); 58 | //Modular.to.pop(); 59 | }, 60 | ), 61 | ), 62 | body: Column( 63 | children: [ 64 | Expanded( 65 | child: SearchPanel( 66 | keyword: mySearchController.searchKeyWord, 67 | searchType: SearchType.media_bangumi, 68 | tag: DateTime.now().millisecondsSinceEpoch.toString(), 69 | )), 70 | ], 71 | ), 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/pages/video/video_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:mobx/mobx.dart'; 3 | import 'package:bilineo/pages/search/search_type.dart'; 4 | import 'package:bilineo/pages/player/player_controller.dart'; 5 | import 'package:flutter_modular/flutter_modular.dart'; 6 | import 'package:bilineo/bean/bangumi/bangumi_info.dart'; 7 | 8 | part 'video_controller.g.dart'; 9 | 10 | class VideoController = _VideoController with _$VideoController; 11 | 12 | abstract class _VideoController with Store { 13 | @observable 14 | late String bvid; 15 | 16 | @observable 17 | late int cid; 18 | 19 | @observable 20 | late String pic; 21 | 22 | @observable 23 | late String heroTag; 24 | 25 | @observable 26 | late dynamic videoType; 27 | 28 | @observable 29 | late BangumiInfoModel? bangumiItem; 30 | 31 | String from = '/tab/popular/'; 32 | 33 | // 界面管理 34 | @observable 35 | bool showPositioned = false; 36 | @observable 37 | bool showPosition = false; 38 | @observable 39 | bool showBrightness = false; 40 | @observable 41 | bool showVolume = false; 42 | 43 | @observable 44 | bool playing = false; 45 | @observable 46 | bool isBuffering = true; 47 | @observable 48 | Duration currentPosition = Duration.zero; 49 | @observable 50 | Duration buffer = Duration.zero; 51 | @observable 52 | Duration duration = Duration.zero; 53 | 54 | // 视频音量/亮度 55 | @observable 56 | double volume = 0; 57 | @observable 58 | double brightness = 0; 59 | 60 | // 播放器倍速 61 | @observable 62 | double playerSpeed = 1.0; 63 | 64 | // 弹幕开关 65 | @observable 66 | bool danmakuOn = false; 67 | 68 | // 安卓全屏状态 69 | @observable 70 | bool androidFullscreen = false; 71 | 72 | // final PlayerController playerController = Modular.get(); 73 | 74 | Future init(PlayerController playerController) async { 75 | playerController.bvid = bvid; 76 | playerController.cid = cid; 77 | playerController.pic = pic; 78 | playerController.heroTag = heroTag; 79 | playerController.videoType = SearchType.media_bangumi; 80 | await playerController.init(); 81 | debugPrint('PlayerContriller 初始化成功'); 82 | return 'PlayerContriller Init Success'; 83 | } 84 | 85 | // 修改分P或番剧分集 86 | Future changeSeasonOrbangu(bvidS, cidS, PlayerController playerController) async { 87 | // 重新获取视频资源 88 | bvid = bvidS; 89 | cid = cidS; 90 | playerController.bvid = bvidS; 91 | playerController.cid = cidS; 92 | await playerController.init(); 93 | debugPrint('PlayController 重新初始化成功'); 94 | } 95 | 96 | Future setPlaybackSpeed(double playerSpeed) async { 97 | final PlayerController playerController = Modular.get(); 98 | try { 99 | playerController.mediaPlayer.setRate(playerSpeed); 100 | } catch(e) { 101 | debugPrint(e.toString()); 102 | } 103 | this.playerSpeed = playerSpeed; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/pages/video/video_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/pages/video/video_page.dart'; 2 | import 'package:bilineo/pages/video/video_controller.dart'; 3 | import 'package:bilineo/pages/player/player_controller.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | 6 | class VideoModule extends Module { 7 | @override 8 | void binds(i) { 9 | //i.addSingleton(VideoController.new); 10 | i.addSingleton(PlayerController.new); 11 | } 12 | 13 | @override 14 | void routes(r) { 15 | r.child("/", child: (_) => const VideoPage()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/pages/webview/webview_controller.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'webview_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 _$WebviewController on _WebviewController, Store { 12 | late final _$typeAtom = 13 | Atom(name: '_WebviewController.type', context: context); 14 | 15 | @override 16 | String get type { 17 | _$typeAtom.reportRead(); 18 | return super.type; 19 | } 20 | 21 | @override 22 | set type(String value) { 23 | _$typeAtom.reportWrite(value, super.type, () { 24 | super.type = value; 25 | }); 26 | } 27 | 28 | late final _$loadProgressAtom = 29 | Atom(name: '_WebviewController.loadProgress', context: context); 30 | 31 | @override 32 | int get loadProgress { 33 | _$loadProgressAtom.reportRead(); 34 | return super.loadProgress; 35 | } 36 | 37 | @override 38 | set loadProgress(int value) { 39 | _$loadProgressAtom.reportWrite(value, super.loadProgress, () { 40 | super.loadProgress = value; 41 | }); 42 | } 43 | 44 | late final _$loadShowAtom = 45 | Atom(name: '_WebviewController.loadShow', context: context); 46 | 47 | @override 48 | bool get loadShow { 49 | _$loadShowAtom.reportRead(); 50 | return super.loadShow; 51 | } 52 | 53 | @override 54 | set loadShow(bool value) { 55 | _$loadShowAtom.reportWrite(value, super.loadShow, () { 56 | super.loadShow = value; 57 | }); 58 | } 59 | 60 | @override 61 | String toString() { 62 | return ''' 63 | type: ${type}, 64 | loadProgress: ${loadProgress}, 65 | loadShow: ${loadShow} 66 | '''; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/pages/webview/webview_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_modular/flutter_modular.dart'; 2 | import 'package:bilineo/pages/webview/webview_page.dart'; 3 | 4 | class WebviewMoudle extends Module { 5 | @override 6 | void binds(i) { 7 | // i.addSingleton(); 8 | } 9 | 10 | @override 11 | void routes(r) { 12 | r.child('/', child: (_) => const WebviewPage()); 13 | } 14 | } -------------------------------------------------------------------------------- /lib/pages/webview/webview_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mobx/flutter_mobx.dart'; 3 | import 'package:url_launcher/url_launcher.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:bilineo/pages/webview/webview_controller.dart'; 6 | import 'package:webview_flutter/webview_flutter.dart'; 7 | 8 | class WebviewPage extends StatefulWidget { 9 | const WebviewPage({super.key}); 10 | 11 | @override 12 | State createState() => _WebviewPageState(); 13 | } 14 | 15 | class _WebviewPageState extends State { 16 | final _webviewController = Modular.get(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: AppBar( 22 | centerTitle: false, 23 | titleSpacing: 0, 24 | leading: IconButton( 25 | icon: const Icon(Icons.arrow_back), 26 | onPressed: () { 27 | Modular.to.navigate('/tab/my/'); 28 | //Modular.to.pop(); 29 | }, 30 | ), 31 | title: Text( 32 | _webviewController.pageTitle, 33 | style: Theme.of(context).textTheme.titleMedium, 34 | ), 35 | actions: [ 36 | const SizedBox(width: 4), 37 | IconButton( 38 | onPressed: () { 39 | _webviewController.controller.reload(); 40 | }, 41 | icon: Icon(Icons.refresh_outlined, 42 | color: Theme.of(context).colorScheme.primary), 43 | ), 44 | IconButton( 45 | onPressed: () { 46 | launchUrl(Uri.parse(_webviewController.url)); 47 | }, 48 | icon: Icon(Icons.open_in_browser_outlined, 49 | color: Theme.of(context).colorScheme.primary), 50 | ), 51 | Observer(builder: (context) { 52 | return _webviewController.type == 'login' 53 | ? TextButton( 54 | onPressed: () => _webviewController.confirmLogin(null), 55 | child: const Text('刷新登录状态'), 56 | ) 57 | : const SizedBox(); 58 | }), 59 | const SizedBox(width: 12) 60 | ], 61 | ), 62 | body: Column( 63 | children: [ 64 | Observer(builder: (context) { 65 | return AnimatedContainer( 66 | curve: Curves.easeInOut, 67 | duration: const Duration(milliseconds: 350), 68 | height: _webviewController.loadShow ? 4 : 0, 69 | child: LinearProgressIndicator( 70 | key: ValueKey(_webviewController.loadProgress), 71 | value: _webviewController.loadProgress / 100, 72 | ), 73 | ); 74 | }), 75 | if (_webviewController.type == 'login') 76 | Container( 77 | width: double.infinity, 78 | color: Theme.of(context).colorScheme.onInverseSurface, 79 | padding: const EdgeInsets.only( 80 | left: 12, right: 12, top: 6, bottom: 6), 81 | child: const Text('登录成功未自动跳转? 请点击右上角「刷新登录状态」'), 82 | ), 83 | Expanded( 84 | child: WebViewWidget(controller: _webviewController.controller), 85 | ), 86 | ], 87 | )); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/pages/webview_desktop/webview_desktop_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_modular/flutter_modular.dart'; 2 | import 'package:bilineo/pages/webview_desktop/webview_desktop_page.dart'; 3 | 4 | class WebviewDesktopMoudle extends Module { 5 | @override 6 | void binds(i) { 7 | // i.addSingleton(); 8 | } 9 | 10 | @override 11 | void routes(r) { 12 | r.child('/', child: (_) => const WebviewDesktopPage()); 13 | } 14 | } -------------------------------------------------------------------------------- /lib/request/bangumi.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/request/request.dart'; 2 | import 'package:bilineo/request/api.dart'; 3 | import 'package:bilineo/bean/bangumi/bangumi_list.dart'; 4 | import 'package:bilineo/bean/bangumi/bangumi_info.dart'; 5 | 6 | class BangumiHttp { 7 | static Future bangumiList({int? page}) async { 8 | var res = await Request().get(Api.bangumiList, data: {'page': page}); 9 | if (res.data['code'] == 0) { 10 | return { 11 | 'status': true, 12 | 'data': BangumiListDataModel.fromJson(res.data['data']) 13 | }; 14 | } else { 15 | return { 16 | 'status': false, 17 | 'data': [], 18 | 'msg': res.data['message'], 19 | }; 20 | } 21 | } 22 | 23 | static Future> bangumiInfo( 24 | {int? seasonId, int? epId}) async { 25 | final Map data = {}; 26 | if (seasonId != null) { 27 | data['season_id'] = seasonId; 28 | } else if (epId != null) { 29 | data['ep_id'] = epId; 30 | } 31 | final dynamic res = 32 | await Request().get(Api.bangumiInfo, data: {...data}); 33 | if (res.data['code'] == 0) { 34 | return { 35 | 'status': true, 36 | 'data': BangumiInfoModel.fromJson(res.data['result']), 37 | }; 38 | } else { 39 | return { 40 | 'status': false, 41 | 'data': [], 42 | 'msg': '请求错误 🙅', 43 | }; 44 | } 45 | } 46 | 47 | static Future bangumiFollow({int? mid}) async { 48 | var res = await Request().get(Api.bangumiFollow, data: {'vmid': mid}); 49 | if (res.data['code'] == 0) { 50 | return { 51 | 'status': true, 52 | 'data': BangumiListDataModel.fromJson(res.data['data']) 53 | }; 54 | } else { 55 | return { 56 | 'status': false, 57 | 'data': [], 58 | 'msg': res.data['message'], 59 | }; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/request/constants.dart: -------------------------------------------------------------------------------- 1 | class HttpString { 2 | static const String baseUrl = 'https://www.bilibili.com'; 3 | static const String apiBaseUrl = 'https://api.bilibili.com'; 4 | static const String unlockAPIUrl = 'https://bili.meark.me'; 5 | static const String tUrl = 'https://api.vc.bilibili.com'; 6 | static const String appBaseUrl = 'https://app.bilibili.com'; 7 | static const String liveBaseUrl = 'https://api.live.bilibili.com'; 8 | static const String passBaseUrl = 'https://passport.bilibili.com'; 9 | static const List validateStatusCodes = [ 10 | 302, 11 | 304, 12 | 307, 13 | 400, 14 | 401, 15 | 403, 16 | 404, 17 | 405, 18 | 409, 19 | 412, 20 | 500, 21 | 503, 22 | 504, 23 | 509, 24 | 616, 25 | 617, 26 | 625, 27 | 626, 28 | 628, 29 | 629, 30 | 632, 31 | 643, 32 | 650, 33 | 652, 34 | 658, 35 | 662, 36 | 688, 37 | 689, 38 | 701, 39 | 799, 40 | 8888 41 | ]; 42 | } 43 | -------------------------------------------------------------------------------- /lib/request/cookie.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/request/constants.dart'; 2 | import 'package:bilineo/request/request.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:webview_cookie_manager/webview_cookie_manager.dart'; 5 | 6 | class SetCookie { 7 | // 不适用于桌面端 8 | static onSet() async { 9 | var cookies = await WebviewCookieManager().getCookies(HttpString.baseUrl); 10 | await Request.cookieManager.cookieJar 11 | .saveFromResponse(Uri.parse(HttpString.baseUrl), cookies); 12 | var cookieString = 13 | cookies.map((cookie) => '${cookie.name}=${cookie.value}').join('; '); 14 | Request.dio.options.headers['cookie'] = cookieString; 15 | 16 | // Debug 17 | debugPrint('移动端调试 当前baseCookie 为 $cookieString'); 18 | 19 | cookies = await WebviewCookieManager().getCookies(HttpString.apiBaseUrl); 20 | await Request.cookieManager.cookieJar 21 | .saveFromResponse(Uri.parse(HttpString.apiBaseUrl), cookies); 22 | await Request.cookieManager.cookieJar 23 | .saveFromResponse(Uri.parse(HttpString.unlockAPIUrl), cookies); 24 | 25 | cookies.forEach((cookie) { 26 | debugPrint('移动端调试 当前apicookie: ${cookie.name}: ${cookie.value}'); 27 | }); 28 | 29 | cookies = await WebviewCookieManager().getCookies(HttpString.tUrl); 30 | await Request.cookieManager.cookieJar 31 | .saveFromResponse(Uri.parse(HttpString.tUrl), cookies); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/request/danmaku.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:bilineo/request/api.dart'; 3 | import 'package:bilineo/request/request.dart'; 4 | import 'package:bilineo/bean/danmaku/dm.pb.dart'; 5 | 6 | class DanmakaHttp { 7 | // 获取视频弹幕 8 | static Future queryDanmaku({ 9 | required int cid, 10 | required int segmentIndex, 11 | }) async { 12 | // 构建参数对象 13 | Map params = { 14 | 'type': 1, 15 | 'oid': cid, 16 | 'segment_index': segmentIndex, 17 | }; 18 | var response = await Request().get( 19 | Api.webDanmaku, 20 | data: params, 21 | extra: {'resType': ResponseType.bytes}, 22 | ); 23 | return DmSegMobileReply.fromBuffer(response.data); 24 | } 25 | 26 | static Future shootDanmaku({ 27 | int type = 1, //弹幕类选择(1:视频弹幕 2:漫画弹幕) 28 | required int oid, // 视频cid 29 | required String msg, //弹幕文本(长度小于 100 字符) 30 | int mode = 31 | 1, // 弹幕类型(1:滚动弹幕 4:底端弹幕 5:顶端弹幕 6:逆向弹幕(不能使用) 7:高级弹幕 8:代码弹幕(不能使用) 9:BAS弹幕(pool必须为2)) 32 | // String? aid,// 稿件avid 33 | // String? bvid,// bvid与aid必须有一个 34 | required String bvid, 35 | int? progress, // 弹幕出现在视频内的时间(单位为毫秒,默认为0) 36 | int? color, // 弹幕颜色(默认白色,16777215) 37 | int? fontsize, // 弹幕字号(默认25) 38 | int? pool, // 弹幕池选择(0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕)默认普通池,0) 39 | //int? rnd,// 当前时间戳*1000000(若无此项,则发送弹幕冷却时间限制为90s;若有此项,则发送弹幕冷却时间限制为5s) 40 | int? colorful, //60001:专属渐变彩色(需要会员) 41 | int? checkbox_type, //是否带 UP 身份标识(0:普通;4:带有标识) 42 | // String? csrf,//CSRF Token(位于 Cookie) Cookie 方式必要 43 | // String? access_key,// APP 登录 Token APP 方式必要 44 | }) async { 45 | // 构建参数对象 46 | // assert(aid != null || bvid != null); 47 | // assert(csrf != null || access_key != null); 48 | assert(msg.length < 100); 49 | // 构建参数对象 50 | var params = { 51 | 'type': type, 52 | 'oid': oid, 53 | 'msg': msg, 54 | 'mode': mode, 55 | //'aid': aid, 56 | 'bvid': bvid, 57 | 'progress': progress, 58 | 'color': color, 59 | 'fontsize': fontsize, 60 | 'pool': pool, 61 | 'rnd': DateTime.now().microsecondsSinceEpoch, 62 | 'colorful': colorful, 63 | 'checkbox_type': checkbox_type, 64 | 'csrf': await Request.getCsrf(), 65 | // 'access_key': access_key, 66 | }..removeWhere((key, value) => value == null); 67 | 68 | var response = await Request().post( 69 | Api.shootDanmaku, 70 | data: params, 71 | options: Options( 72 | contentType: Headers.formUrlEncodedContentType, 73 | ), 74 | ); 75 | if (response.statusCode != 200) { 76 | return { 77 | 'status': false, 78 | 'data': [], 79 | 'msg': '弹幕发送失败,状态码:${response.statusCode}', 80 | }; 81 | } 82 | if (response.data['code'] == 0) { 83 | return { 84 | 'status': true, 85 | 'data': response.data['data'], 86 | }; 87 | } else { 88 | return { 89 | 'status': false, 90 | 'data': [], 91 | 'msg': "${response.data['code']}: ${response.data['message']}", 92 | }; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/request/interceptor.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_print 2 | 3 | import 'package:connectivity_plus/connectivity_plus.dart'; 4 | import 'package:dio/dio.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 7 | import 'package:hive/hive.dart'; 8 | import 'package:bilineo/request/constants.dart'; 9 | import '../utils/storage.dart'; 10 | 11 | class ApiInterceptor extends Interceptor { 12 | static Box setting = GStorage.setting; 13 | @override 14 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 15 | if (setting.get(SettingBoxKey.aeraUnlock, defaultValue: false)) { 16 | debugPrint('当前解锁状态:已解锁'); 17 | if (options.baseUrl == HttpString.apiBaseUrl) { 18 | debugPrint('使用 ${HttpString.unlockAPIUrl} 尝试解锁'); 19 | options.baseUrl = HttpString.unlockAPIUrl; 20 | } 21 | } else { 22 | debugPrint('当前解锁状态:未解锁'); 23 | } 24 | // ebugPrint("当前解锁状态 ${SettingBoxKey.aeraUnlock}"); 25 | // 在请求之前添加头部或认证信息 26 | // options.headers['Authorization'] = 'Bearer token'; 27 | // options.headers['Content-Type'] = 'application/json'; 28 | handler.next(options); 29 | } 30 | 31 | @override 32 | void onResponse(Response response, ResponseInterceptorHandler handler) { 33 | try { 34 | if (response.statusCode == 302) { 35 | final List locations = response.headers['location']!; 36 | if (locations.isNotEmpty) { 37 | if (locations.first.startsWith('https://www.mcbbs.net')) { 38 | final Uri uri = Uri.parse(locations.first); 39 | final String? accessKey = uri.queryParameters['access_key']; 40 | final String? mid = uri.queryParameters['mid']; 41 | try { 42 | Box localCache = GStorage.localCache; 43 | localCache.put(LocalCacheKey.accessKey, 44 | {'mid': mid, 'value': accessKey}); 45 | } catch (_) {} 46 | } 47 | } 48 | } 49 | } catch (err) { 50 | print('ApiInterceptor: $err'); 51 | } 52 | 53 | handler.next(response); 54 | } 55 | 56 | @override 57 | void onError(DioException err, ErrorInterceptorHandler handler) async { 58 | // 处理网络请求错误 59 | // handler.next(err); 60 | // SmartDialog.showToast( 61 | // await dioError(err), 62 | // displayType: SmartToastType.onlyRefresh, 63 | // ); 64 | super.onError(err, handler); 65 | } 66 | 67 | static Future dioError(DioException error) async { 68 | switch (error.type) { 69 | case DioExceptionType.badCertificate: 70 | return '证书有误!'; 71 | case DioExceptionType.badResponse: 72 | return '服务器异常,请稍后重试!'; 73 | case DioExceptionType.cancel: 74 | return '请求已被取消,请重新请求'; 75 | case DioExceptionType.connectionError: 76 | return '连接错误,请检查网络设置'; 77 | case DioExceptionType.connectionTimeout: 78 | return '网络连接超时,请检查网络设置'; 79 | case DioExceptionType.receiveTimeout: 80 | return '响应超时,请稍后重试!'; 81 | case DioExceptionType.sendTimeout: 82 | return '发送请求超时,请检查网络设置'; 83 | case DioExceptionType.unknown: 84 | final String res = await checkConnect(); 85 | return '$res,网络异常!如果您已打开港澳台模式,尝试关闭'; 86 | } 87 | } 88 | 89 | static Future checkConnect() async { 90 | final ConnectivityResult connectivityResult = 91 | await Connectivity().checkConnectivity(); 92 | switch (connectivityResult) { 93 | case ConnectivityResult.mobile: 94 | return '正在使用移动流量'; 95 | case ConnectivityResult.wifi: 96 | return '正在使用wifi'; 97 | case ConnectivityResult.ethernet: 98 | return '正在使用局域网'; 99 | case ConnectivityResult.vpn: 100 | return '正在使用代理网络'; 101 | case ConnectivityResult.other: 102 | return '正在使用其他网络'; 103 | case ConnectivityResult.none: 104 | return '未连接到任何网络'; 105 | default: 106 | return ''; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/request/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/request/request.dart'; 2 | import 'package:bilineo/request/api.dart'; 3 | import 'package:bilineo/pages/my/user_info.dart'; 4 | 5 | class UserHttp { 6 | static Future userStat({required int mid}) async { 7 | var res = await Request().get(Api.userStat, data: {'vmid': mid}); 8 | if (res.data['code'] == 0) { 9 | return {'status': true, 'data': res.data['data']}; 10 | } else { 11 | return {'status': false}; 12 | } 13 | } 14 | 15 | static Future userInfo() async { 16 | var res = await Request().get(Api.userInfo); 17 | if (res.data['code'] == 0) { 18 | UserInfoData data = UserInfoData.fromJson(res.data['data']); 19 | return {'status': true, 'data': data}; 20 | } else { 21 | return {'status': false, 'msg': res.data['message']}; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /lib/request/video.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:convert'; 3 | 4 | import 'api.dart'; 5 | import 'request.dart'; 6 | import 'package:hive/hive.dart'; 7 | import 'package:bilineo/utils/wbisign.dart'; 8 | import 'package:bilineo/pages/player/player_url.dart'; 9 | import 'package:bilineo/utils/utils.dart'; 10 | 11 | class VideoRequest { 12 | // Todo 获取字幕 13 | static Future subUrl({int? avid, String? bvid, required int cid}) async { 14 | Map data = { 15 | 'cid': cid, 16 | 'bvid': bvid, 17 | }; 18 | if (avid != null) { 19 | data['avid'] = avid; 20 | } 21 | Map params = await WbiSign().makSign({ 22 | ...data, 23 | 'fourk': 1, 24 | 'voice_balance': 1, 25 | 'gaia_source': 'pre-load', 26 | 'web_location': 1550101, 27 | }); 28 | try { 29 | var res = await Request().get(Api.subUrl, data: params); 30 | Map jsonMap = json.decode(res.toString()); 31 | List subtitles = jsonMap['data']['subtitle']['subtitles']; 32 | String subtitleUrl = subtitles.firstWhere( 33 | (subtitle) => subtitle['lan'].startsWith('zh'), 34 | orElse: () => null)['subtitle_url']; 35 | subtitleUrl = 'https:' + subtitleUrl; 36 | debugPrint(subtitleUrl); 37 | return subtitleUrl; 38 | } catch (e) { 39 | debugPrint('查询字幕失败 ${e.toString()}'); 40 | return ''; 41 | } 42 | } 43 | 44 | static Future getSub(String subUrl) async{ 45 | var res = await Request().get(subUrl); 46 | final jsonData = json.decode(res.toString()); 47 | final webvttString = Utils.jsonToWebVTT(jsonData); 48 | // debugPrint(webvttString); 49 | return webvttString; 50 | } 51 | 52 | static Future videoUrl( 53 | {int? avid, String? bvid, required int cid, int? qn}) async { 54 | Map data = { 55 | 'cid': cid, 56 | 'qn': qn ?? 80, 57 | // 获取所有格式的视频 58 | 'fnval': 4048, 59 | }; 60 | if (avid != null) { 61 | data['avid'] = avid; 62 | } 63 | if (bvid != null) { 64 | data['bvid'] = bvid; 65 | } 66 | 67 | // Todo 大会员相关 68 | 69 | // 免登录查看1080p 70 | // if (userInfoCache.get('userInfoCache') == null && 71 | // setting.get(SettingBoxKey.p1080, defaultValue: true)) { 72 | // data['try_look'] = 1; 73 | // } 74 | 75 | data['try_look'] = 1; 76 | 77 | Map params = await WbiSign().makSign({ 78 | ...data, 79 | 'fourk': 1, 80 | 'voice_balance': 1, 81 | 'gaia_source': 'pre-load', 82 | 'web_location': 1550101, 83 | }); 84 | 85 | try { 86 | var res = await Request().get(Api.videoUrl, data: params); 87 | if (res.data['code'] == 0) { 88 | return { 89 | 'status': true, 90 | 'data': PlayUrlModel.fromJson(res.data['data']) 91 | }; 92 | } else { 93 | return { 94 | 'status': false, 95 | 'data': [], 96 | 'code': res.data['code'], 97 | 'msg': res.data['message'], 98 | }; 99 | } 100 | } catch (err) { 101 | return {'status': false, 'data': [], 'msg': err}; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/utils/constans.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class StyleString { 4 | static const double cardSpace = 8; 5 | static const double safeSpace = 12; 6 | static BorderRadius mdRadius = BorderRadius.circular(10); 7 | static const Radius imgRadius = Radius.circular(10); 8 | static const double aspectRatio = 16 / 10; 9 | } 10 | 11 | class Constants { 12 | // 27eb53fc9058f8c3 移动端 Android 13 | // 4409e2ce8ffd12b8 TV端 14 | static const String appKey = '4409e2ce8ffd12b8'; 15 | // 59b43e04ad6965f34319062b478f83dd TV端 16 | static const String appSec = '59b43e04ad6965f34319062b478f83dd'; 17 | static const String thirdSign = '04224646d1fea004e79606d3b038c84a'; 18 | static const String thirdApi = 19 | 'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png'; 20 | } 21 | -------------------------------------------------------------------------------- /lib/utils/em.dart: -------------------------------------------------------------------------------- 1 | class Em { 2 | static regCate(String origin) { 3 | String str = origin; 4 | RegExp exp = RegExp('<[^>]*>([^<]*)]*>'); 5 | Iterable matches = exp.allMatches(origin); 6 | for (Match match in matches) { 7 | str = match.group(1)!; 8 | } 9 | return str; 10 | } 11 | 12 | static regTitle(String origin) { 13 | RegExp exp = RegExp('<[^>]*>([^<]*)]*>'); 14 | List res = []; 15 | origin.splitMapJoin(exp, onMatch: (Match match) { 16 | String matchStr = match[0]!; 17 | Map map = {'type': 'em', 'text': regCate(matchStr)}; 18 | res.add(map); 19 | return regCate(matchStr); 20 | }, onNonMatch: (String str) { 21 | if (str != '') { 22 | str = str 23 | .replaceAll('<', '<') 24 | .replaceAll('>', '>') 25 | .replaceAll('"', '"') 26 | .replaceAll(''', "'") 27 | .replaceAll('"', '"') 28 | .replaceAll(''', "'") 29 | .replaceAll(' ', " ") 30 | .replaceAll('&', "&"); 31 | Map map = {'type': 'text', 'text': str}; 32 | res.add(map); 33 | } 34 | return str; 35 | }); 36 | return res; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/utils/id.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names, non_constant_identifier_names 2 | 3 | import 'dart:convert'; 4 | 5 | class IdUtils { 6 | static final XOR_CODE = BigInt.parse('23442827791579'); 7 | static final MASK_CODE = BigInt.parse('2251799813685247'); 8 | static final MAX_AID = BigInt.one << (BigInt.from(51)).toInt(); 9 | static final BASE = BigInt.from(58); 10 | 11 | static const data = 12 | 'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf'; 13 | 14 | /// av转bv 15 | static String av2bv(int aid) { 16 | List bytes = [ 17 | 'B', 18 | 'V', 19 | '1', 20 | '0', 21 | '0', 22 | '0', 23 | '0', 24 | '0', 25 | '0', 26 | '0', 27 | '0', 28 | '0' 29 | ]; 30 | int bvIndex = bytes.length - 1; 31 | BigInt tmp = (MAX_AID | BigInt.from(aid)) ^ XOR_CODE; 32 | while (tmp > BigInt.zero) { 33 | bytes[bvIndex] = data[(tmp % BASE).toInt()]; 34 | tmp = tmp ~/ BASE; 35 | bvIndex -= 1; 36 | } 37 | String tmpSwap = bytes[3]; 38 | bytes[3] = bytes[9]; 39 | bytes[9] = tmpSwap; 40 | 41 | tmpSwap = bytes[4]; 42 | bytes[4] = bytes[7]; 43 | bytes[7] = tmpSwap; 44 | 45 | return bytes.join(); 46 | } 47 | 48 | /// bv转av 49 | static int bv2av(String bvid) { 50 | List bvidArr = bvid.split(''); 51 | final tmpValue = bvidArr[3]; 52 | bvidArr[3] = bvidArr[9]; 53 | bvidArr[9] = tmpValue; 54 | 55 | final tmpValue2 = bvidArr[4]; 56 | bvidArr[4] = bvidArr[7]; 57 | bvidArr[7] = tmpValue2; 58 | 59 | bvidArr.removeRange(0, 3); 60 | BigInt tmp = bvidArr.fold(BigInt.zero, 61 | (pre, bvidChar) => pre * BASE + BigInt.from(data.indexOf(bvidChar))); 62 | return ((tmp & MASK_CODE) ^ XOR_CODE).toInt(); 63 | } 64 | 65 | // 匹配 66 | static Map matchAvorBv({String? input}) { 67 | final Map result = {}; 68 | if (input == null || input.isEmpty) { 69 | return result; 70 | } 71 | final RegExp bvRegex = RegExp(r'BV[0-9A-Za-z]{10}', caseSensitive: false); 72 | final RegExp avRegex = RegExp(r'AV\d+', caseSensitive: false); 73 | 74 | final Iterable bvMatches = bvRegex.allMatches(input); 75 | final Iterable avMatches = avRegex.allMatches(input); 76 | 77 | final List bvs = 78 | bvMatches.map((Match match) => match.group(0)!).toList(); 79 | final List avs = 80 | avMatches.map((Match match) => match.group(0)!).toList(); 81 | 82 | if (bvs.isNotEmpty) { 83 | result['BV'] = bvs[0].substring(0, 2).toUpperCase() + bvs[0].substring(2); 84 | } 85 | if (avs.isNotEmpty) { 86 | result['AV'] = int.parse(avs[0].substring(2)); 87 | } 88 | return result; 89 | } 90 | 91 | // eid生成 92 | static String? genAuroraEid(int uid) { 93 | if (uid == 0) { 94 | return null; 95 | } 96 | String uidString = uid.toString(); 97 | List resultBytes = List.generate( 98 | uidString.length, 99 | (i) => uidString.codeUnitAt(i) ^ "ad1va46a7lza".codeUnitAt(i % 12), 100 | ); 101 | String auroraEid = base64Url.encode(resultBytes); 102 | auroraEid = auroraEid.replaceAll(RegExp(r'=*$', multiLine: true), ''); 103 | return auroraEid; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/utils/storage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:hive_flutter/hive_flutter.dart'; 5 | import 'package:path_provider/path_provider.dart'; 6 | import 'package:bilineo/pages/my/user_info.dart'; 7 | 8 | class GStorage { 9 | static late final Box localCache; 10 | static late final Box userInfo; 11 | static late final Box setting; 12 | 13 | static Future init() async { 14 | final Directory dir = await getApplicationSupportDirectory(); 15 | final String path = dir.path; 16 | await Hive.initFlutter('$path/hive'); 17 | regAdapter(); 18 | setting = await Hive.openBox('setting'); 19 | // Todo 登录用户信息 20 | userInfo = await Hive.openBox( 21 | 'userInfo', 22 | compactionStrategy: (int entries, int deletedEntries) { 23 | return deletedEntries > 2; 24 | }, 25 | ); 26 | 27 | // 本地缓存 28 | localCache = await Hive.openBox( 29 | 'localCache', 30 | compactionStrategy: (int entries, int deletedEntries) { 31 | return deletedEntries > 4; 32 | }, 33 | ); 34 | debugPrint('GStorage 初始化完成'); 35 | } 36 | 37 | // Todo 所有者相关 38 | static void regAdapter() { 39 | Hive.registerAdapter(UserInfoDataAdapter()); 40 | Hive.registerAdapter(LevelInfoAdapter()); 41 | } 42 | 43 | static Future close() async { 44 | userInfo.compact(); 45 | userInfo.close(); 46 | localCache.compact(); 47 | localCache.close(); 48 | setting.compact(); 49 | setting.close(); 50 | } 51 | } 52 | 53 | class LocalCacheKey { 54 | // 历史记录暂停状态 默认false 记录 55 | static const String historyPause = 'historyPause', 56 | // access_key 57 | accessKey = 'accessKey', 58 | 59 | // 60 | wbiKeys = 'wbiKeys', 61 | timeStamp = 'timeStamp', 62 | 63 | // 弹幕相关设置 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 64 | danmakuBlockType = 'danmakuBlockType', 65 | danmakuShowArea = 'danmakuShowArea', 66 | danmakuOpacity = 'danmakuOpacity', 67 | danmakuFontScale = 'danmakuFontScale', 68 | danmakuDuration = 'danmakuDuration', 69 | strokeWidth = 'strokeWidth', 70 | 71 | // 代理host port 72 | systemProxyHost = 'systemProxyHost', 73 | systemProxyPort = 'systemProxyPort'; 74 | } 75 | 76 | class SettingBoxKey { 77 | static const String aeraUnlock = 'aeraUnlock', 78 | themeMode = 'themeMode', 79 | // Todo 检查更新 80 | autoUpdate = '', 81 | danmakuEnhance = 'danmakuEnhance', 82 | danmakuBorder = 'danmakuBorder', 83 | danmakuOpacity = 'danmakuOpacity', 84 | danmakuFontSize = 'danmakuFontSize', 85 | danmakuTop = 'danmakuTop', 86 | danmakuScroll = 'danmakuScroll', 87 | danmakuBottom = 'danmakuBottom', 88 | danmakuArea = 'danmakuArea'; 89 | } 90 | -------------------------------------------------------------------------------- /lib/utils/video.dart: -------------------------------------------------------------------------------- 1 | import 'package:bilineo/pages/player/player_url.dart'; 2 | 3 | 4 | class VideoUtils { 5 | static String getCdnUrl(dynamic item) { 6 | var backupUrl = ""; 7 | var videoUrl = ""; 8 | 9 | /// 先获取backupUrl 一般是upgcxcode地址 播放更稳定 10 | if (item is VideoItem) { 11 | backupUrl = item.backupUrl ?? ""; 12 | videoUrl = backupUrl.contains("http") ? backupUrl : (item.baseUrl ?? ""); 13 | } else if (item is AudioItem) { 14 | backupUrl = item.backupUrl ?? ""; 15 | videoUrl = backupUrl.contains("http") ? backupUrl : (item.baseUrl ?? ""); 16 | } else { 17 | return ""; 18 | } 19 | 20 | /// pilipala issues #70 21 | if (videoUrl.contains(".mcdn.bilivideo")) { 22 | videoUrl = 23 | 'https://proxy-tf-all-ws.bilivideo.com/?url=${Uri.encodeComponent(videoUrl)}'; 24 | } else if (videoUrl.contains("/upgcxcode/")) { 25 | //CDN列表 26 | var cdnList = { 27 | 'ali': 'upos-sz-mirrorali.bilivideo.com', 28 | 'cos': 'upos-sz-mirrorcos.bilivideo.com', 29 | 'hw': 'upos-sz-mirrorhw.bilivideo.com', 30 | }; 31 | //取一个CDN 32 | var cdn = cdnList['ali'] ?? ""; 33 | var reg = RegExp(r'(http|https)://(.*?)/upgcxcode/'); 34 | videoUrl = videoUrl.replaceAll(reg, "https://$cdn/upgcxcode/"); 35 | } 36 | 37 | return videoUrl; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | void fl_register_plugins(FlPluginRegistry* registry) { 17 | g_autoptr(FlPluginRegistrar) flutter_volume_controller_registrar = 18 | fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin"); 19 | flutter_volume_controller_plugin_register_with_registrar(flutter_volume_controller_registrar); 20 | g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar = 21 | fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin"); 22 | media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar); 23 | g_autoptr(FlPluginRegistrar) media_kit_video_registrar = 24 | fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin"); 25 | media_kit_video_plugin_register_with_registrar(media_kit_video_registrar); 26 | g_autoptr(FlPluginRegistrar) screen_retriever_registrar = 27 | fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); 28 | screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); 29 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 30 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 31 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 32 | g_autoptr(FlPluginRegistrar) window_manager_registrar = 33 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); 34 | window_manager_plugin_register_with_registrar(window_manager_registrar); 35 | } 36 | -------------------------------------------------------------------------------- /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 | flutter_volume_controller 7 | media_kit_libs_linux 8 | media_kit_video 9 | screen_retriever 10 | url_launcher_linux 11 | window_manager 12 | ) 13 | 14 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 15 | media_kit_native_event_loop 16 | ) 17 | 18 | set(PLUGIN_BUNDLED_LIBRARIES) 19 | 20 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 22 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 25 | endforeach(plugin) 26 | 27 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 28 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 29 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 30 | endforeach(ffi_plugin) 31 | -------------------------------------------------------------------------------- /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.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "bilineo"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "bilineo"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_show(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GObject::dispose. 85 | static void my_application_dispose(GObject* object) { 86 | MyApplication* self = MY_APPLICATION(object); 87 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 88 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 89 | } 90 | 91 | static void my_application_class_init(MyApplicationClass* klass) { 92 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 93 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 94 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 95 | } 96 | 97 | static void my_application_init(MyApplication* self) {} 98 | 99 | MyApplication* my_application_new() { 100 | return MY_APPLICATION(g_object_new(my_application_get_type(), 101 | "application-id", APPLICATION_ID, 102 | "flags", G_APPLICATION_NON_UNIQUE, 103 | nullptr)); 104 | } 105 | -------------------------------------------------------------------------------- /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 flutter_volume_controller 11 | import media_kit_libs_macos_video 12 | import media_kit_video 13 | import package_info_plus 14 | import path_provider_foundation 15 | import screen_brightness_macos 16 | import screen_retriever 17 | import shared_preferences_foundation 18 | import sqflite 19 | import url_launcher_macos 20 | import wakelock_plus 21 | import window_manager 22 | 23 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 24 | ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) 25 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 26 | FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin")) 27 | MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) 28 | MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) 29 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 30 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 31 | ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) 32 | ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) 33 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 34 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 35 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 36 | WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) 37 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) 38 | } 39 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /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/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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 = bilineo 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.bilineo 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 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:bilineo/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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 | bilineo 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bilineo", 3 | "short_name": "bilineo", 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/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /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 | 19 | void RegisterPlugins(flutter::PluginRegistry* registry) { 20 | ConnectivityPlusWindowsPluginRegisterWithRegistrar( 21 | registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); 22 | FlutterVolumeControllerPluginCApiRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("FlutterVolumeControllerPluginCApi")); 24 | MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); 26 | MediaKitVideoPluginCApiRegisterWithRegistrar( 27 | registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); 28 | ScreenBrightnessWindowsPluginRegisterWithRegistrar( 29 | registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); 30 | ScreenRetrieverPluginRegisterWithRegistrar( 31 | registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); 32 | UrlLauncherWindowsRegisterWithRegistrar( 33 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 34 | WebviewWindowsPluginRegisterWithRegistrar( 35 | registry->GetRegistrarForPlugin("WebviewWindowsPlugin")); 36 | WindowManagerPluginRegisterWithRegistrar( 37 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 38 | } 39 | -------------------------------------------------------------------------------- /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 | flutter_volume_controller 8 | media_kit_libs_windows_video 9 | media_kit_video 10 | screen_brightness_windows 11 | screen_retriever 12 | url_launcher_windows 13 | webview_windows 14 | window_manager 15 | ) 16 | 17 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 18 | media_kit_native_event_loop 19 | ) 20 | 21 | set(PLUGIN_BUNDLED_LIBRARIES) 22 | 23 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 25 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 28 | endforeach(plugin) 29 | 30 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 31 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 32 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 33 | endforeach(ffi_plugin) 34 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "bilineo" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "bilineo" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "bilineo.exe" "\0" 98 | VALUE "ProductName", "bilineo" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"bilineo", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Predidit/BiliNeo/0e441fa786151a47405fa052d6125f5f37c28893/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 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | --------------------------------------------------------------------------------