├── .gitignore ├── .gitmoji-changelogrc ├── .metadata ├── .tool-versions ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_EN.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── jniLibs │ │ │ ├── arm64-v8a │ │ │ │ └── libaria2c.so │ │ │ ├── armeabi-v7a │ │ │ │ └── libaria2c.so │ │ │ ├── x86 │ │ │ │ └── libaria2c.so │ │ │ └── x86_64 │ │ │ │ └── libaria2c.so │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── yolx │ │ │ │ └── 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-ldpi │ │ │ └── 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 └── yolx_android.iml ├── assets ├── icon-foreground.png ├── logo.ico ├── logo.png └── no_data.svg ├── distribute_options.yaml ├── lib ├── common │ ├── const.dart │ └── global.dart ├── generated │ ├── intl │ │ ├── messages_all.dart │ │ ├── messages_en.dart │ │ └── messages_zh_CN.dart │ └── l10n.dart ├── l10n │ ├── intl_en.arb │ └── intl_zh_CN.arb ├── main.dart ├── model │ ├── download_item.dart │ └── download_list_model.dart ├── resources │ └── yolx_aria2.conf ├── screens │ ├── downloading.dart │ ├── settings.dart │ ├── stopped.dart │ └── waiting.dart ├── theme.dart ├── utils │ ├── aria2_manager.dart │ ├── ariar2_http_utils.dart │ ├── common_utils.dart │ ├── file_utils.dart │ ├── log.dart │ ├── native_channel_utils.dart │ ├── permission_util.dart │ ├── tracker_http_utils.dart │ └── url_utils.dart └── widgets │ ├── download_file_card.dart │ ├── new_download_dialog.dart │ ├── page.dart │ ├── settings_card.dart │ └── upload_torrent.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── bin │ └── plugin │ │ └── aria2 │ │ └── yolx_aria2c ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc ├── my_application.h └── packaging │ ├── appimage │ └── make_config.yaml │ ├── deb │ └── make_config.yaml │ └── rpm │ └── make_config.yaml ├── pubspec.yaml ├── symbols ├── app.android-arm.symbols ├── app.android-arm64.symbols └── app.android-x64.symbols ├── test └── widget_test.dart ├── windows ├── .gitignore ├── CMakeLists.txt ├── bin │ └── plugin │ │ └── aria2 │ │ └── yolx_aria2c.exe ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── packaging │ └── exe │ │ ├── inno_setup.iss │ │ └── make_config.yaml └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h └── yolx.iml /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | .dart_tool/ 5 | .packages 6 | .idea/ 7 | build/ 8 | dist/ 9 | symbols/ 10 | # If you're building an application, you may want to check-in your pubspec.lock 11 | pubspec.lock 12 | 13 | # Directory created by dartdoc 14 | # If you don't generate documentation locally you can remove this line. 15 | doc/api/ 16 | 17 | # dotenv environment variables file 18 | .env* 19 | 20 | # Avoid committing generated Javascript files: 21 | *.dart.js 22 | *.info.json # Produced by the --dump-info flag. 23 | *.js # When generated by dart2js. Don't specify *.js if your 24 | # project includes source files written in JavaScript. 25 | *.js_ 26 | *.js.deps 27 | *.js.map 28 | 29 | .flutter-plugins 30 | .flutter-plugins-dependencies 31 | windows/installers/yolx-setup.exe 32 | -------------------------------------------------------------------------------- /.gitmoji-changelogrc: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "name": "Yolx", 4 | "description": "A changelog generator for Yolx", 5 | "version": "0.3.8" 6 | } 7 | } -------------------------------------------------------------------------------- /.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: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" 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: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 17 | base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 18 | - platform: android 19 | create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 20 | base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 21 | - platform: ios 22 | create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 23 | base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 24 | - platform: linux 25 | create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 26 | base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 27 | - platform: macos 28 | create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 29 | base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 30 | - platform: windows 31 | create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 32 | base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 33 | 34 | # User provided section 35 | 36 | # List of Local paths (relative to this file) that should be 37 | # ignored by the migrate tool. 38 | # 39 | # Files that are not part of the templates will be ignored by default. 40 | unmanaged_files: 41 | - 'lib/main.dart' 42 | - 'ios/Runner.xcodeproj/project.pbxproj' 43 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 21.5.0 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.sourceDirectory": "C:/Users/Yoyo/Documents/GitHub/Yolx/windows", 3 | "java.configuration.updateBuildConfiguration": "interactive" 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |
6 | 7 | # Yolx 8 | 9 | `Yolx` 一款现代化下载工具! 10 | 11 | 12 | Gitmoji 16 | 17 | 18 | [![gitmoji-changelog](https://img.shields.io/badge/Changelog-gitmoji-brightgreen.svg)](https://github.com/frinyvonnick/gitmoji-changelog) 19 |
20 | 21 | --- 22 | 23 | 24 | 简体中文 | [English](/README_EN.md) 25 | 26 | `Yolx` 是一款以「Aria 2」作为核心的现代化下载工具。 27 | 28 | ## 🙌 简单的开始 29 | 30 | 如果想安装Yolx,请打开右侧的 [Release](https://github.com/uiYzzi/Yolx/releases) 页面,找到最新版本,并选择适用于当前系统的安装包下载。 31 | 32 | 33 | **Watch** 项目,以获取应用的更新动态。 34 | 35 | ## 🚀 协作 36 | 37 | 非常感谢有兴趣的开发者或爱好者参与 `Yolx` 项目,分享你的见解与思路。 38 | 39 | ## 🍭 编译 40 | 41 | ### Windows 42 | #### 依赖安装 43 | 44 | 1、按照[官方文档](https://flutter.cn/docs/get-started/install/windows)安装`Flutter SDK`以及[Visual Studio 2022 生成工具](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022) 45 | 46 | 2、安装打包工具[Inno Setup 6](https://jrsoftware.org/isinfo.php),并添加[中文语言包](https://jrsoftware.org/files/istrans/) `ChineseSimplified.isl` 和 `ChineseTraditional.isl` 到 `C:\Program Files (x86)\Inno Setup 6\Languages` 目录 47 | 48 | 3、按照[官方文档](https://distributor.leanflutter.dev/zh-hans/getting-started/)安装构建工具 49 | #### 打包构建 50 | 51 | 在项目目录运行下面命令进行编译打包,构建完成后可在 `dist` 文件夹内找到安装包 52 | 53 | ``` 54 | flutter_distributor release --name windows 55 | ``` 56 | 57 | ### Linux 58 | #### 依赖安装 59 | 60 | 1、按照[官方文档](https://flutter.cn/docs/get-started/install/linux)安装`Flutter SDK` 61 | 62 | 2、输入以下命令安装构建依赖 63 | ``` 64 | sudo apt-get install clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev libayatana-appindicator3-dev 65 | 66 | ``` 67 | 68 | 3、按照[官方文档](https://distributor.leanflutter.dev/zh-hans/getting-started/)安装构建工具 69 | 70 | 4、输入以下命令安装打包依赖 71 | ``` 72 | sudo apt install rpm patchelf locate libfuse2 fuse 73 | wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" 74 | chmod +x appimagetool 75 | sudo mv appimagetool /usr/local/bin/ 76 | ``` 77 | #### 打包构建 78 | 79 | 在项目目录运行下面命令进行编译打包,构建完成后可在 `dist` 文件夹内找到安装包 80 | 81 | ``` 82 | flutter_distributor release --name linux 83 | ``` 84 | 85 | ## 🫸 特别鸣谢 86 | - [LeanFlutter](https://github.com/leanflutter) 87 | - [星火计划 Project Spark](https://gitee.com/spark-store-project) -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |
6 | 7 | # Yolx 8 | 9 | `Yolx` is a modern download tool! 10 | 11 | 12 | Gitmoji 16 | 17 | 18 | [![gitmoji-changelog](https://img.shields.io/badge/Changelog-gitmoji-brightgreen.svg)](https://github.com/frinyvonnick/gitmoji-changelog) 19 | 20 |
21 | 22 | --- 23 | 24 | English | [简体中文](../../) 25 | 26 | `Yolx` is a modern download tool with "Aria 2" at its core. 27 | 28 | ## 🙌 Getting Started 29 | 30 | To install Yolx, please open the [Release](https://github.com/uiYzzi/Yolx/releases) page on the right, find the latest version, and select the installation package suitable for your current system. 31 | 32 | **Watch** the project for updates on the application. 33 | 34 | ## 🚀 Collaboration 35 | 36 | Thank you very much to developers or enthusiasts interested in participating in the `Yolx` project. Share your insights and ideas. 37 | 38 | ## 🍭 Compilation 39 | 40 | ### Windows 41 | #### Dependency Installation 42 | 43 | 1. Follow the [official documentation](https://flutter.cn/docs/get-started/install/windows) to install `Flutter SDK` and [Visual Studio 2022 Build Tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022). 44 | 45 | 2. Install the packaging tool [Inno Setup 6](https://jrsoftware.org/isinfo.php), and add the [Chinese language packs](https://jrsoftware.org/files/istrans/) `ChineseSimplified.isl` and `ChineseTraditional.isl` to the `C:\Program Files (x86)\Inno Setup 6\Languages` directory. 46 | 47 | 3. Follow the [official documentation](https://distributor.leanflutter.dev/zh-hans/getting-started/) to install the build tools. 48 | #### Packaging and Building 49 | 50 | Run the following command in the project directory to compile and package. After the build is complete, you can find the installation package in the `dist` folder. 51 | 52 | ``` 53 | flutter_distributor release --name windows 54 | ``` 55 | 56 | ### Linux 57 | #### Dependency Installation 58 | 59 | 1. Follow the [official documentation](https://flutter.cn/docs/get-started/install/linux) to install `Flutter SDK`. 60 | 61 | 2. Enter the following command to install build dependencies: 62 | ``` 63 | sudo apt-get install clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev libayatana-appindicator3-dev 64 | ``` 65 | 66 | 3. Follow the [official documentation](https://distributor.leanflutter.dev/zh-hans/getting-started/) to install the build tools. 67 | 68 | 4. Enter the following command to install packaging dependencies: 69 | ``` 70 | sudo apt install rpm patchelf locate libfuse2 fuse 71 | wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" 72 | chmod +x appimagetool 73 | sudo mv appimagetool /usr/local/bin/ 74 | ``` 75 | 76 | #### Packaging and Building 77 | 78 | Run the following command in the project directory to compile and package. After the build is complete, you can find the installation package in the `dist` folder. 79 | 80 | ``` 81 | flutter_distributor release --name linux 82 | ``` 83 | 84 | ## 🫸 Acknowledgments 85 | - [LeanFlutter](https://github.com/leanflutter) 86 | - [星火计划 Project Spark](https://gitee.com/spark-store-project) -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml -------------------------------------------------------------------------------- /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 | def keystoreProperties = new Properties() 26 | def keystorePropertiesFile = rootProject.file('key.properties') 27 | if (keystorePropertiesFile.exists()) { 28 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 29 | } 30 | 31 | android { 32 | namespace "com.yoyo.yolx" 33 | compileSdkVersion flutter.compileSdkVersion 34 | flavorDimensions "abi" 35 | ndkVersion flutter.ndkVersion 36 | 37 | productFlavors { 38 | dev { 39 | dimension "abi" 40 | ndk { abiFilter "arm64-v8a" } 41 | resValue "string", "app_name", "Alga Dev" 42 | } 43 | arm { 44 | dimension "abi" 45 | ndk { abiFilter "armeabi-v7a" } 46 | resValue "string", "app_name", "Alga" 47 | } 48 | arm64 { 49 | dimension "abi" 50 | ndk { abiFilter "arm64-v8a" } 51 | resValue "string", "app_name", "Alga" 52 | } 53 | x86_64 { 54 | dimension "abi" 55 | applicationId "tech.laihz.alga" 56 | ndk { abiFilter "x86_64" } 57 | resValue "string", "app_name", "Alga" 58 | } 59 | universal { 60 | dimension "abi" 61 | applicationId "tech.laihz.alga" 62 | resValue "string", "app_name", "Alga" 63 | } 64 | aab { 65 | dimension "abi" 66 | applicationId "tech.laihz.alga" 67 | resValue "string", "app_name", "Alga" 68 | } 69 | } 70 | 71 | compileOptions { 72 | sourceCompatibility JavaVersion.VERSION_1_8 73 | targetCompatibility JavaVersion.VERSION_1_8 74 | } 75 | 76 | kotlinOptions { 77 | jvmTarget = '1.8' 78 | } 79 | 80 | sourceSets { 81 | main { 82 | java.srcDirs += 'src/main/kotlin' 83 | jniLibs.srcDirs=['src/main/jniLibs'] 84 | } 85 | } 86 | 87 | repositories { 88 | flatDir { 89 | dirs 'libs' 90 | } 91 | } 92 | 93 | defaultConfig { 94 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 95 | applicationId "com.yoyo.yolx" 96 | // You can update the following values to match your application needs. 97 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 98 | minSdkVersion flutter.minSdkVersion 99 | targetSdkVersion flutter.targetSdkVersion 100 | versionCode flutterVersionCode.toInteger() 101 | versionName flutterVersionName 102 | ndk {} 103 | } 104 | 105 | signingConfigs { 106 | release { 107 | keyAlias keystoreProperties['keyAlias'] 108 | keyPassword keystoreProperties['keyPassword'] 109 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 110 | storePassword keystoreProperties['storePassword'] 111 | } 112 | } 113 | 114 | buildTypes { 115 | release { 116 | minifyEnabled true 117 | shrinkResources true 118 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),'proguard-rules.pro' 119 | signingConfig signingConfigs.release 120 | } 121 | } 122 | } 123 | 124 | flutter { 125 | source '../..' 126 | } 127 | 128 | dependencies { 129 | implementation fileTree(include: ['*.jar','*.so'], dir: 'libs') 130 | implementation 'org.bouncycastle:bcprov-jdk16:1.46' 131 | } 132 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | #Flutter Wrapper 2 | -keep class io.flutter.app.** { *; } 3 | -keep class io.flutter.plugin.** { *; } 4 | -keep class io.flutter.util.** { *; } 5 | -keep class io.flutter.view.** { *; } 6 | -keep class io.flutter.** { *; } 7 | -keep class io.flutter.plugins.** { *; } 8 | -keep class de.prosiebensat1digital.** { *; } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 16 | 25 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /android/app/src/main/jniLibs/arm64-v8a/libaria2c.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/android/app/src/main/jniLibs/arm64-v8a/libaria2c.so -------------------------------------------------------------------------------- /android/app/src/main/jniLibs/armeabi-v7a/libaria2c.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/android/app/src/main/jniLibs/armeabi-v7a/libaria2c.so -------------------------------------------------------------------------------- /android/app/src/main/jniLibs/x86/libaria2c.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/android/app/src/main/jniLibs/x86/libaria2c.so -------------------------------------------------------------------------------- /android/app/src/main/jniLibs/x86_64/libaria2c.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/android/app/src/main/jniLibs/x86_64/libaria2c.so -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/yolx/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yoyo.yolx 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.os.Build 6 | import android.os.Environment 7 | import android.provider.Settings; 8 | import io.flutter.embedding.android.FlutterActivity 9 | import io.flutter.embedding.engine.FlutterEngine 10 | import io.flutter.plugin.common.MethodChannel 11 | import java.io.File 12 | 13 | class MainActivity: FlutterActivity() { 14 | private val CHANNEL = "com.yoyo.flutter_native_channel/native_methods" 15 | private var channelResult: MethodChannel.Result? =null; 16 | override fun configureFlutterEngine(flutterEngine: FlutterEngine) { 17 | super.configureFlutterEngine(flutterEngine) 18 | MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> 19 | channelResult=result; 20 | if(call.method == "nativeLibraryDir"){ 21 | result.success(applicationInfo.nativeLibraryDir) 22 | }else if(call.method == "requestPermission"){ 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { //30 24 | // 先判断有没有权限 25 | if (!Environment.isExternalStorageManager()) { 26 | //跳转到设置界面引导用户打开 27 | val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) 28 | intent.data = Uri.parse("package:$packageName") 29 | startActivityForResult(intent, 6666) 30 | }else{ 31 | result.success(true) 32 | } 33 | } 34 | 35 | } else { 36 | result.notImplemented() 37 | } 38 | } 39 | } 40 | 41 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 42 | super.onActivityResult(requestCode, resultCode, data) 43 | if (requestCode==6666){ 44 | if (Environment.isExternalStorageManager()){ 45 | channelResult?.success(true) 46 | } else { 47 | channelResult?.success(false); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/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/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/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/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/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/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/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/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/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/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/android/app/src/main/res/mipmap-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/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 | #131367 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.9.10' 3 | repositories { 4 | // google() 5 | // mavenCentral() 6 | maven { url 'https://maven.aliyun.com/repository/google' } 7 | maven { url 'https://maven.aliyun.com/repository/central' } 8 | } 9 | 10 | dependencies { 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | // google() 18 | // mavenCentral() 19 | maven { url 'https://maven.aliyun.com/repository/google' } 20 | maven { url 'https://maven.aliyun.com/repository/central' } 21 | } 22 | } 23 | 24 | rootProject.buildDir = '../build' 25 | subprojects { 26 | project.buildDir = "${rootProject.buildDir}/${project.name}" 27 | } 28 | subprojects { 29 | project.evaluationDependsOn(':app') 30 | } 31 | 32 | tasks.register("clean", Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/yolx_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /assets/icon-foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/assets/icon-foreground.png -------------------------------------------------------------------------------- /assets/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/assets/logo.ico -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/assets/logo.png -------------------------------------------------------------------------------- /assets/no_data.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /distribute_options.yaml: -------------------------------------------------------------------------------- 1 | output: dist/ 2 | artifact_name: "{{name}}-{{platform}}{{#flavor}}-{{flavor}}{{/flavor}}{{#channel}}-{{channel}}{{/channel}}-{{version}}{{#is_installer}}-setup{{/is_installer}}.{{ext}}" 3 | releases: 4 | - name: android 5 | jobs: 6 | - name: arm 7 | package: 8 | platform: android 9 | target: apk 10 | build_args: 11 | flavor: arm 12 | obfuscate: 13 | split-debug-info: symbols 14 | - name: arm64 15 | package: 16 | platform: android 17 | target: apk 18 | build_args: 19 | flavor: arm64 20 | obfuscate: 21 | split-debug-info: symbols 22 | - name: x86_64 23 | package: 24 | platform: android 25 | target: apk 26 | build_args: 27 | flavor: x86_64 28 | obfuscate: 29 | split-debug-info: symbols 30 | - name: universal 31 | package: 32 | platform: android 33 | target: apk 34 | build_args: 35 | flavor: universal 36 | obfuscate: 37 | split-debug-info: symbols 38 | - name: aab 39 | package: 40 | platform: android 41 | target: aab 42 | build_args: 43 | flavor: aab 44 | obfuscate: 45 | split-debug-info: symbols 46 | - name: windows 47 | jobs: 48 | - name: exe 49 | package: 50 | platform: windows 51 | target: exe 52 | obfuscate: 53 | split-debug-info: symbols 54 | - name: zip 55 | package: 56 | platform: windows 57 | target: zip 58 | obfuscate: 59 | split-debug-info: symbols 60 | - name: linux 61 | jobs: 62 | - name: deb 63 | package: 64 | platform: linux 65 | target: deb 66 | obfuscate: 67 | split-debug-info: symbols 68 | - name: appimage 69 | package: 70 | platform: linux 71 | target: appimage 72 | obfuscate: 73 | split-debug-info: symbols 74 | - name: rpm 75 | package: 76 | platform: linux 77 | target: rpm 78 | obfuscate: 79 | split-debug-info: symbols -------------------------------------------------------------------------------- /lib/common/const.dart: -------------------------------------------------------------------------------- 1 | const String appTitle = 'Yolx'; 2 | const String githubURL = 'https://github.com/uiYzzi/Yolx'; 3 | const int defaultRPCPort = 16801; 4 | const double defaultWindowWidth = 900; 5 | const double defaultWindowHeight = 600; 6 | const int defaultMaxConcurrentDownloads = 5; 7 | const int defaultMaxConnectionPerServer = 16; 8 | const String defaultCompressedFilesRule = 9 | 'zip,rar,arj,gz,sit,sitx,sea,ace,bz2,7z'; 10 | const String defaultDocumentsRule = 'doc,pdf,ppt,pps,docx,pptx'; 11 | const String defaultMusicRule = 'mp3,wav,wma,mpa,ram,ra,aac,aif,m4a,tsa'; 12 | const String defaultProgramsRule = 'exe,msi'; 13 | const String defaultVideosRule = 14 | 'avi,mpg,mpe,mpeg,asf,wmv,mov,qt,rm,mp4,flv,m4v,webm,ogv,ogg,mkv,ts,tsv'; 15 | const String rpcURLValue = 'http://127.0.0.1:{port}/jsonrpc'; 16 | const String defaultTrackerSubscriptionAddress = 17 | 'https://cdn.jsdelivr.net/gh/ngosang/trackerslist@master/trackers_all.txt,https://cf.trackerslist.com/all.txt'; 18 | const List sizeUnits = [ 19 | 'B', 20 | 'KB', 21 | 'MB', 22 | 'GB', 23 | 'TB', 24 | 'PB', 25 | 'EB', 26 | 'ZB', 27 | 'YB' 28 | ]; 29 | -------------------------------------------------------------------------------- /lib/common/global.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:fluent_ui/fluent_ui.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | import 'package:yolx/common/const.dart'; 7 | import 'package:yolx/theme.dart'; 8 | // ignore: library_prefixes 9 | 10 | class Global { 11 | static late SharedPreferences prefs; 12 | static final appTheme = AppTheme(); 13 | static String pathSeparator = Platform.pathSeparator; 14 | static int rpcPort = defaultRPCPort; 15 | static String rpcSecret = ''; 16 | static String ua = ''; 17 | static String proxy = ''; 18 | static String bypassProxy = ''; 19 | static String downloadPath = ''; 20 | static String trackerSubscriptionAddress = defaultTrackerSubscriptionAddress; 21 | static String trackerServersList = ''; 22 | static double windowWidth = defaultWindowWidth; 23 | static double windowHeight = defaultWindowHeight; 24 | static bool rememberWindowSize = true; 25 | static double maxOverallDownloadLimit = 0; 26 | static double maxDownloadLimit = 0; 27 | static double lowestDownloadLimit = 0; 28 | static double maxOverallUploadLimit = 0; 29 | static double maxUploadLimit = 0; 30 | static String rpcUrl = 31 | rpcURLValue.replaceAll('{port}', Global.rpcPort.toString()); 32 | static bool classificationSaving = false; 33 | static bool isAutoUpdateTrackerList = true; 34 | static String compressedFilesRule = defaultCompressedFilesRule; 35 | static String documentsRule = defaultDocumentsRule; 36 | static String musicRule = defaultMusicRule; 37 | static String programsRule = defaultProgramsRule; 38 | static String videosRule = defaultVideosRule; 39 | static int maxConcurrentDownloads = defaultMaxConcurrentDownloads; 40 | static int maxConnectionPerServer = defaultMaxConnectionPerServer; 41 | static bool silentStart = false; 42 | static Future init() async { 43 | WidgetsFlutterBinding.ensureInitialized(); 44 | prefs = await SharedPreferences.getInstance(); 45 | appTheme.mode = ThemeMode.values[prefs.getInt('ThemeMode') ?? 0]; 46 | if (prefs.getString('Language')?.split('_').length == 2) { 47 | appTheme.locale = Locale( 48 | prefs.getString('Language')?.split('_')[0] ?? 'zh', 49 | prefs.getString('Language')?.split('_')[1] ?? 'CN'); 50 | } else { 51 | appTheme.locale = Locale(prefs.getString('Language') ?? 'en'); 52 | } 53 | 54 | appTheme.displayMode = 55 | PaneDisplayMode.values[prefs.getInt('NavigationMode') ?? 4]; 56 | appTheme.indicator = 57 | NavigationIndicators.values[prefs.getInt('NavigationIndicator') ?? 0]; 58 | rpcPort = prefs.getInt('RPCPort') ?? defaultRPCPort; 59 | rpcSecret = prefs.getString('RPCSecret') ?? ''; 60 | ua = prefs.getString('UA') ?? 61 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'; 62 | proxy = prefs.getString('Proxy') ?? ''; 63 | bypassProxy = prefs.getString('BypassProxy') ?? ''; 64 | Directory? dir; 65 | if (Platform.isAndroid) { 66 | dir = Directory('/storage/emulated/0/Download'); 67 | } else { 68 | dir = await getDownloadsDirectory(); 69 | } 70 | downloadPath = (prefs.getString('DownloadPath') ?? dir?.path)!; 71 | rememberWindowSize = prefs.getBool('RememberWindowSize') ?? true; 72 | isAutoUpdateTrackerList = prefs.getBool('IsAutoUpdateTrackerList') ?? true; 73 | if (rememberWindowSize) { 74 | windowWidth = prefs.getDouble('WindowWidth') ?? defaultWindowWidth; 75 | windowHeight = prefs.getDouble('WindowHeight') ?? defaultWindowHeight; 76 | } 77 | maxOverallDownloadLimit = prefs.getDouble('MaxOverallDownloadLimit') ?? 0; 78 | maxDownloadLimit = prefs.getDouble('MaxDownloadLimit') ?? 0; 79 | lowestDownloadLimit = prefs.getDouble('LowestDownloadLimit') ?? 0; 80 | maxOverallUploadLimit = prefs.getDouble('MaxOverallUploadLimit') ?? 0; 81 | maxUploadLimit = prefs.getDouble('MaxUploadLimit') ?? 0; 82 | classificationSaving = prefs.getBool('ClassificationSaving') ?? false; 83 | compressedFilesRule = 84 | prefs.getString('CompressedFilesRule') ?? defaultCompressedFilesRule; 85 | documentsRule = prefs.getString('DocumentsRule') ?? defaultDocumentsRule; 86 | musicRule = prefs.getString('MusicRule') ?? defaultMusicRule; 87 | programsRule = prefs.getString('ProgramsRule') ?? defaultProgramsRule; 88 | videosRule = prefs.getString('VideosRule') ?? defaultVideosRule; 89 | maxConcurrentDownloads = 90 | prefs.getInt('MaxConcurrentDownloads') ?? defaultMaxConcurrentDownloads; 91 | maxConnectionPerServer = 92 | prefs.getInt('MaxConnectionPerServer') ?? defaultMaxConnectionPerServer; 93 | if (maxConcurrentDownloads < 1) { 94 | maxConcurrentDownloads = 1; 95 | } 96 | if (maxConnectionPerServer < 1) { 97 | maxConnectionPerServer = 1; 98 | } 99 | if (maxConnectionPerServer > 16) { 100 | maxConnectionPerServer = 16; 101 | } 102 | trackerSubscriptionAddress = 103 | prefs.getString('TrackerSubscriptionAddress') ?? 104 | defaultTrackerSubscriptionAddress; 105 | trackerServersList = prefs.getString('TrackerServersList') ?? ''; 106 | silentStart = prefs.getBool('SilentStart') ?? false; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | // Ignore issues from commonly used lints in this file. 6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new 7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering 8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment 9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases 10 | // ignore_for_file:comment_references 11 | 12 | import 'dart:async'; 13 | 14 | import 'package:flutter/foundation.dart'; 15 | import 'package:intl/intl.dart'; 16 | import 'package:intl/message_lookup_by_library.dart'; 17 | import 'package:intl/src/intl_helpers.dart'; 18 | 19 | import 'messages_en.dart' as messages_en; 20 | import 'messages_zh_CN.dart' as messages_zh_cn; 21 | 22 | typedef Future LibraryLoader(); 23 | Map _deferredLibraries = { 24 | 'en': () => new SynchronousFuture(null), 25 | 'zh_CN': () => new SynchronousFuture(null), 26 | }; 27 | 28 | MessageLookupByLibrary? _findExact(String localeName) { 29 | switch (localeName) { 30 | case 'en': 31 | return messages_en.messages; 32 | case 'zh_CN': 33 | return messages_zh_cn.messages; 34 | default: 35 | return null; 36 | } 37 | } 38 | 39 | /// User programs should call this before using [localeName] for messages. 40 | Future initializeMessages(String localeName) { 41 | var availableLocale = Intl.verifiedLocale( 42 | localeName, (locale) => _deferredLibraries[locale] != null, 43 | onFailure: (_) => null); 44 | if (availableLocale == null) { 45 | return new SynchronousFuture(false); 46 | } 47 | var lib = _deferredLibraries[availableLocale]; 48 | lib == null ? new SynchronousFuture(false) : lib(); 49 | initializeInternalMessageLookup(() => new CompositeMessageLookup()); 50 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 51 | return new SynchronousFuture(true); 52 | } 53 | 54 | bool _messagesExistFor(String locale) { 55 | try { 56 | return _findExact(locale) != null; 57 | } catch (e) { 58 | return false; 59 | } 60 | } 61 | 62 | MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { 63 | var actualLocale = 64 | Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); 65 | if (actualLocale == null) return null; 66 | return _findExact(actualLocale); 67 | } 68 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_en.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a en locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes 11 | // ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes 12 | 13 | import 'package:intl/intl.dart'; 14 | import 'package:intl/message_lookup_by_library.dart'; 15 | 16 | final messages = new MessageLookup(); 17 | 18 | typedef String MessageIfAbsent(String messageStr, List args); 19 | 20 | class MessageLookup extends MessageLookupByLibrary { 21 | String get localeName => 'en'; 22 | 23 | final messages = _notInlinedMessages(_notInlinedMessages); 24 | static Map _notInlinedMessages(_) => { 25 | "RPC": MessageLookupByLibrary.simpleMessage("RPC"), 26 | "RPCInfo": MessageLookupByLibrary.simpleMessage( 27 | "Restarting aria2 service to take effect settings."), 28 | "RPCListenPort": 29 | MessageLookupByLibrary.simpleMessage("RPC Listen Port"), 30 | "RPCSecret": MessageLookupByLibrary.simpleMessage("RPC Secret"), 31 | "UA": MessageLookupByLibrary.simpleMessage("User-Agent"), 32 | "URL": MessageLookupByLibrary.simpleMessage("URL"), 33 | "URLTextBox": MessageLookupByLibrary.simpleMessage( 34 | "One task url per line (supports magnet , Flashget:// , qqdl:// and thunder://)"), 35 | "advanced": MessageLookupByLibrary.simpleMessage("Advanced"), 36 | "autoUpdateTrackerList": MessageLookupByLibrary.simpleMessage( 37 | "Update tracker list every day automatically"), 38 | "basic": MessageLookupByLibrary.simpleMessage("Basic"), 39 | "bypassProxy": MessageLookupByLibrary.simpleMessage( 40 | "Bypass proxy settings for these Hosts and Domains,a comma separated"), 41 | "cancel": MessageLookupByLibrary.simpleMessage("Cancel"), 42 | "classificationSaving": 43 | MessageLookupByLibrary.simpleMessage("Classification Saving"), 44 | "classificationSavingInfo": MessageLookupByLibrary.simpleMessage( 45 | "Enabling category saving will create category directories in the download path."), 46 | "classificationSavingRules": 47 | MessageLookupByLibrary.simpleMessage("Classification Saving Rules"), 48 | "classificationSavingRulesInfo": MessageLookupByLibrary.simpleMessage( 49 | "Customization of classification saving rules"), 50 | "closeInfo": MessageLookupByLibrary.simpleMessage( 51 | "Are you sure you want to close this window?"), 52 | "compressedFiles": 53 | MessageLookupByLibrary.simpleMessage("Compressed-Files"), 54 | "confirmClose": MessageLookupByLibrary.simpleMessage("Confirm close"), 55 | "delete": MessageLookupByLibrary.simpleMessage("Delete"), 56 | "deleteAllTasks": 57 | MessageLookupByLibrary.simpleMessage("Delete All Tasks"), 58 | "deleteFile": MessageLookupByLibrary.simpleMessage( 59 | "Delete files when removing tasks"), 60 | "deleteThisTasks": 61 | MessageLookupByLibrary.simpleMessage("Delete This Tasks"), 62 | "documents": MessageLookupByLibrary.simpleMessage("Documents"), 63 | "downloadPath": MessageLookupByLibrary.simpleMessage("Download Path"), 64 | "downloadPathInfo": 65 | MessageLookupByLibrary.simpleMessage("Set default download path"), 66 | "downloading": MessageLookupByLibrary.simpleMessage("Downloading"), 67 | "dropTorrent": MessageLookupByLibrary.simpleMessage( 68 | "Drag the torrent/metalink file here or click here to open it"), 69 | "exitApp": MessageLookupByLibrary.simpleMessage("Exit App"), 70 | "general": MessageLookupByLibrary.simpleMessage("General"), 71 | "language": MessageLookupByLibrary.simpleMessage("Language"), 72 | "lowestDownloadLimit": 73 | MessageLookupByLibrary.simpleMessage("Lowest Download Limit"), 74 | "lowestDownloadLimitInfo": MessageLookupByLibrary.simpleMessage( 75 | "Close connection if download speed is lower than or equal to this value."), 76 | "maxConcurrentDownloads": 77 | MessageLookupByLibrary.simpleMessage("Max Concurrent Downloads"), 78 | "maxConnectionPerServer": 79 | MessageLookupByLibrary.simpleMessage("Max Connection Per Server"), 80 | "maxDownloadLimit": 81 | MessageLookupByLibrary.simpleMessage("Max Download Limit"), 82 | "maxOverallDownloadLimit": 83 | MessageLookupByLibrary.simpleMessage("Max Overall Download Limit"), 84 | "maxOverallUploadLimit": 85 | MessageLookupByLibrary.simpleMessage("Max Overall Upload Limit"), 86 | "maxUploadLimit": 87 | MessageLookupByLibrary.simpleMessage("Max Upload Limit"), 88 | "mockUA": MessageLookupByLibrary.simpleMessage("Mock User-Agent."), 89 | "music": MessageLookupByLibrary.simpleMessage("Music"), 90 | "navigationIndicator": 91 | MessageLookupByLibrary.simpleMessage("Navigation Indicator"), 92 | "navigationMode": 93 | MessageLookupByLibrary.simpleMessage("Navigation Mode"), 94 | "newDownload": MessageLookupByLibrary.simpleMessage("New"), 95 | "no": MessageLookupByLibrary.simpleMessage("No"), 96 | "noTaskDownloaded": 97 | MessageLookupByLibrary.simpleMessage("There are no current tasks"), 98 | "openDirectory": MessageLookupByLibrary.simpleMessage("Open Directory"), 99 | "openFile": MessageLookupByLibrary.simpleMessage("Open File"), 100 | "path": MessageLookupByLibrary.simpleMessage("Path"), 101 | "pauseAllTasks": 102 | MessageLookupByLibrary.simpleMessage("Pause All Tasks"), 103 | "pauseThisTasks": 104 | MessageLookupByLibrary.simpleMessage("Pause This Tasks"), 105 | "programs": MessageLookupByLibrary.simpleMessage("Programs"), 106 | "proxy": MessageLookupByLibrary.simpleMessage("Proxy"), 107 | "purgeTaskRecord": 108 | MessageLookupByLibrary.simpleMessage("Purge Task Record"), 109 | "refreshTaskList": 110 | MessageLookupByLibrary.simpleMessage("Refresh Task List"), 111 | "rememberWindowSize": 112 | MessageLookupByLibrary.simpleMessage("Remember Window Size"), 113 | "rememberWindowSizeInfo": MessageLookupByLibrary.simpleMessage( 114 | "Restore the previous window size at startup"), 115 | "removeTask": MessageLookupByLibrary.simpleMessage("Remove Task"), 116 | "removeTaskInfo": MessageLookupByLibrary.simpleMessage( 117 | "Are you sure you want to remove this download task?"), 118 | "resumeAllTasks": 119 | MessageLookupByLibrary.simpleMessage("Resume All Tasks"), 120 | "resumeThisTasks": 121 | MessageLookupByLibrary.simpleMessage("Resume This Tasks"), 122 | "saveApply": MessageLookupByLibrary.simpleMessage("Save & Apply"), 123 | "savedSuccessfully": MessageLookupByLibrary.simpleMessage( 124 | "Preferences saved successfully."), 125 | "setsAnOverrideForTheAppsPreferredLanguage": 126 | MessageLookupByLibrary.simpleMessage( 127 | "Sets an override for the app\'s preferred language."), 128 | "setsDownloadProxyServer": 129 | MessageLookupByLibrary.simpleMessage("Sets Download Proxy Server."), 130 | "setsTheDisplayModeOfTheNavigationPane": 131 | MessageLookupByLibrary.simpleMessage( 132 | "Sets the display mode of the navigation pane."), 133 | "setsTheRPCOfTheApplication": MessageLookupByLibrary.simpleMessage( 134 | "Sets the RPC of the application."), 135 | "setsTheStyleOfTheNavigationIndicator": 136 | MessageLookupByLibrary.simpleMessage( 137 | "Set the style of the navigation indicator."), 138 | "setsTheThemeOfTheApplication": MessageLookupByLibrary.simpleMessage( 139 | "Sets the theme of the application."), 140 | "settings": MessageLookupByLibrary.simpleMessage("Settings"), 141 | "showWindow": MessageLookupByLibrary.simpleMessage("Show Window"), 142 | "silentStart": MessageLookupByLibrary.simpleMessage("Silent Start"), 143 | "silentStartInfo": MessageLookupByLibrary.simpleMessage( 144 | "Minimize to tray after startup"), 145 | "sourceCode": MessageLookupByLibrary.simpleMessage("Source code"), 146 | "speedLimit": MessageLookupByLibrary.simpleMessage("Speed Limit"), 147 | "speedLimitInfo": MessageLookupByLibrary.simpleMessage( 148 | "Restrict download or upload speed"), 149 | "stopped": MessageLookupByLibrary.simpleMessage("Stopped"), 150 | "submit": MessageLookupByLibrary.simpleMessage("Submit"), 151 | "subscriptionAddress": 152 | MessageLookupByLibrary.simpleMessage("Subscription Address"), 153 | "subscriptionAddressInfo": MessageLookupByLibrary.simpleMessage( 154 | "Separate links with commas(,)"), 155 | "taskManagement": 156 | MessageLookupByLibrary.simpleMessage("Task Management"), 157 | "taskManagementInfo": MessageLookupByLibrary.simpleMessage( 158 | "Download task related settings"), 159 | "theme": MessageLookupByLibrary.simpleMessage("Theme"), 160 | "topping": MessageLookupByLibrary.simpleMessage("Topping"), 161 | "torrent": MessageLookupByLibrary.simpleMessage("Torrent"), 162 | "trackerServers": 163 | MessageLookupByLibrary.simpleMessage("Tracker Servers"), 164 | "trackerServersInfo": MessageLookupByLibrary.simpleMessage( 165 | "Set the Bt Tracker server address"), 166 | "trackerServersList": 167 | MessageLookupByLibrary.simpleMessage("Tracker Servers List"), 168 | "trackerServersListInfo": MessageLookupByLibrary.simpleMessage( 169 | "Tracker servers,one per line"), 170 | "videos": MessageLookupByLibrary.simpleMessage("Videos"), 171 | "waiting": MessageLookupByLibrary.simpleMessage("Waiting"), 172 | "yes": MessageLookupByLibrary.simpleMessage("Yes") 173 | }; 174 | } 175 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_zh_CN.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a zh_CN locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes 11 | // ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes 12 | 13 | import 'package:intl/intl.dart'; 14 | import 'package:intl/message_lookup_by_library.dart'; 15 | 16 | final messages = new MessageLookup(); 17 | 18 | typedef String MessageIfAbsent(String messageStr, List args); 19 | 20 | class MessageLookup extends MessageLookupByLibrary { 21 | String get localeName => 'zh_CN'; 22 | 23 | final messages = _notInlinedMessages(_notInlinedMessages); 24 | static Map _notInlinedMessages(_) => { 25 | "RPC": MessageLookupByLibrary.simpleMessage("RPC"), 26 | "RPCInfo": MessageLookupByLibrary.simpleMessage("正在重启aria2服务以生效设置项"), 27 | "RPCListenPort": MessageLookupByLibrary.simpleMessage("RPC侦听端口"), 28 | "RPCSecret": MessageLookupByLibrary.simpleMessage("RPC密钥"), 29 | "UA": MessageLookupByLibrary.simpleMessage("User-Agent"), 30 | "URL": MessageLookupByLibrary.simpleMessage("链接任务"), 31 | "URLTextBox": MessageLookupByLibrary.simpleMessage( 32 | "添加多个下载链接时,请确保每行只有一个链接(支持磁力链、Flashget://、qqdl://和thunder://)"), 33 | "advanced": MessageLookupByLibrary.simpleMessage("进阶"), 34 | "autoUpdateTrackerList": 35 | MessageLookupByLibrary.simpleMessage("每天自动更新 Tracker 服务器列表"), 36 | "basic": MessageLookupByLibrary.simpleMessage("基础"), 37 | "bypassProxy": 38 | MessageLookupByLibrary.simpleMessage("绕过这些主机和域的代理设置,多个使用逗号分隔"), 39 | "cancel": MessageLookupByLibrary.simpleMessage("取消"), 40 | "classificationSaving": MessageLookupByLibrary.simpleMessage("分类存放"), 41 | "classificationSavingInfo": 42 | MessageLookupByLibrary.simpleMessage("启用分类存放后将在下载路径创建分类目录"), 43 | "classificationSavingRules": 44 | MessageLookupByLibrary.simpleMessage("分类存放规则"), 45 | "classificationSavingRulesInfo": 46 | MessageLookupByLibrary.simpleMessage("自定义分类存放规则"), 47 | "closeInfo": MessageLookupByLibrary.simpleMessage("您确定要关闭此窗口吗?"), 48 | "compressedFiles": MessageLookupByLibrary.simpleMessage("压缩文件"), 49 | "confirmClose": MessageLookupByLibrary.simpleMessage("确认关闭"), 50 | "delete": MessageLookupByLibrary.simpleMessage("移除"), 51 | "deleteAllTasks": MessageLookupByLibrary.simpleMessage("删除所有任务"), 52 | "deleteFile": MessageLookupByLibrary.simpleMessage("移除任务时删除文件"), 53 | "deleteThisTasks": MessageLookupByLibrary.simpleMessage("删除本任务"), 54 | "documents": MessageLookupByLibrary.simpleMessage("文档"), 55 | "downloadPath": MessageLookupByLibrary.simpleMessage("下载路径"), 56 | "downloadPathInfo": MessageLookupByLibrary.simpleMessage("设置默认下载路径"), 57 | "downloading": MessageLookupByLibrary.simpleMessage("下载中"), 58 | "dropTorrent": 59 | MessageLookupByLibrary.simpleMessage("拖动torrent/metalink文件到此或点击打开"), 60 | "exitApp": MessageLookupByLibrary.simpleMessage("退出程序"), 61 | "general": MessageLookupByLibrary.simpleMessage("常规"), 62 | "language": MessageLookupByLibrary.simpleMessage("语言"), 63 | "lowestDownloadLimit": 64 | MessageLookupByLibrary.simpleMessage("最小单任务下载速度"), 65 | "lowestDownloadLimitInfo": 66 | MessageLookupByLibrary.simpleMessage("当下载速度低于此选项设置的值时将会关闭连接"), 67 | "maxConcurrentDownloads": 68 | MessageLookupByLibrary.simpleMessage("最大同时下载数"), 69 | "maxConnectionPerServer": 70 | MessageLookupByLibrary.simpleMessage("单服务器最大连接数"), 71 | "maxDownloadLimit": MessageLookupByLibrary.simpleMessage("最大单任务下载速度"), 72 | "maxOverallDownloadLimit": 73 | MessageLookupByLibrary.simpleMessage("最大全局下载速度"), 74 | "maxOverallUploadLimit": 75 | MessageLookupByLibrary.simpleMessage("最大全局上传速度"), 76 | "maxUploadLimit": MessageLookupByLibrary.simpleMessage("最大单任务上传速度"), 77 | "mockUA": MessageLookupByLibrary.simpleMessage("模拟用户代理"), 78 | "music": MessageLookupByLibrary.simpleMessage("音乐"), 79 | "navigationIndicator": MessageLookupByLibrary.simpleMessage("导航指示器"), 80 | "navigationMode": MessageLookupByLibrary.simpleMessage("导航模式"), 81 | "newDownload": MessageLookupByLibrary.simpleMessage("新建"), 82 | "no": MessageLookupByLibrary.simpleMessage("不是"), 83 | "noTaskDownloaded": MessageLookupByLibrary.simpleMessage("当前没有下载任务"), 84 | "openDirectory": MessageLookupByLibrary.simpleMessage("打开目录"), 85 | "openFile": MessageLookupByLibrary.simpleMessage("打开文件"), 86 | "path": MessageLookupByLibrary.simpleMessage("存储路径"), 87 | "pauseAllTasks": MessageLookupByLibrary.simpleMessage("暂停所有任务"), 88 | "pauseThisTasks": MessageLookupByLibrary.simpleMessage("暂停本任务"), 89 | "programs": MessageLookupByLibrary.simpleMessage("程序"), 90 | "proxy": MessageLookupByLibrary.simpleMessage("代理"), 91 | "purgeTaskRecord": MessageLookupByLibrary.simpleMessage("清除任务记录"), 92 | "refreshTaskList": MessageLookupByLibrary.simpleMessage("刷新任务列表"), 93 | "rememberWindowSize": MessageLookupByLibrary.simpleMessage("记住窗口大小"), 94 | "rememberWindowSizeInfo": 95 | MessageLookupByLibrary.simpleMessage("启动时恢复上次的窗口大小"), 96 | "removeTask": MessageLookupByLibrary.simpleMessage("移除任务"), 97 | "removeTaskInfo": MessageLookupByLibrary.simpleMessage("您确定要移除本下载任务吗?"), 98 | "resumeAllTasks": MessageLookupByLibrary.simpleMessage("继续所有任务"), 99 | "resumeThisTasks": MessageLookupByLibrary.simpleMessage("继续本任务"), 100 | "saveApply": MessageLookupByLibrary.simpleMessage("保存并应用"), 101 | "savedSuccessfully": MessageLookupByLibrary.simpleMessage("已成功保存首选项"), 102 | "setsAnOverrideForTheAppsPreferredLanguage": 103 | MessageLookupByLibrary.simpleMessage("设置应用程序首选语言的覆盖"), 104 | "setsDownloadProxyServer": 105 | MessageLookupByLibrary.simpleMessage("设置下载代理服务器"), 106 | "setsTheDisplayModeOfTheNavigationPane": 107 | MessageLookupByLibrary.simpleMessage("设置导航窗格的显示模式"), 108 | "setsTheRPCOfTheApplication": 109 | MessageLookupByLibrary.simpleMessage("设置应用程序的RPC"), 110 | "setsTheStyleOfTheNavigationIndicator": 111 | MessageLookupByLibrary.simpleMessage("设置导航指示器的样式"), 112 | "setsTheThemeOfTheApplication": 113 | MessageLookupByLibrary.simpleMessage("设置应用程序的主题"), 114 | "settings": MessageLookupByLibrary.simpleMessage("设置"), 115 | "showWindow": MessageLookupByLibrary.simpleMessage("显示窗口"), 116 | "silentStart": MessageLookupByLibrary.simpleMessage("静默启动"), 117 | "silentStartInfo": MessageLookupByLibrary.simpleMessage("启动后最小化到托盘"), 118 | "sourceCode": MessageLookupByLibrary.simpleMessage("源代码"), 119 | "speedLimit": MessageLookupByLibrary.simpleMessage("速度限制"), 120 | "speedLimitInfo": MessageLookupByLibrary.simpleMessage("限制下载或上传速度"), 121 | "stopped": MessageLookupByLibrary.simpleMessage("已停止"), 122 | "submit": MessageLookupByLibrary.simpleMessage("提交"), 123 | "subscriptionAddress": MessageLookupByLibrary.simpleMessage("订阅地址"), 124 | "subscriptionAddressInfo": 125 | MessageLookupByLibrary.simpleMessage("请以英文逗号(,)分隔链接"), 126 | "taskManagement": MessageLookupByLibrary.simpleMessage("任务管理"), 127 | "taskManagementInfo": MessageLookupByLibrary.simpleMessage("下载任务相关设置项"), 128 | "theme": MessageLookupByLibrary.simpleMessage("主题"), 129 | "topping": MessageLookupByLibrary.simpleMessage("优先下载"), 130 | "torrent": MessageLookupByLibrary.simpleMessage("种子任务"), 131 | "trackerServers": MessageLookupByLibrary.simpleMessage("Tracker 服务器"), 132 | "trackerServersInfo": 133 | MessageLookupByLibrary.simpleMessage("设置Bt Tracker服务器地址"), 134 | "trackerServersList": 135 | MessageLookupByLibrary.simpleMessage("Tracker 服务器列表"), 136 | "trackerServersListInfo": 137 | MessageLookupByLibrary.simpleMessage("Tracker 服务器,一行一个"), 138 | "videos": MessageLookupByLibrary.simpleMessage("视频"), 139 | "waiting": MessageLookupByLibrary.simpleMessage("等待中"), 140 | "yes": MessageLookupByLibrary.simpleMessage("是的") 141 | }; 142 | } 143 | -------------------------------------------------------------------------------- /lib/l10n/intl_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "downloading":"Downloading", 3 | "waiting":"Waiting", 4 | "stopped":"Stopped", 5 | "settings":"Settings", 6 | "deleteAllTasks":"Delete All Tasks", 7 | "refreshTaskList":"Refresh Task List", 8 | "resumeAllTasks":"Resume All Tasks", 9 | "pauseAllTasks":"Pause All Tasks", 10 | "purgeTaskRecord":"Purge Task Record", 11 | "basic":"Basic", 12 | "theme":"Theme", 13 | "setsTheThemeOfTheApplication":"Sets the theme of the application.", 14 | "language":"Language", 15 | "setsAnOverrideForTheAppsPreferredLanguage":"Sets an override for the app's preferred language.", 16 | "navigationMode":"Navigation Mode", 17 | "setsTheDisplayModeOfTheNavigationPane":"Sets the display mode of the navigation pane.", 18 | "navigationIndicator":"Navigation Indicator", 19 | "setsTheStyleOfTheNavigationIndicator":"Set the style of the navigation indicator.", 20 | "advanced":"Advanced", 21 | "setsDownloadProxyServer":"Sets Download Proxy Server.", 22 | "proxy":"Proxy", 23 | "bypassProxy":"Bypass proxy settings for these Hosts and Domains,a comma separated", 24 | "saveApply":"Save & Apply", 25 | "savedSuccessfully":"Preferences saved successfully.", 26 | "RPC":"RPC", 27 | "setsTheRPCOfTheApplication":"Sets the RPC of the application.", 28 | "RPCListenPort":"RPC Listen Port", 29 | "RPCInfo":"Restarting aria2 service to take effect settings.", 30 | "RPCSecret":"RPC Secret", 31 | "UA":"User-Agent", 32 | "mockUA":"Mock User-Agent.", 33 | "sourceCode":"Source code", 34 | "confirmClose":"Confirm close", 35 | "closeInfo":"Are you sure you want to close this window?", 36 | "yes":"Yes", 37 | "no":"No", 38 | "downloadPath":"Download Path", 39 | "downloadPathInfo":"Set default download path", 40 | "newDownload":"New", 41 | "URL":"URL", 42 | "torrent":"Torrent", 43 | "URLTextBox":"One task url per line (supports magnet , Flashget:// , qqdl:// and thunder://)", 44 | "cancel":"Cancel", 45 | "submit":"Submit", 46 | "path":"Path", 47 | "deleteThisTasks":"Delete This Tasks", 48 | "resumeThisTasks":"Resume This Tasks", 49 | "pauseThisTasks":"Pause This Tasks", 50 | "rememberWindowSize":"Remember Window Size", 51 | "rememberWindowSizeInfo":"Restore the previous window size at startup", 52 | "topping":"Topping", 53 | "speedLimit":"Speed Limit", 54 | "speedLimitInfo":"Restrict download or upload speed", 55 | "maxOverallDownloadLimit":"Max Overall Download Limit", 56 | "maxDownloadLimit":"Max Download Limit", 57 | "maxOverallUploadLimit":"Max Overall Upload Limit", 58 | "maxUploadLimit":"Max Upload Limit", 59 | "classificationSaving":"Classification Saving", 60 | "classificationSavingInfo":"Enabling category saving will create category directories in the download path.", 61 | "general":"General", 62 | "compressedFiles":"Compressed-Files", 63 | "documents":"Documents", 64 | "music":"Music", 65 | "programs":"Programs", 66 | "videos":"Videos", 67 | "classificationSavingRules":"Classification Saving Rules", 68 | "classificationSavingRulesInfo":"Customization of classification saving rules", 69 | "taskManagement":"Task Management", 70 | "taskManagementInfo":"Download task related settings", 71 | "maxConcurrentDownloads": "Max Concurrent Downloads", 72 | "maxConnectionPerServer":"Max Connection Per Server", 73 | "removeTask":"Remove Task", 74 | "removeTaskInfo":"Are you sure you want to remove this download task?", 75 | "delete":"Delete", 76 | "deleteFile":"Delete files when removing tasks", 77 | "showWindow":"Show Window", 78 | "exitApp":"Exit App", 79 | "openDirectory":"Open Directory", 80 | "openFile":"Open File", 81 | "dropTorrent":"Drag the torrent/metalink file here or click here to open it", 82 | "lowestDownloadLimit":"Lowest Download Limit", 83 | "lowestDownloadLimitInfo":"Close connection if download speed is lower than or equal to this value.", 84 | "trackerServers":"Tracker Servers", 85 | "trackerServersList":"Tracker Servers List", 86 | "trackerServersListInfo":"Tracker servers,one per line", 87 | "trackerServersInfo":"Set the Bt Tracker server address", 88 | "subscriptionAddress":"Subscription Address", 89 | "subscriptionAddressInfo":"Separate links with commas(,)", 90 | "autoUpdateTrackerList":"Update tracker list every day automatically", 91 | "noTaskDownloaded":"There are no current tasks", 92 | "silentStart":"Silent Start", 93 | "silentStartInfo":"Minimize to tray after startup" 94 | } -------------------------------------------------------------------------------- /lib/l10n/intl_zh_CN.arb: -------------------------------------------------------------------------------- 1 | { 2 | "downloading":"下载中", 3 | "waiting":"等待中", 4 | "stopped":"已停止", 5 | "settings":"设置", 6 | "deleteAllTasks":"删除所有任务", 7 | "refreshTaskList":"刷新任务列表", 8 | "resumeAllTasks":"继续所有任务", 9 | "pauseAllTasks":"暂停所有任务", 10 | "purgeTaskRecord":"清除任务记录", 11 | "basic":"基础", 12 | "theme":"主题", 13 | "setsTheThemeOfTheApplication":"设置应用程序的主题", 14 | "language":"语言", 15 | "setsAnOverrideForTheAppsPreferredLanguage":"设置应用程序首选语言的覆盖", 16 | "navigationMode":"导航模式", 17 | "setsTheDisplayModeOfTheNavigationPane":"设置导航窗格的显示模式", 18 | "navigationIndicator":"导航指示器", 19 | "setsTheStyleOfTheNavigationIndicator":"设置导航指示器的样式", 20 | "advanced":"进阶", 21 | "setsDownloadProxyServer":"设置下载代理服务器", 22 | "proxy":"代理", 23 | "bypassProxy":"绕过这些主机和域的代理设置,多个使用逗号分隔", 24 | "saveApply":"保存并应用", 25 | "savedSuccessfully":"已成功保存首选项", 26 | "RPC":"RPC", 27 | "setsTheRPCOfTheApplication":"设置应用程序的RPC", 28 | "RPCListenPort":"RPC侦听端口", 29 | "RPCInfo":"正在重启aria2服务以生效设置项", 30 | "RPCSecret":"RPC密钥", 31 | "UA":"User-Agent", 32 | "mockUA":"模拟用户代理", 33 | "sourceCode":"源代码", 34 | "confirmClose":"确认关闭", 35 | "closeInfo":"您确定要关闭此窗口吗?", 36 | "yes":"是的", 37 | "no":"不是", 38 | "downloadPath":"下载路径", 39 | "downloadPathInfo":"设置默认下载路径", 40 | "newDownload":"新建", 41 | "URL":"链接任务", 42 | "torrent":"种子任务", 43 | "URLTextBox":"添加多个下载链接时,请确保每行只有一个链接(支持磁力链、Flashget://、qqdl://和thunder://)", 44 | "cancel":"取消", 45 | "submit":"提交", 46 | "path":"存储路径", 47 | "deleteThisTasks":"删除本任务", 48 | "resumeThisTasks":"继续本任务", 49 | "pauseThisTasks":"暂停本任务", 50 | "rememberWindowSize":"记住窗口大小", 51 | "rememberWindowSizeInfo":"启动时恢复上次的窗口大小", 52 | "topping":"优先下载", 53 | "speedLimit":"速度限制", 54 | "speedLimitInfo":"限制下载或上传速度", 55 | "maxOverallDownloadLimit":"最大全局下载速度", 56 | "maxDownloadLimit":"最大单任务下载速度", 57 | "maxOverallUploadLimit":"最大全局上传速度", 58 | "maxUploadLimit":"最大单任务上传速度", 59 | "classificationSaving":"分类存放", 60 | "classificationSavingInfo":"启用分类存放后将在下载路径创建分类目录", 61 | "general":"常规", 62 | "compressedFiles":"压缩文件", 63 | "documents":"文档", 64 | "music":"音乐", 65 | "programs":"程序", 66 | "videos":"视频", 67 | "classificationSavingRules":"分类存放规则", 68 | "classificationSavingRulesInfo":"自定义分类存放规则", 69 | "taskManagement":"任务管理", 70 | "taskManagementInfo":"下载任务相关设置项", 71 | "maxConcurrentDownloads": "最大同时下载数", 72 | "maxConnectionPerServer":"单服务器最大连接数", 73 | "removeTask":"移除任务", 74 | "removeTaskInfo":"您确定要移除本下载任务吗?", 75 | "delete":"移除", 76 | "deleteFile":"移除任务时删除文件", 77 | "showWindow":"显示窗口", 78 | "exitApp":"退出程序", 79 | "openDirectory":"打开目录", 80 | "openFile":"打开文件", 81 | "dropTorrent":"拖动torrent/metalink文件到此或点击打开", 82 | "lowestDownloadLimit":"最小单任务下载速度", 83 | "lowestDownloadLimitInfo":"当下载速度低于此选项设置的值时将会关闭连接", 84 | "trackerServers":"Tracker 服务器", 85 | "trackerServersList":"Tracker 服务器列表", 86 | "trackerServersListInfo":"Tracker 服务器,一行一个", 87 | "trackerServersInfo":"设置Bt Tracker服务器地址", 88 | "subscriptionAddress":"订阅地址", 89 | "subscriptionAddressInfo":"请以英文逗号(,)分隔链接", 90 | "autoUpdateTrackerList":"每天自动更新 Tracker 服务器列表", 91 | "noTaskDownloaded":"当前没有下载任务", 92 | "silentStart":"静默启动", 93 | "silentStartInfo":"启动后最小化到托盘" 94 | } -------------------------------------------------------------------------------- /lib/model/download_item.dart: -------------------------------------------------------------------------------- 1 | class DownloadItem { 2 | int completedLength; 3 | String path; 4 | String connections; 5 | int downloadSpeed; 6 | String gid; 7 | String status; 8 | int totalLength; 9 | int uploadSpeed; 10 | 11 | DownloadItem({ 12 | required this.completedLength, 13 | required this.path, 14 | required this.connections, 15 | required this.downloadSpeed, 16 | required this.gid, 17 | required this.status, 18 | required this.totalLength, 19 | required this.uploadSpeed, 20 | }); 21 | 22 | factory DownloadItem.fromJson(Map json) { 23 | return DownloadItem( 24 | completedLength: json['completedLength'], 25 | path: json['path'], 26 | connections: json['connections'], 27 | downloadSpeed: json['downloadSpeed'], 28 | gid: json['gid'], 29 | status: json['status'], 30 | totalLength: json['totalLength'], 31 | uploadSpeed: json['uploadSpeed'], 32 | ); 33 | } 34 | 35 | Map toJson() { 36 | return { 37 | 'completedLength': completedLength, 38 | 'path': path, 39 | 'connections': connections, 40 | 'downloadSpeed': downloadSpeed, 41 | 'gid': gid, 42 | 'status': status, 43 | 'totalLength': totalLength, 44 | 'uploadSpeed': uploadSpeed, 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/model/download_list_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:yolx/model/download_item.dart'; 5 | import 'package:yolx/utils/file_utils.dart'; 6 | import 'package:yolx/utils/log.dart'; 7 | 8 | class DownloadListModel extends ChangeNotifier { 9 | List _downloadingList = []; 10 | List _waitingList = []; 11 | List _stoppedList = []; 12 | List _historyList = []; 13 | 14 | List get downloadingList => _downloadingList; 15 | List get waitingList => _waitingList; 16 | List get stoppedList { 17 | List combinedList = List.from(_stoppedList); 18 | List downloadGids = _stoppedList.map((item) => item.gid).toList(); 19 | List uniqueHistoryItems = 20 | _historyList.where((item) => !downloadGids.contains(item.gid)).toList(); 21 | combinedList.addAll(uniqueHistoryItems); 22 | return combinedList; 23 | } 24 | 25 | void removeAllFromHistoryList() { 26 | _historyList.clear(); 27 | saveHistoryListToJson(); 28 | notifyListeners(); 29 | } 30 | 31 | void removeFromHistoryList(String gid) { 32 | _historyList.removeWhere((item) => item.gid == gid); 33 | saveHistoryListToJson(); 34 | notifyListeners(); 35 | } 36 | 37 | Future saveHistoryListToJson() async { 38 | updateHistoryList(); 39 | final historyListFile = await getLocalFile('historyList.json'); 40 | List> jsonList = 41 | _historyList.map((item) => item.toJson()).toList(); 42 | String jsonString = json.encode(jsonList); 43 | await historyListFile.writeAsString(jsonString); 44 | } 45 | 46 | Future loadHistoryListFromJson() async { 47 | try { 48 | final historyListFile = await getLocalFile('historyList.json'); 49 | String jsonString = await historyListFile.readAsString(); 50 | List jsonData = json.decode(jsonString); 51 | _historyList = 52 | jsonData.map((itemJson) => DownloadItem.fromJson(itemJson)).toList(); 53 | notifyListeners(); 54 | } catch (e) { 55 | Log.e(e); 56 | } 57 | } 58 | 59 | void updateHistoryList() { 60 | var completedItems = 61 | _stoppedList.where((item) => item.status == "complete").toList(); 62 | for (var item in completedItems) { 63 | item.status = "history"; 64 | } 65 | 66 | List historyIds = _historyList.map((item) => item.gid).toList(); 67 | _historyList.insertAll( 68 | 0, completedItems.where((item) => !historyIds.contains(item.gid))); 69 | } 70 | 71 | void updateStoppedList(List newList) { 72 | if (_stoppedList.length != newList.length) { 73 | _stoppedList = newList; 74 | saveHistoryListToJson(); 75 | } else { 76 | _stoppedList = newList; 77 | } 78 | notifyListeners(); 79 | } 80 | 81 | void updateDownloadingList(List newList) { 82 | _downloadingList = newList; 83 | notifyListeners(); 84 | } 85 | 86 | void updateWaitingList(List newList) { 87 | _waitingList = newList; 88 | notifyListeners(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/resources/yolx_aria2.conf: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Yolx Linux Aria2 config file 3 | # 4 | # @see https://aria2.github.io/manual/en/html/aria2c.html 5 | # 6 | ############################### 7 | 8 | 9 | ################ RPC ################ 10 | # Enable JSON-RPC/XML-RPC server. 11 | enable-rpc=true 12 | # Add Access-Control-Allow-Origin header field with value * to the RPC response. 13 | rpc-allow-origin-all=true 14 | # Listen incoming JSON-RPC/XML-RPC requests on all network interfaces. 15 | rpc-listen-all=true 16 | 17 | 18 | ################ File system ################ 19 | # Save a control file(*.aria2) every SEC seconds. 20 | auto-save-interval=10 21 | # Enable disk cache. 22 | disk-cache=64M 23 | # Specify file allocation method. 24 | file-allocation=falloc 25 | # No file allocation is made for files whose size is smaller than SIZE 26 | no-file-allocation-limit=64M 27 | # Save error/unfinished downloads to a file specified by --save-session option every SEC seconds. 28 | save-session-interval=10 29 | # Force save, even if the task is completed, to save information to the session file 30 | force-save=false 31 | 32 | ################ Task ################ 33 | # Exclude seed only downloads when counting concurrent active downloads 34 | bt-detach-seed-only=true 35 | # Verify the peer using certificates specified in --ca-certificate option. 36 | check-certificate=false 37 | # If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times 38 | # without getting a single byte, then force the download to fail. 39 | max-file-not-found=10 40 | # Set number of tries. 41 | max-tries=0 42 | # Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response. 43 | retry-wait=10 44 | # Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead. 45 | connect-timeout=10 46 | # Set timeout in seconds. 47 | timeout=10 48 | # aria2 does not split less than 2*SIZE byte range. 49 | min-split-size=1M 50 | # Send Accept: deflate, gzip request header. 51 | http-accept-gzip=true 52 | # Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file. 53 | remote-time=true 54 | # Set interval in seconds to output download progress summary. Setting 0 suppresses the output. 55 | summary-interval=0 56 | # Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*. 57 | content-disposition-default-utf8=true 58 | 59 | 60 | ################ BT Task ################ 61 | # Enable Local Peer Discovery. 62 | bt-enable-lpd=true 63 | # Requires BitTorrent message payload encryption with arc4. 64 | # bt-force-encryption=true 65 | # If true is given, after hash check using --check-integrity option and file is complete, continue to seed file. 66 | bt-hash-check-seed=true 67 | # Specify the maximum number of peers per torrent. 68 | bt-max-peers=128 69 | # Try to download first and last pieces of each file first. This is useful for previewing files. 70 | bt-prioritize-piece=head 71 | # Removes the unselected files when download is completed in BitTorrent. 72 | bt-remove-unselected-file=true 73 | # Seed previously downloaded files without verifying piece hashes. 74 | bt-seed-unverified=false 75 | # Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead. 76 | bt-tracker-connect-timeout=10 77 | # Set timeout in seconds. 78 | bt-tracker-timeout=10 79 | # Set host and port as an entry point to IPv4 DHT network. 80 | dht-entry-point=dht.transmissionbt.com:6881 81 | # Set host and port as an entry point to IPv6 DHT network. 82 | dht-entry-point6=dht.transmissionbt.com:6881 83 | # Enable IPv4 DHT functionality. It also enables UDP tracker support. 84 | enable-dht=true 85 | # Enable IPv6 DHT functionality. 86 | enable-dht6=true 87 | # Enable Peer Exchange extension. 88 | enable-peer-exchange=true 89 | # Specify the string used during the bitorrent extended handshake for the peer's client version. 90 | peer-agent=Transmission/3.00 91 | # Specify the prefix of peer ID. 92 | peer-id-prefix=-TR3000- 93 | -------------------------------------------------------------------------------- /lib/screens/downloading.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluent_ui/fluent_ui.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:yolx/common/global.dart'; 5 | import 'package:yolx/generated/l10n.dart'; 6 | import 'package:yolx/model/download_item.dart'; 7 | import 'package:yolx/model/download_list_model.dart'; 8 | import 'package:yolx/utils/common_utils.dart'; 9 | import 'package:yolx/widgets/download_file_card.dart'; 10 | import 'dart:async'; 11 | // ignore: library_prefixes 12 | import 'package:yolx/utils/ariar2_http_utils.dart' as Aria2Http; 13 | import 'package:yolx/widgets/new_download_dialog.dart'; 14 | 15 | import '../widgets/page.dart'; 16 | 17 | class DownloadingPage extends StatefulWidget { 18 | const DownloadingPage({super.key}); 19 | 20 | @override 21 | State createState() => _DownloadingPageState(); 22 | } 23 | 24 | class _DownloadingPageState extends State with PageMixin { 25 | bool selected = true; 26 | String? comboboxValue; 27 | // ignore: prefer_typing_uninitialized_variables 28 | var time; 29 | 30 | void showNewDialog(BuildContext context) async { 31 | await showDialog( 32 | context: context, 33 | builder: (context) => const NewDownloadDialog(), 34 | ); 35 | } 36 | 37 | void updateList() async { 38 | if (!mounted) { 39 | return; 40 | } 41 | var res = await Aria2Http.tellActive(Global.rpcUrl); 42 | if (res == null) { 43 | return; 44 | } 45 | if (mounted) { 46 | var downloadListModel = 47 | // ignore: use_build_context_synchronously 48 | Provider.of(context, listen: false); 49 | List downloadList = parseDownloadList(res); 50 | if (downloadList.length == downloadListModel.downloadingList.length) { 51 | downloadListModel.updateDownloadingList(parseDownloadList(res)); 52 | } else { 53 | downloadListModel.updateDownloadingList(parseDownloadList(res)); 54 | var stoppedRes = await Aria2Http.tellStopped(Global.rpcUrl); 55 | if (stoppedRes == null) { 56 | return; 57 | } 58 | var stoppedListModel = 59 | // ignore: use_build_context_synchronously 60 | Provider.of(context, listen: false); 61 | stoppedListModel.updateStoppedList(parseDownloadList(stoppedRes)); 62 | } 63 | } 64 | } 65 | 66 | @override 67 | void initState() { 68 | super.initState(); 69 | updateList(); 70 | time = Timer.periodic(const Duration(milliseconds: 1000), (t) { 71 | updateList(); 72 | }); 73 | } 74 | 75 | @override 76 | void dispose() { 77 | time?.cancel(); 78 | super.dispose(); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | assert(debugCheckHasFluentTheme(context)); 84 | var downloadList = Provider.of(context).downloadingList; 85 | return Padding( 86 | padding: EdgeInsets.all((MediaQuery.sizeOf(context).width < 640.0) 87 | ? 12.0 88 | : kPageDefaultVerticalPadding), 89 | child: Column( 90 | crossAxisAlignment: CrossAxisAlignment.center, 91 | children: [ 92 | Row( 93 | children: [ 94 | Text(S.of(context).downloading, 95 | style: FluentTheme.of(context).typography.title), 96 | const Spacer(), 97 | Tooltip( 98 | message: S.of(context).newDownload, 99 | displayHorizontally: true, 100 | useMousePosition: false, 101 | style: const TooltipThemeData(preferBelow: true), 102 | child: IconButton( 103 | icon: const Icon(FluentIcons.add, size: 18.0), 104 | onPressed: () async { 105 | showNewDialog(context); 106 | }, 107 | ), 108 | ), 109 | Tooltip( 110 | message: S.of(context).resumeAllTasks, 111 | displayHorizontally: true, 112 | useMousePosition: false, 113 | style: const TooltipThemeData(preferBelow: true), 114 | child: IconButton( 115 | icon: const Icon(FluentIcons.play, size: 18.0), 116 | onPressed: () async { 117 | await Aria2Http.unpauseAll(Global.rpcUrl); 118 | }, 119 | ), 120 | ), 121 | Tooltip( 122 | message: S.of(context).pauseAllTasks, 123 | displayHorizontally: true, 124 | useMousePosition: false, 125 | style: const TooltipThemeData(preferBelow: true), 126 | child: IconButton( 127 | icon: const Icon(FluentIcons.pause, size: 18.0), 128 | onPressed: () async { 129 | await Aria2Http.forcePauseAll(Global.rpcUrl); 130 | }, 131 | ), 132 | ), 133 | ], 134 | ), 135 | const SizedBox(height: 20.0), 136 | if (downloadList.isNotEmpty) ...[ 137 | Expanded( 138 | child: ListView.builder( 139 | itemCount: downloadList.length, 140 | itemBuilder: (context, index) { 141 | final contact = downloadList[index]; 142 | return DownloadFileCard( 143 | downloadFile: contact, 144 | ); 145 | }, 146 | )), 147 | ] else ...[ 148 | Expanded( 149 | child: SvgPicture.asset( 150 | 'assets/no_data.svg', 151 | width: 260, 152 | semanticsLabel: 'My Icon', 153 | )), 154 | Text(S.of(context).noTaskDownloaded) 155 | ] 156 | ], 157 | ), 158 | ); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /lib/screens/stopped.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluent_ui/fluent_ui.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:yolx/common/global.dart'; 5 | import 'package:yolx/generated/l10n.dart'; 6 | import 'package:yolx/model/download_list_model.dart'; 7 | import 'package:yolx/utils/common_utils.dart'; 8 | import 'package:yolx/widgets/download_file_card.dart'; 9 | import 'dart:async'; 10 | import 'package:yolx/utils/ariar2_http_utils.dart' as Aria2Http; 11 | 12 | import '../widgets/page.dart'; 13 | 14 | class StoppedPage extends StatefulWidget { 15 | const StoppedPage({super.key}); 16 | 17 | @override 18 | State createState() => _StoppedPageState(); 19 | } 20 | 21 | class _StoppedPageState extends State with PageMixin { 22 | bool selected = true; 23 | String? comboboxValue; 24 | // ignore: prefer_typing_uninitialized_variables 25 | var time; 26 | 27 | void updateList() async { 28 | if (!mounted) { 29 | return; 30 | } 31 | var res = await Aria2Http.tellStopped(Global.rpcUrl); 32 | if (res == null) { 33 | return; 34 | } 35 | if (mounted) { 36 | var downloadListModel = 37 | // ignore: use_build_context_synchronously 38 | Provider.of(context, listen: false); 39 | downloadListModel.updateStoppedList(parseDownloadList(res)); 40 | } 41 | } 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | updateList(); 47 | time = Timer.periodic(const Duration(milliseconds: 1000), (t) { 48 | updateList(); 49 | }); 50 | } 51 | 52 | @override 53 | void dispose() { 54 | time?.cancel(); // 取消定时器以避免未来触发 55 | super.dispose(); 56 | } 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | assert(debugCheckHasFluentTheme(context)); 61 | var downloadListModel = Provider.of(context); 62 | var downloadList = downloadListModel.stoppedList; 63 | return Padding( 64 | padding: EdgeInsets.all((MediaQuery.sizeOf(context).width < 640.0) 65 | ? 12.0 66 | : kPageDefaultVerticalPadding), 67 | child: Column( 68 | crossAxisAlignment: CrossAxisAlignment.center, 69 | children: [ 70 | Row( 71 | children: [ 72 | Text(S.of(context).stopped, 73 | style: FluentTheme.of(context).typography.title), 74 | const Spacer(), 75 | Tooltip( 76 | message: S.of(context).purgeTaskRecord, 77 | displayHorizontally: true, 78 | useMousePosition: false, 79 | style: const TooltipThemeData(preferBelow: true), 80 | child: IconButton( 81 | icon: const Icon(FluentIcons.delete, size: 18.0), 82 | onPressed: () async { 83 | await Aria2Http.purgeDownloadResult(Global.rpcUrl); 84 | // ignore: use_build_context_synchronously 85 | downloadListModel.removeAllFromHistoryList(); 86 | }, 87 | ), 88 | ), 89 | Tooltip( 90 | message: S.of(context).resumeAllTasks, 91 | displayHorizontally: true, 92 | useMousePosition: false, 93 | style: const TooltipThemeData(preferBelow: true), 94 | child: IconButton( 95 | icon: const Icon(FluentIcons.play, size: 18.0), 96 | onPressed: () async { 97 | await Aria2Http.unpauseAll(Global.rpcUrl); 98 | }, 99 | ), 100 | ), 101 | Tooltip( 102 | message: S.of(context).pauseAllTasks, 103 | displayHorizontally: true, 104 | useMousePosition: false, 105 | style: const TooltipThemeData(preferBelow: true), 106 | child: IconButton( 107 | icon: const Icon(FluentIcons.pause, size: 18.0), 108 | onPressed: () async { 109 | await Aria2Http.forcePauseAll(Global.rpcUrl); 110 | }, 111 | ), 112 | ), 113 | ], 114 | ), 115 | const SizedBox(height: 20.0), 116 | if (downloadList.isNotEmpty) ...[ 117 | Expanded( 118 | child: ListView.builder( 119 | itemCount: downloadList.length, 120 | itemBuilder: (context, index) { 121 | final contact = downloadList[index]; 122 | return DownloadFileCard( 123 | downloadFile: contact, 124 | ); 125 | }, 126 | )), 127 | ] else ...[ 128 | Expanded( 129 | child: SvgPicture.asset( 130 | 'assets/no_data.svg', 131 | width: 260, 132 | semanticsLabel: 'My Icon', 133 | )), 134 | Text(S.of(context).noTaskDownloaded) 135 | ] 136 | ], 137 | ), 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/screens/waiting.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluent_ui/fluent_ui.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:yolx/common/global.dart'; 5 | import 'package:yolx/generated/l10n.dart'; 6 | import 'package:yolx/model/download_list_model.dart'; 7 | import 'package:yolx/utils/common_utils.dart'; 8 | import 'package:yolx/widgets/download_file_card.dart'; 9 | import 'dart:async'; 10 | import 'package:yolx/utils/ariar2_http_utils.dart' as Aria2Http; 11 | 12 | import '../widgets/page.dart'; 13 | 14 | class WaitingPage extends StatefulWidget { 15 | const WaitingPage({super.key}); 16 | 17 | @override 18 | State createState() => _WaitingPageState(); 19 | } 20 | 21 | class _WaitingPageState extends State with PageMixin { 22 | bool selected = true; 23 | String? comboboxValue; 24 | // ignore: prefer_typing_uninitialized_variables 25 | var time; 26 | 27 | void updateList() async { 28 | if (!mounted) { 29 | return; 30 | } 31 | var res = await Aria2Http.tellWaiting(Global.rpcUrl); 32 | if (res == null) { 33 | return; 34 | } 35 | if (mounted) { 36 | var downloadListModel = 37 | // ignore: use_build_context_synchronously 38 | Provider.of(context, listen: false); 39 | downloadListModel.updateWaitingList(parseDownloadList(res)); 40 | } 41 | } 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | updateList(); 47 | time = Timer.periodic(const Duration(milliseconds: 1000), (t) { 48 | updateList(); 49 | }); 50 | } 51 | 52 | @override 53 | void dispose() { 54 | time?.cancel(); // 取消定时器以避免未来触发 55 | super.dispose(); 56 | } 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | assert(debugCheckHasFluentTheme(context)); 61 | var downloadList = Provider.of(context).waitingList; 62 | return Padding( 63 | padding: EdgeInsets.all((MediaQuery.sizeOf(context).width < 640.0) 64 | ? 12.0 65 | : kPageDefaultVerticalPadding), 66 | child: Column( 67 | crossAxisAlignment: CrossAxisAlignment.center, 68 | children: [ 69 | Row( 70 | children: [ 71 | Text(S.of(context).waiting, 72 | style: FluentTheme.of(context).typography.title), 73 | const Spacer(), 74 | Tooltip( 75 | message: S.of(context).resumeAllTasks, 76 | displayHorizontally: true, 77 | useMousePosition: false, 78 | style: const TooltipThemeData(preferBelow: true), 79 | child: IconButton( 80 | icon: const Icon(FluentIcons.play, size: 18.0), 81 | onPressed: () async { 82 | await Aria2Http.unpauseAll(Global.rpcUrl); 83 | }, 84 | ), 85 | ), 86 | Tooltip( 87 | message: S.of(context).pauseAllTasks, 88 | displayHorizontally: true, 89 | useMousePosition: false, 90 | style: const TooltipThemeData(preferBelow: true), 91 | child: IconButton( 92 | icon: const Icon(FluentIcons.pause, size: 18.0), 93 | onPressed: () async { 94 | await Aria2Http.forcePauseAll(Global.rpcUrl); 95 | }, 96 | ), 97 | ), 98 | ], 99 | ), 100 | const SizedBox(height: 20.0), 101 | if (downloadList.isNotEmpty) ...[ 102 | Expanded( 103 | child: ListView.builder( 104 | itemCount: downloadList.length, 105 | itemBuilder: (context, index) { 106 | final contact = downloadList[index]; 107 | return DownloadFileCard( 108 | downloadFile: contact, 109 | ); 110 | }, 111 | )), 112 | ] else ...[ 113 | Expanded( 114 | child: SvgPicture.asset( 115 | 'assets/no_data.svg', 116 | width: 260, 117 | semanticsLabel: 'My Icon', 118 | )), 119 | Text(S.of(context).noTaskDownloaded) 120 | ] 121 | ], 122 | ), 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:system_theme/system_theme.dart'; 3 | 4 | import 'package:fluent_ui/fluent_ui.dart'; 5 | 6 | enum NavigationIndicators { sticky, end } 7 | 8 | class AppTheme extends ChangeNotifier { 9 | AccentColor? _color; 10 | AccentColor get color => _color ?? systemAccentColor; 11 | set color(AccentColor color) { 12 | _color = color; 13 | notifyListeners(); 14 | } 15 | 16 | ThemeMode _mode = ThemeMode.system; 17 | ThemeMode get mode => _mode; 18 | set mode(ThemeMode mode) { 19 | _mode = mode; 20 | notifyListeners(); 21 | } 22 | 23 | PaneDisplayMode _displayMode = PaneDisplayMode.auto; 24 | PaneDisplayMode get displayMode => _displayMode; 25 | set displayMode(PaneDisplayMode displayMode) { 26 | _displayMode = displayMode; 27 | notifyListeners(); 28 | } 29 | 30 | NavigationIndicators _indicator = NavigationIndicators.sticky; 31 | NavigationIndicators get indicator => _indicator; 32 | set indicator(NavigationIndicators indicator) { 33 | _indicator = indicator; 34 | notifyListeners(); 35 | } 36 | 37 | TextDirection _textDirection = TextDirection.ltr; 38 | TextDirection get textDirection => _textDirection; 39 | set textDirection(TextDirection direction) { 40 | _textDirection = direction; 41 | notifyListeners(); 42 | } 43 | 44 | Locale? _locale; 45 | Locale? get locale => _locale; 46 | set locale(Locale? locale) { 47 | _locale = locale; 48 | notifyListeners(); 49 | } 50 | } 51 | 52 | AccentColor get systemAccentColor { 53 | if ((defaultTargetPlatform == TargetPlatform.windows || 54 | defaultTargetPlatform == TargetPlatform.android) && 55 | !kIsWeb) { 56 | return AccentColor.swatch({ 57 | 'darkest': SystemTheme.accentColor.darkest, 58 | 'darker': SystemTheme.accentColor.darker, 59 | 'dark': SystemTheme.accentColor.dark, 60 | 'normal': SystemTheme.accentColor.accent, 61 | 'light': SystemTheme.accentColor.light, 62 | 'lighter': SystemTheme.accentColor.lighter, 63 | 'lightest': SystemTheme.accentColor.lightest, 64 | }); 65 | } 66 | return Colors.blue; 67 | } 68 | -------------------------------------------------------------------------------- /lib/utils/aria2_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:yolx/common/const.dart'; 6 | import 'package:yolx/common/global.dart'; 7 | import 'package:yolx/utils/common_utils.dart'; 8 | import 'package:yolx/utils/file_utils.dart'; 9 | import 'package:yolx/utils/log.dart'; 10 | import 'package:yolx/utils/native_channel_utils.dart'; 11 | import 'ariar2_http_utils.dart' as Aria2Http; 12 | 13 | class Aria2Manager { 14 | late Future cmdProcess; 15 | late int processPid = 0; 16 | 17 | getAria2ExePath() async { 18 | if (Platform.isWindows || Platform.isLinux) { 19 | String dir = await getPlugAssetsDir('aria2'); 20 | String ariaName = 'yolx_aria2c'; 21 | if (Platform.isWindows) { 22 | ariaName = 'yolx_aria2c.exe'; 23 | } 24 | return '$dir/$ariaName'; 25 | } else if (Platform.isAndroid) { 26 | final libDir = await nativeLibraryDir(); 27 | var libPath = '$libDir/libaria2c.so'; 28 | File file = File(libPath); 29 | if (!file.existsSync()) { 30 | Log.e("aria2 not found:$libPath"); 31 | } 32 | return libPath; 33 | } 34 | } 35 | 36 | getAria2ConfPath() async { 37 | Directory dir = await getApplicationSupportDirectory(); 38 | String confName = 'yolx_aria2.conf'; 39 | return '${dir.path}${Global.pathSeparator}$confName'; 40 | } 41 | 42 | getAria2Session() async { 43 | Directory appDocumentsCacheDirectory = await getApplicationCacheDirectory(); 44 | return '${appDocumentsCacheDirectory.path}${Global.pathSeparator}download.session'; 45 | } 46 | 47 | initAria2Conf() async { 48 | String confPath = await getAria2ConfPath(); 49 | if (!await File(confPath).exists()) { 50 | List aria2ConfLines = await readDefAria2Conf(); 51 | await writeLinesToFile(confPath, aria2ConfLines.join("\n")); 52 | } 53 | } 54 | 55 | void startServer() async { 56 | closeServer(); 57 | Global.rpcUrl = rpcURLValue.replaceAll('{port}', Global.rpcPort.toString()); 58 | var exe = await getAria2ExePath(); 59 | var conf = await getAria2ConfPath(); 60 | // print(File(conf).existsSync()); 61 | if (Platform.isLinux) { 62 | permission777(exe); 63 | permission777(conf); 64 | } 65 | int port = Global.rpcPort; 66 | String secret = Global.rpcSecret; 67 | String session = await getAria2Session(); 68 | File file = File(session); 69 | bool fileExists = await file.exists(); 70 | 71 | if (!fileExists) { 72 | await file.create(recursive: true); 73 | } 74 | List arguments = [ 75 | '--conf-path=$conf', 76 | '--rpc-listen-port=$port', 77 | '--input-file=$session', 78 | '--save-session=$session' 79 | ]; 80 | if (secret.isNotEmpty) { 81 | arguments.add('--rpc-secret=$secret'); 82 | } 83 | cmdProcess = Process.start(exe, arguments); 84 | cmdProcess.then((processResult) { 85 | processPid = processResult.pid; 86 | processResult.exitCode.then((value) => Log.i(value)); 87 | processResult.stdout 88 | .transform(utf8.decoder) 89 | .transform(const LineSplitter()) 90 | .listen((event) { 91 | if (event.trim().isNotEmpty) { 92 | Log.i(event); 93 | } 94 | }); 95 | processResult.stderr 96 | .transform(utf8.decoder) 97 | .transform(const LineSplitter()) 98 | .listen((event) { 99 | if (event.trim().isNotEmpty) { 100 | Log.e("Error: $event"); 101 | } 102 | }); 103 | }); 104 | Aria2Http.changeGlobalOption( 105 | {'dir': Global.downloadPath, 'user-agent': Global.ua}, Global.rpcUrl); 106 | if (Global.proxy.isNotEmpty) { 107 | Aria2Http.changeGlobalOption({'all-proxy': Global.proxy}, Global.rpcUrl); 108 | } 109 | if (Global.bypassProxy.isNotEmpty) { 110 | Aria2Http.changeGlobalOption( 111 | {'no-proxy': Global.bypassProxy}, Global.rpcUrl); 112 | } 113 | Aria2Http.changeGlobalOption({ 114 | 'max-concurrent-downloads': Global.maxConcurrentDownloads.toString(), 115 | 'max-connection-per-server': Global.maxConnectionPerServer.toString() 116 | }, Global.rpcUrl); 117 | } 118 | 119 | closeServer() { 120 | bool killSuccess = false; 121 | if (Platform.isWindows) { 122 | final processResult = 123 | Process.runSync('taskkill', ['/F', '/T', '/IM', 'yolx_aria2c.exe']); 124 | killSuccess = processResult.exitCode == 0; 125 | } else if (Platform.isLinux || Platform.isAndroid) { 126 | final processResult = Process.runSync('killall', ['yolx_aria2c']); 127 | killSuccess = processResult.exitCode == 0; 128 | } 129 | Log.i(killSuccess 130 | ? "Successfully killed aria2 service" 131 | : "Killing Aria2 service failed"); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/utils/ariar2_http_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:uuid/uuid.dart'; 3 | import 'package:http/http.dart' as http; 4 | import 'package:yolx/common/global.dart'; 5 | import 'package:yolx/utils/log.dart'; 6 | 7 | var uuid = const Uuid(); 8 | 9 | getVersion(aria2url) async { 10 | String aria2Version = '0'; 11 | try { 12 | var res = await http.post(Uri.parse(aria2url), 13 | body: json.encode({ 14 | "jsonrpc": "2.0", 15 | "method": "aria2.getVersion", 16 | "id": 'getVersion', 17 | "params": [] 18 | })); 19 | if (res.statusCode == 200) { 20 | var resJson = json.decode(res.body); 21 | aria2Version = resJson['result']['version']; 22 | } 23 | } catch (e) { 24 | Log.e(e); 25 | } 26 | //{"id":1,"jsonrpc":"2.0","result":{"enabledFeatures":["Async DNS","BitTorrent","Firefox3 Cookie","GZip","HTTPS","Message Digest","Metalink","XML-RPC","SFTP"],"version":"1.36.0"}} 27 | // print(aria2Version); 28 | return aria2Version; 29 | } 30 | 31 | changeGlobalOption(params, aria2url) async { 32 | try { 33 | var rpcSecret = Global.rpcSecret; 34 | 35 | var res = await http.post(Uri.parse(aria2url), 36 | body: json.encode({ 37 | "jsonrpc": "2.0", 38 | "method": "aria2.changeGlobalOption", 39 | "id": "changeGlobalOption", 40 | "params": ['token:$rpcSecret', params] 41 | })); 42 | if (res.statusCode == 200) { 43 | var resJson = json.decode(res.body); 44 | return resJson['result']; 45 | } 46 | } catch (e) { 47 | Log.e(e); 48 | } 49 | } 50 | 51 | addUrl(params, aria2url) async { 52 | try { 53 | var rpcSecret = Global.rpcSecret; 54 | var res = await http.post(Uri.parse(aria2url), 55 | body: json.encode({ 56 | "jsonrpc": "2.0", 57 | "method": "aria2.addUri", 58 | "id": uuid.v4(), 59 | "params": ['token:$rpcSecret', ...params] 60 | })); 61 | if (res.statusCode == 200) { 62 | var resJson = json.decode(res.body); 63 | return resJson['result']; 64 | } 65 | } catch (e) { 66 | Log.e(e); 67 | } 68 | } 69 | 70 | addTorrent(params, aria2url) async { 71 | try { 72 | var rpcSecret = Global.rpcSecret; 73 | var res = await http.post(Uri.parse(aria2url), 74 | body: json.encode({ 75 | "jsonrpc": "2.0", 76 | "method": "aria2.addTorrent", 77 | "id": uuid.v4(), 78 | "params": ['token:$rpcSecret', ...params] 79 | })); 80 | if (res.statusCode == 200) { 81 | var resJson = json.decode(res.body); 82 | return resJson['result']; 83 | } 84 | } catch (e) { 85 | Log.e(e); 86 | } 87 | } 88 | 89 | addMetalink(params, aria2url) async { 90 | try { 91 | var rpcSecret = Global.rpcSecret; 92 | var res = await http.post(Uri.parse(aria2url), 93 | body: json.encode({ 94 | "jsonrpc": "2.0", 95 | "method": "aria2.addMetalink", 96 | "id": uuid.v4(), 97 | "params": ['token:$rpcSecret', ...params] 98 | })); 99 | if (res.statusCode == 200) { 100 | var resJson = json.decode(res.body); 101 | return resJson['result']; 102 | } 103 | } catch (e) { 104 | Log.e(e); 105 | } 106 | } 107 | 108 | tellStopped(String aria2url) async { 109 | try { 110 | var rpcSecret = Global.rpcSecret; 111 | var res = await http.post(Uri.parse(aria2url), 112 | body: json.encode({ 113 | "jsonrpc": "2.0", 114 | "method": "aria2.tellStopped", 115 | "id": 'tellStopped', 116 | "params": [ 117 | 'token:$rpcSecret', 118 | -1, 119 | 1000, 120 | [ 121 | "gid", 122 | "files", 123 | "totalLength", 124 | "completedLength", 125 | "uploadSpeed", 126 | "downloadSpeed", 127 | "connections", 128 | "numSeeders", 129 | "seeder", 130 | "status", 131 | "errorCode", 132 | "verifiedLength", 133 | "verifyIntegrityPending" 134 | ] 135 | ] 136 | })); 137 | if (res.statusCode == 200) { 138 | var jsonResponse = json.decode(utf8.decode(res.bodyBytes)); 139 | var result = jsonResponse['result']; 140 | return result; 141 | } else { 142 | Log.e('${res.reasonPhrase}'); 143 | } 144 | } catch (e) { 145 | Log.e(e); 146 | } 147 | } 148 | 149 | tellWaiting(String aria2url) async { 150 | try { 151 | var rpcSecret = Global.rpcSecret; 152 | var res = await http.post(Uri.parse(aria2url), 153 | body: json.encode({ 154 | "jsonrpc": "2.0", 155 | "method": "aria2.tellWaiting", 156 | "id": 'tellWaiting', 157 | "params": [ 158 | 'token:$rpcSecret', 159 | 0, 160 | 100, 161 | [ 162 | "gid", 163 | "files", 164 | "totalLength", 165 | "completedLength", 166 | "uploadSpeed", 167 | "downloadSpeed", 168 | "connections", 169 | "numSeeders", 170 | "seeder", 171 | "status", 172 | "errorCode", 173 | "verifiedLength", 174 | "verifyIntegrityPending" 175 | ] 176 | ] 177 | })); 178 | if (res.statusCode == 200) { 179 | var jsonResponse = json.decode(utf8.decode(res.bodyBytes)); 180 | var result = jsonResponse['result']; 181 | return result; 182 | } else { 183 | Log.e('${res.reasonPhrase}'); 184 | } 185 | } catch (e) { 186 | Log.e(e); 187 | } 188 | } 189 | 190 | tellActive(String aria2url) async { 191 | try { 192 | var rpcSecret = Global.rpcSecret; 193 | var res = await http.post(Uri.parse(aria2url), 194 | body: json.encode({ 195 | "jsonrpc": "2.0", 196 | "method": "aria2.tellActive", 197 | "id": 'tellActive', 198 | "params": [ 199 | 'token:$rpcSecret', 200 | [ 201 | "gid", 202 | "files", 203 | "totalLength", 204 | "completedLength", 205 | "uploadSpeed", 206 | "downloadSpeed", 207 | "connections", 208 | "numSeeders", 209 | "seeder", 210 | "status", 211 | "errorCode", 212 | "verifiedLength", 213 | "verifyIntegrityPending" 214 | ] 215 | ] 216 | })); 217 | if (res.statusCode == 200) { 218 | var jsonResponse = json.decode(utf8.decode(res.bodyBytes)); 219 | var result = jsonResponse['result']; 220 | return result; 221 | } else { 222 | Log.e('${res.reasonPhrase}'); 223 | } 224 | } catch (e) { 225 | Log.e(e); 226 | } 227 | } 228 | 229 | unpauseAll(String aria2url) async { 230 | try { 231 | var rpcSecret = Global.rpcSecret; 232 | await http.post(Uri.parse(aria2url), 233 | body: json.encode({ 234 | "jsonrpc": "2.0", 235 | "method": "aria2.forcePauseAll", 236 | "id": 'forcePauseAll', 237 | "params": ['token:$rpcSecret'] 238 | })); 239 | } catch (e) { 240 | Log.e(e); 241 | } 242 | } 243 | 244 | forcePauseAll(String aria2url) async { 245 | try { 246 | var rpcSecret = Global.rpcSecret; 247 | await http.post(Uri.parse(aria2url), 248 | body: json.encode({ 249 | "jsonrpc": "2.0", 250 | "method": "aria2.forcePauseAll", 251 | "id": 'forcePauseAll', 252 | "params": ['token:$rpcSecret'] 253 | })); 254 | } catch (e) { 255 | Log.e(e); 256 | } 257 | } 258 | 259 | purgeDownloadResult(String aria2url) async { 260 | try { 261 | var rpcSecret = Global.rpcSecret; 262 | await http.post(Uri.parse(aria2url), 263 | body: json.encode({ 264 | "jsonrpc": "2.0", 265 | "method": "aria2.purgeDownloadResult", 266 | "id": 'purgeDownloadResult', 267 | "params": ['token:$rpcSecret'] 268 | })); 269 | } catch (e) { 270 | Log.e(e); 271 | } 272 | } 273 | 274 | forcePause(String aria2url, String gid) async { 275 | try { 276 | var rpcSecret = Global.rpcSecret; 277 | await http.post(Uri.parse(aria2url), 278 | body: json.encode({ 279 | "jsonrpc": "2.0", 280 | "method": "aria2.forcePause", 281 | "id": 'forcePause', 282 | "params": ['token:$rpcSecret', gid] 283 | })); 284 | } catch (e) { 285 | Log.e(e); 286 | } 287 | } 288 | 289 | unpause(String aria2url, String gid) async { 290 | try { 291 | var rpcSecret = Global.rpcSecret; 292 | await http.post(Uri.parse(aria2url), 293 | body: json.encode({ 294 | "jsonrpc": "2.0", 295 | "method": "aria2.unpause", 296 | "id": 'unpause', 297 | "params": ['token:$rpcSecret', gid] 298 | })); 299 | } catch (e) { 300 | Log.e(e); 301 | } 302 | } 303 | 304 | forceRemove(String aria2url, String gid) async { 305 | try { 306 | var rpcSecret = Global.rpcSecret; 307 | var r = await http.post(Uri.parse(aria2url), 308 | body: json.encode({ 309 | "jsonrpc": "2.0", 310 | "method": "aria2.forceRemove", 311 | "id": 'forceRemove', 312 | "params": ['token:$rpcSecret', gid] 313 | })); 314 | Log.i(r.body); 315 | } catch (e) { 316 | Log.e(e); 317 | } 318 | } 319 | 320 | removeDownloadResult(String aria2url, String gid) async { 321 | try { 322 | var rpcSecret = Global.rpcSecret; 323 | var r = await http.post(Uri.parse(aria2url), 324 | body: json.encode({ 325 | "jsonrpc": "2.0", 326 | "method": "aria2.removeDownloadResult", 327 | "id": 'removeDownloadResult', 328 | "params": ['token:$rpcSecret', gid] 329 | })); 330 | Log.i(r.body); 331 | } catch (e) { 332 | Log.e(e); 333 | } 334 | } 335 | 336 | changePosition(String aria2url, String gid, int pos, String how) async { 337 | try { 338 | var rpcSecret = Global.rpcSecret; 339 | var r = await http.post(Uri.parse(aria2url), 340 | body: json.encode({ 341 | "jsonrpc": "2.0", 342 | "method": "aria2.changePosition", 343 | "id": 'changePosition', 344 | "params": ['token:$rpcSecret', gid, pos, how] 345 | })); 346 | Log.i(r.body); 347 | } catch (e) { 348 | Log.e(e); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /lib/utils/common_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:math'; 3 | 4 | import 'package:fluent_ui/fluent_ui.dart'; 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:yolx/common/const.dart'; 7 | import 'package:yolx/model/download_item.dart'; 8 | 9 | bool get isDesktop { 10 | if (kIsWeb) return false; 11 | return [ 12 | TargetPlatform.windows, 13 | TargetPlatform.linux, 14 | TargetPlatform.macOS, 15 | ].contains(defaultTargetPlatform); 16 | } 17 | 18 | bool isTablet(MediaQueryData queryData) { 19 | double devicePixelRatio = queryData.devicePixelRatio; 20 | double screenWidth = queryData.size.shortestSide; 21 | 22 | // 判断设备是否为平板 23 | return (devicePixelRatio < 2.0 && screenWidth > 600) || 24 | (devicePixelRatio >= 2.0 && screenWidth > 960); 25 | } 26 | 27 | permission777(filePath) { 28 | Process.runSync('chmod', ['-R', '777', filePath]); 29 | } 30 | 31 | String formatFileSize(int size) { 32 | if (size <= 0) return '0 B'; 33 | final int index = (log(size) / log(1024)).floor(); 34 | return '${(size / pow(1024, index)).toStringAsFixed(2)} ${sizeUnits[index]}'; 35 | } 36 | 37 | List parseDownloadList(dynamic responseData) { 38 | List downloadList = []; 39 | for (var itemData in responseData) { 40 | var downloadItem = DownloadItem( 41 | completedLength: int.parse(itemData["completedLength"]), 42 | path: itemData["files"][0]["path"], 43 | connections: itemData["connections"], 44 | downloadSpeed: int.parse(itemData["downloadSpeed"]), 45 | gid: itemData["gid"], 46 | status: itemData["status"], 47 | totalLength: int.parse(itemData["totalLength"]), 48 | uploadSpeed: int.parse(itemData["uploadSpeed"]), 49 | ); 50 | downloadList.add(downloadItem); 51 | } 52 | return downloadList; 53 | } 54 | -------------------------------------------------------------------------------- /lib/utils/file_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:yolx/common/global.dart'; 6 | import 'package:yolx/generated/l10n.dart'; 7 | 8 | Future getLocalFile(String filename) async { 9 | final directory = await getApplicationCacheDirectory(); 10 | return File('${directory.path}${Global.pathSeparator}$filename'); 11 | } 12 | 13 | Future> readDefAria2Conf() async { 14 | String text = await rootBundle.loadString("lib/resources/yolx_aria2.conf"); 15 | return text.split('\n'); 16 | } 17 | 18 | writeLinesToFile(String path, String text) { 19 | File file = File(path); 20 | if (!file.existsSync()) { 21 | file.createSync(recursive: true); 22 | } 23 | file.writeAsStringSync(text, flush: true); 24 | } 25 | 26 | createDir(String dir) { 27 | Directory directory = Directory(dir); 28 | if (!directory.existsSync()) { 29 | directory.create(recursive: true); 30 | } 31 | } 32 | 33 | getPlugAssetsDir(String plugName) async { 34 | if (Platform.isWindows || Platform.isLinux) { 35 | String plugDir = 36 | 'data${Global.pathSeparator}plugin${Global.pathSeparator}$plugName'; 37 | String exePath = Platform.resolvedExecutable; 38 | List pathList = exePath.split(Global.pathSeparator); 39 | // String basename = path.basename(exePath); 40 | pathList[pathList.length - 1] = plugDir; 41 | return pathList.join(Global.pathSeparator); 42 | } else if (Platform.isAndroid) { 43 | Directory? cacheDir = await getExternalStorageDirectory(); 44 | String plugDir = '${cacheDir?.path}${Global.pathSeparator}$plugName'; 45 | createDir(plugDir); 46 | return plugDir; 47 | } 48 | return null; 49 | } 50 | 51 | void createFolderIfNotExists(String folderPath) { 52 | Directory directory = Directory(folderPath); 53 | 54 | if (!directory.existsSync()) { 55 | // 文件夹不存在,创建文件夹 56 | directory.createSync(recursive: true); 57 | } 58 | } 59 | 60 | void createClassificationFolder() { 61 | createFolderIfNotExists( 62 | "${Global.downloadPath}${Global.pathSeparator}${S.current.general}"); 63 | createFolderIfNotExists( 64 | "${Global.downloadPath}${Global.pathSeparator}${S.current.compressedFiles}"); 65 | createFolderIfNotExists( 66 | "${Global.downloadPath}${Global.pathSeparator}${S.current.documents}"); 67 | createFolderIfNotExists( 68 | "${Global.downloadPath}${Global.pathSeparator}${S.current.music}"); 69 | createFolderIfNotExists( 70 | "${Global.downloadPath}${Global.pathSeparator}${S.current.programs}"); 71 | createFolderIfNotExists( 72 | "${Global.downloadPath}${Global.pathSeparator}${S.current.videos}"); 73 | } 74 | 75 | int getDownloadDirectory(String fileType) { 76 | List fileTypeRules = [ 77 | Global.compressedFilesRule, 78 | Global.documentsRule, 79 | Global.musicRule, 80 | Global.programsRule, 81 | Global.videosRule, 82 | ]; 83 | 84 | for (int i = 0; i < fileTypeRules.length; i++) { 85 | List extensions = fileTypeRules[i].split(','); 86 | if (extensions.contains(fileType)) { 87 | return i; 88 | } 89 | } 90 | 91 | return 5; 92 | } 93 | -------------------------------------------------------------------------------- /lib/utils/log.dart: -------------------------------------------------------------------------------- 1 | import 'package:logger/logger.dart'; 2 | 3 | class Log { 4 | static final Logger _logger = Logger( 5 | printer: PrefixPrinter(PrettyPrinter()), 6 | ); 7 | 8 | static void v(dynamic message) { 9 | // ignore: deprecated_member_use 10 | _logger.v(message); 11 | } 12 | 13 | static void d(dynamic message) { 14 | _logger.d(message); 15 | } 16 | 17 | static void i(dynamic message) { 18 | _logger.i(message); 19 | } 20 | 21 | static void w(dynamic message) { 22 | _logger.w(message); 23 | } 24 | 25 | static void e(dynamic message) { 26 | _logger.e(message); 27 | } 28 | 29 | static void wtf(dynamic message) { 30 | // ignore: deprecated_member_use 31 | _logger.wtf(message); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/utils/native_channel_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | const MethodChannel _channel = 4 | MethodChannel('com.yoyo.flutter_native_channel/native_methods'); 5 | 6 | Future nativeLibraryDir() async { 7 | return await _channel.invokeMethod('nativeLibraryDir'); 8 | } 9 | 10 | requestPermission() async { 11 | return await _channel.invokeMethod('requestPermission'); 12 | } 13 | -------------------------------------------------------------------------------- /lib/utils/permission_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:device_info_plus/device_info_plus.dart'; 2 | import 'package:permission_handler/permission_handler.dart'; 3 | 4 | import 'native_channel_utils.dart'; 5 | 6 | Future checkStoragePermission() async { 7 | DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); 8 | AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; 9 | if (androidInfo.version.sdkInt >= 30) { 10 | bool isGranted = await requestPermission(); 11 | return isGranted; 12 | } else { 13 | PermissionStatus storageStatus = await Permission.storage.status; 14 | if (storageStatus.isDenied) { 15 | return true; 16 | } 17 | if ((await Permission.storage.request()).isGranted) { 18 | return true; 19 | } 20 | } 21 | return false; 22 | } 23 | -------------------------------------------------------------------------------- /lib/utils/tracker_http_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:http/http.dart' as http; 2 | import 'package:yolx/common/global.dart'; 3 | import 'package:yolx/utils/log.dart'; 4 | 5 | getTrackerServersList(String subscriptionAddress) async { 6 | try { 7 | Set serversList = Set.from(Global.trackerServersList.split('\n')); 8 | for (String address in subscriptionAddress.split(',')) { 9 | var res = await http.get(Uri.parse(address)); 10 | if (res.statusCode == 200) { 11 | serversList.addAll(res.body 12 | .replaceAll(RegExp(r'\s+$', multiLine: true), '') 13 | .split('\n')); 14 | } else { 15 | Log.e('${res.reasonPhrase}'); 16 | } 17 | } 18 | serversList.remove(''); 19 | return serversList.join('\n'); 20 | } catch (e) { 21 | Log.e(e); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils/url_utils.dart: -------------------------------------------------------------------------------- 1 | // ignore: depend_on_referenced_packages 2 | import 'dart:convert'; 3 | 4 | import 'package:http/http.dart' as http; 5 | import 'package:yolx/utils/log.dart'; 6 | 7 | String getURLFromQQDL(String url) { 8 | String base64Input = url.substring("qqdl://".length); 9 | String base64Output = utf8.decode(base64.decode(base64Input)); 10 | return base64Output; 11 | } 12 | 13 | String getURLFromFlashget(String url) { 14 | String base64Input = url.substring("flashget://".length); 15 | String base64Output = utf8.decode(base64.decode(base64Input)); 16 | if (base64Output.startsWith("[FLASHGET]")) { 17 | base64Output = base64Output.substring("[FLASHGET]".length); 18 | } 19 | 20 | if (base64Output.endsWith("[FLASHGET]")) { 21 | base64Output = 22 | base64Output.substring(0, base64Output.length - "[FLASHGET]".length); 23 | } 24 | return base64Output; 25 | } 26 | 27 | String getURLFromThunder(String url) { 28 | String base64Input = url.substring("thunder://".length); 29 | String base64Output = utf8.decode(base64.decode(base64Input)); 30 | if (base64Output.startsWith("AA")) { 31 | base64Output = base64Output.substring("AA".length); 32 | } 33 | 34 | if (base64Output.endsWith("ZZ")) { 35 | base64Output = base64Output.substring(0, base64Output.length - "ZZ".length); 36 | } 37 | return base64Output; 38 | } 39 | 40 | Future getFileTypeFromHeader(String url) async { 41 | try { 42 | var response = await http.head(Uri.parse(url)); 43 | // 获取文件名 44 | var filename = response.headers['content-disposition']; 45 | if (filename != null) { 46 | filename = filename.split('filename=')[1]; 47 | int dotPos = filename.lastIndexOf('.'); 48 | if (dotPos != -1 && dotPos < filename.length - 1) { 49 | // extract and return the file extension 50 | return filename.substring(dotPos + 1); 51 | } else { 52 | return ''; 53 | } 54 | } 55 | } catch (e) { 56 | Log.w(e); 57 | } 58 | return ''; 59 | } 60 | 61 | Future getFileTypeFromURL(String url) async { 62 | try { 63 | Uri uri = Uri.parse(url); 64 | String host = uri.host; 65 | 66 | if (host.isNotEmpty && url.endsWith(host)) { 67 | // handle ...example.com 68 | return ''; 69 | } 70 | } catch (e) { 71 | return ''; 72 | } 73 | int lastSlashPos = url.lastIndexOf('/'); 74 | int startIndex = (lastSlashPos != -1) ? lastSlashPos + 1 : 0; 75 | int length = url.length; 76 | // find end index for ? 77 | int lastQMPos = url.lastIndexOf('?'); 78 | if (lastQMPos == -1) { 79 | lastQMPos = length; 80 | } 81 | // find end index for # 82 | int lastHashPos = url.lastIndexOf('#'); 83 | if (lastHashPos == -1) { 84 | lastHashPos = length; 85 | } 86 | // calculate the end index 87 | int endIndex = (lastQMPos < lastHashPos) ? lastQMPos : lastHashPos; 88 | String fileName = url.substring(startIndex, endIndex); 89 | 90 | int dotPos = fileName.lastIndexOf('.'); 91 | if (dotPos != -1 && dotPos < fileName.length - 1) { 92 | // extract and return the file extension 93 | return fileName.substring(dotPos + 1); 94 | } else { 95 | return ''; 96 | } 97 | } 98 | 99 | Future getFileType(String url) async { 100 | String? fileType = await getFileTypeFromURL(url); 101 | if (fileType.isEmpty) { 102 | return getFileTypeFromHeader(url); 103 | } else { 104 | return fileType; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/widgets/download_file_card.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:fluent_ui/fluent_ui.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:url_launcher/url_launcher.dart'; 6 | import 'package:yolx/common/global.dart'; 7 | import 'package:yolx/generated/l10n.dart'; 8 | import 'package:yolx/model/download_item.dart'; 9 | import 'package:yolx/model/download_list_model.dart'; 10 | import 'package:yolx/utils/common_utils.dart'; 11 | import 'package:path/path.dart' as path; 12 | // ignore: library_prefixes 13 | import 'package:yolx/utils/ariar2_http_utils.dart' as Aria2Http; 14 | 15 | class DownloadFileCard extends StatelessWidget { 16 | final DownloadItem downloadFile; 17 | 18 | const DownloadFileCard({Key? key, required this.downloadFile}) 19 | : super(key: key); 20 | 21 | void showContentDialog(BuildContext context) async { 22 | final ValueNotifier checkboxValue = ValueNotifier(false); 23 | await showDialog( 24 | context: context, 25 | builder: (context) => ContentDialog( 26 | title: Text(S.of(context).removeTask), 27 | content: Column( 28 | crossAxisAlignment: CrossAxisAlignment.start, 29 | mainAxisSize: MainAxisSize.min, 30 | children: [ 31 | Text(S.of(context).removeTaskInfo), 32 | const SizedBox(height: 4), 33 | ValueListenableBuilder( 34 | valueListenable: checkboxValue, 35 | builder: (context, value, child) { 36 | return Checkbox( 37 | content: Text(S.of(context).deleteFile), 38 | checked: value, 39 | onChanged: (newValue) { 40 | checkboxValue.value = newValue ?? false; 41 | }, 42 | ); 43 | }, 44 | ), 45 | const SizedBox(width: 4), 46 | ], 47 | ), 48 | actions: [ 49 | Button( 50 | child: Text(S.of(context).delete), 51 | onPressed: () async { 52 | if (downloadFile.status == "paused" || 53 | downloadFile.status == "active") { 54 | await Aria2Http.forceRemove(Global.rpcUrl, downloadFile.gid); 55 | } else if (downloadFile.status == "history") { 56 | Provider.of(context, listen: false) 57 | .removeFromHistoryList(downloadFile.gid); 58 | } else { 59 | await Aria2Http.removeDownloadResult( 60 | Global.rpcUrl, downloadFile.gid); 61 | } 62 | if (checkboxValue.value) { 63 | File file = File(downloadFile.path); 64 | if (file.existsSync()) { 65 | file.deleteSync(); 66 | } 67 | File aria2file = File('${downloadFile.path}.aria2'); 68 | if (aria2file.existsSync()) { 69 | aria2file.deleteSync(); 70 | } 71 | } 72 | // ignore: use_build_context_synchronously 73 | Navigator.pop(context); 74 | }, 75 | ), 76 | FilledButton( 77 | child: Text(S.of(context).cancel), 78 | onPressed: () => Navigator.pop(context), 79 | ), 80 | ], 81 | ), 82 | ); 83 | } 84 | 85 | @override 86 | Widget build(BuildContext context) { 87 | return Card( 88 | borderRadius: const BorderRadius.all(Radius.circular(6.0)), 89 | padding: const EdgeInsets.all(12.0), 90 | margin: const EdgeInsets.only(bottom: 10.0), 91 | child: Column( 92 | crossAxisAlignment: CrossAxisAlignment.start, 93 | children: [ 94 | Row( 95 | children: [ 96 | Expanded( 97 | child: Text( 98 | path.basename(downloadFile.path), 99 | style: FluentTheme.of(context).typography.body, 100 | softWrap: false, 101 | maxLines: 1, 102 | overflow: TextOverflow.fade, 103 | ), 104 | ), 105 | if (downloadFile.status == 'paused' || 106 | downloadFile.status == 'waiting') ...[ 107 | Tooltip( 108 | message: S.of(context).topping, 109 | displayHorizontally: true, 110 | useMousePosition: false, 111 | style: const TooltipThemeData(preferBelow: true), 112 | child: IconButton( 113 | icon: const Icon(FluentIcons.up, size: 12.0), 114 | onPressed: () async { 115 | await Aria2Http.changePosition( 116 | Global.rpcUrl, downloadFile.gid, 0, 'POS_SET'); 117 | }, 118 | ), 119 | ), 120 | ], 121 | if (downloadFile.status == 'complete' || 122 | downloadFile.status == 'history') ...[ 123 | Tooltip( 124 | message: S.of(context).openFile, 125 | displayHorizontally: true, 126 | useMousePosition: false, 127 | style: const TooltipThemeData(preferBelow: true), 128 | child: IconButton( 129 | icon: const Icon(FluentIcons.page, size: 14.0), 130 | onPressed: () async { 131 | // ignore: deprecated_member_use 132 | // 此处使用launchUrl会导致Windows下由于中文url编码产生的错误 133 | if (await File(downloadFile.path).exists()) { 134 | launch( 135 | "file:/${downloadFile.path}", 136 | ); 137 | } 138 | }, 139 | ), 140 | ), 141 | ], 142 | if (downloadFile.status == 'complete' || 143 | downloadFile.status == 'history') ...[ 144 | Tooltip( 145 | message: S.of(context).openDirectory, 146 | displayHorizontally: true, 147 | useMousePosition: false, 148 | style: const TooltipThemeData(preferBelow: true), 149 | child: IconButton( 150 | icon: const Icon(FluentIcons.folder_open, size: 14.0), 151 | onPressed: () async { 152 | // ignore: deprecated_member_use 153 | // 此处使用launchUrl会导致Windows下由于中文url编码产生的错误 154 | if (await Directory(path.dirname(downloadFile.path)) 155 | .exists()) { 156 | launch( 157 | "file:/${path.dirname(downloadFile.path)}", 158 | ); 159 | } 160 | }, 161 | ), 162 | ), 163 | ], 164 | Tooltip( 165 | message: S.of(context).deleteThisTasks, 166 | displayHorizontally: true, 167 | useMousePosition: false, 168 | style: const TooltipThemeData(preferBelow: true), 169 | child: IconButton( 170 | icon: const Icon(FluentIcons.clear, size: 12.0), 171 | onPressed: () => showContentDialog(context), 172 | ), 173 | ), 174 | Tooltip( 175 | message: S.of(context).resumeThisTasks, 176 | displayHorizontally: true, 177 | useMousePosition: false, 178 | style: const TooltipThemeData(preferBelow: true), 179 | child: IconButton( 180 | icon: const Icon(FluentIcons.play, size: 14.0), 181 | onPressed: () async { 182 | await Aria2Http.unpause(Global.rpcUrl, downloadFile.gid); 183 | }, 184 | ), 185 | ), 186 | Tooltip( 187 | message: S.of(context).pauseThisTasks, 188 | displayHorizontally: true, 189 | useMousePosition: false, 190 | style: const TooltipThemeData(preferBelow: true), 191 | child: IconButton( 192 | icon: const Icon(FluentIcons.pause, size: 14.0), 193 | onPressed: () async { 194 | await Aria2Http.forcePause(Global.rpcUrl, downloadFile.gid); 195 | }, 196 | ), 197 | ), 198 | ], 199 | ), 200 | const SizedBox(height: 20), 201 | Row( 202 | children: [ 203 | Expanded( 204 | child: ProgressBar( 205 | value: ((downloadFile.completedLength / 206 | downloadFile.totalLength) * 207 | 100) <= 208 | 100 && 209 | ((downloadFile.completedLength / 210 | downloadFile.totalLength) * 211 | 100) >= 212 | 0 213 | ? ((downloadFile.completedLength / 214 | downloadFile.totalLength) * 215 | 100) 216 | : null), 217 | ) 218 | ], 219 | ), 220 | const SizedBox(height: 6), 221 | Row( 222 | children: [ 223 | Text( 224 | '${formatFileSize(downloadFile.completedLength)} / ${formatFileSize(downloadFile.totalLength)}', 225 | style: FluentTheme.of(context).typography.caption), 226 | const Spacer(), 227 | if (downloadFile.downloadSpeed > 1) ...[ 228 | const Icon( 229 | FluentIcons.download, 230 | size: 14, 231 | ), 232 | Text('${formatFileSize(downloadFile.downloadSpeed)}/s', 233 | style: FluentTheme.of(context).typography.caption), 234 | ], 235 | if (downloadFile.uploadSpeed > 1) ...[ 236 | const Icon( 237 | FluentIcons.upload, 238 | size: 14, 239 | ), 240 | Text('${formatFileSize(downloadFile.uploadSpeed)}/s', 241 | style: FluentTheme.of(context).typography.caption), 242 | ], 243 | if (downloadFile.status == 'waiting') ...[ 244 | Text(S.of(context).waiting, 245 | style: FluentTheme.of(context).typography.caption), 246 | ] 247 | ], 248 | ), 249 | ], 250 | ), 251 | ); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /lib/widgets/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluent_ui/fluent_ui.dart'; 2 | 3 | mixin PageMixin { 4 | Widget description({required Widget content}) { 5 | return Builder(builder: (context) { 6 | return Padding( 7 | padding: const EdgeInsetsDirectional.only(bottom: 4.0), 8 | child: DefaultTextStyle( 9 | style: FluentTheme.of(context).typography.body!, 10 | child: content, 11 | ), 12 | ); 13 | }); 14 | } 15 | 16 | Widget subtitle({required Widget content}) { 17 | return Builder(builder: (context) { 18 | return Padding( 19 | padding: const EdgeInsetsDirectional.only(top: 14.0, bottom: 2.0), 20 | child: DefaultTextStyle( 21 | style: FluentTheme.of(context).typography.subtitle!, 22 | child: content, 23 | ), 24 | ); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/widgets/settings_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluent_ui/fluent_ui.dart'; 2 | import 'package:yolx/utils/common_utils.dart'; 3 | 4 | class SettingsCard extends StatelessWidget { 5 | final String title; 6 | final String subtitle; 7 | final Widget content; 8 | final bool isExpander; 9 | 10 | const SettingsCard({ 11 | Key? key, 12 | required this.title, 13 | required this.subtitle, 14 | required this.content, 15 | this.isExpander = false, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | const spacer = SizedBox( 21 | height: 10.0, 22 | width: 10.0, 23 | ); 24 | if (isExpander == false) { 25 | return Card( 26 | borderRadius: const BorderRadius.all(Radius.circular(6.0)), 27 | child: Row( 28 | children: [ 29 | const SizedBox(width: 4), 30 | if (isDesktop || isTablet(MediaQuery.of(context))) ...[ 31 | Expanded( 32 | child: Column( 33 | crossAxisAlignment: CrossAxisAlignment.start, 34 | children: [ 35 | Text( 36 | title, 37 | style: const TextStyle( 38 | fontWeight: FontWeight.bold, 39 | ), 40 | ), 41 | Text(subtitle), 42 | ], 43 | ), 44 | ), 45 | const Spacer(), 46 | content, 47 | ] else ...[ 48 | Expanded( 49 | child: Column( 50 | crossAxisAlignment: CrossAxisAlignment.start, 51 | children: [ 52 | Text( 53 | title, 54 | style: const TextStyle( 55 | fontWeight: FontWeight.bold, 56 | ), 57 | ), 58 | Text(subtitle), 59 | content, 60 | ], 61 | ), 62 | ), 63 | ] 64 | ], 65 | ), 66 | ); 67 | } else { 68 | return Expander( 69 | header: Column( 70 | crossAxisAlignment: CrossAxisAlignment.start, 71 | children: [ 72 | spacer, 73 | Text( 74 | title, 75 | style: const TextStyle( 76 | fontWeight: FontWeight.bold, 77 | ), 78 | ), 79 | Text(subtitle), 80 | spacer, 81 | ], 82 | ), 83 | content: content, 84 | ); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/widgets/upload_torrent.dart: -------------------------------------------------------------------------------- 1 | import 'package:dotted_decoration/dotted_decoration.dart'; 2 | import 'package:fluent_ui/fluent_ui.dart'; 3 | 4 | Widget uploadTorrent(BuildContext context, String text) { 5 | return Container( 6 | decoration: DottedDecoration( 7 | shape: Shape.box, 8 | borderRadius: const BorderRadius.all(Radius.circular(6)), 9 | ), 10 | child: Column( 11 | mainAxisAlignment: MainAxisAlignment.center, 12 | children: [ 13 | const Icon(FluentIcons.bulk_upload, size: 40), 14 | const SizedBox(height: 10), 15 | Text( 16 | text, 17 | style: const TextStyle(fontSize: 16), 18 | ), 19 | ], 20 | ), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.10) 3 | project(runner LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "yolx") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "com.yoyo.yolx") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 58 | 59 | # Define the application target. To change its name, change BINARY_NAME above, 60 | # not the value here, or `flutter run` will no longer work. 61 | # 62 | # Any new source files that you add to the application should be added here. 63 | add_executable(${BINARY_NAME} 64 | "main.cc" 65 | "my_application.cc" 66 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 67 | ) 68 | 69 | # Apply the standard set of build settings. This can be removed for applications 70 | # that need different build settings. 71 | apply_standard_settings(${BINARY_NAME}) 72 | 73 | # Add dependency libraries. Add any application-specific dependencies here. 74 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 75 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 76 | 77 | # Run the Flutter tool portions of the build. This must not be removed. 78 | add_dependencies(${BINARY_NAME} flutter_assemble) 79 | 80 | # Only the install-generated bundle's copy of the executable will launch 81 | # correctly, since the resources must in the right relative locations. To avoid 82 | # people trying to run the unbundled copy, put it in a subdirectory instead of 83 | # the default top-level location. 84 | set_target_properties(${BINARY_NAME} 85 | PROPERTIES 86 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 87 | ) 88 | 89 | 90 | # Generated plugin build rules, which manage building the plugins and adding 91 | # them to the application. 92 | include(flutter/generated_plugins.cmake) 93 | 94 | 95 | # === Installation === 96 | # By default, "installing" just makes a relocatable bundle in the build 97 | # directory. 98 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 99 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 100 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 101 | endif() 102 | 103 | # Start with a clean build bundle directory every time. 104 | install(CODE " 105 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 106 | " COMPONENT Runtime) 107 | 108 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 109 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 110 | 111 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 112 | COMPONENT Runtime) 113 | 114 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 115 | COMPONENT Runtime) 116 | 117 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 118 | COMPONENT Runtime) 119 | 120 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 121 | install(FILES "${bundled_library}" 122 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 123 | COMPONENT Runtime) 124 | endforeach(bundled_library) 125 | 126 | # Copy the native assets provided by the build.dart from all packages. 127 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") 128 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 129 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 130 | COMPONENT Runtime) 131 | 132 | # Fully re-copy the assets directory on each build to avoid having stale files 133 | # from a previous install. 134 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 135 | install(CODE " 136 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 137 | " COMPONENT Runtime) 138 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 139 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 140 | 141 | # Install the AOT library on non-Debug builds only. 142 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 143 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 144 | COMPONENT Runtime) 145 | endif() 146 | 147 | install(DIRECTORY "${PROJECT_MYPLUGIN_DIR}" 148 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 149 | FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ 150 | GROUP_EXECUTE GROUP_READ 151 | WORLD_EXECUTE WORLD_READ 152 | COMPONENT Runtime) -------------------------------------------------------------------------------- /linux/bin/plugin/aria2/yolx_aria2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/linux/bin/plugin/aria2/yolx_aria2c -------------------------------------------------------------------------------- /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 | 90 | set(PROJECT_MYPLUGIN_DIR "${PROJECT_DIR}/linux/bin/" PARENT_SCOPE) -------------------------------------------------------------------------------- /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 | #include 16 | #include 17 | 18 | void fl_register_plugins(FlPluginRegistry* registry) { 19 | g_autoptr(FlPluginRegistrar) desktop_drop_registrar = 20 | fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); 21 | desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); 22 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 23 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 24 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 25 | g_autoptr(FlPluginRegistrar) handy_window_registrar = 26 | fl_plugin_registry_get_registrar_for_plugin(registry, "HandyWindowPlugin"); 27 | handy_window_plugin_register_with_registrar(handy_window_registrar); 28 | g_autoptr(FlPluginRegistrar) screen_retriever_registrar = 29 | fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); 30 | screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); 31 | g_autoptr(FlPluginRegistrar) system_theme_registrar = 32 | fl_plugin_registry_get_registrar_for_plugin(registry, "SystemThemePlugin"); 33 | system_theme_plugin_register_with_registrar(system_theme_registrar); 34 | g_autoptr(FlPluginRegistrar) tray_manager_registrar = 35 | fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin"); 36 | tray_manager_plugin_register_with_registrar(tray_manager_registrar); 37 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 38 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 39 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 40 | g_autoptr(FlPluginRegistrar) window_manager_registrar = 41 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); 42 | window_manager_plugin_register_with_registrar(window_manager_registrar); 43 | } 44 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | desktop_drop 7 | file_selector_linux 8 | handy_window 9 | screen_retriever 10 | system_theme 11 | tray_manager 12 | url_launcher_linux 13 | window_manager 14 | ) 15 | 16 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 17 | ) 18 | 19 | set(PLUGIN_BUNDLED_LIBRARIES) 20 | 21 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 23 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 26 | endforeach(plugin) 27 | 28 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 29 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 30 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 31 | endforeach(ffi_plugin) 32 | -------------------------------------------------------------------------------- /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 | gtk_window_set_title(window, "yolx"); 24 | 25 | gtk_window_set_default_size(window, 1280, 720); 26 | //gtk_widget_show(GTK_WIDGET(window)); 27 | 28 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 29 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 30 | 31 | FlView* view = fl_view_new(project); 32 | //gtk_widget_show(GTK_WIDGET(view)); 33 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 34 | 35 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 36 | 37 | gtk_widget_realize(GTK_WIDGET(window)); 38 | gtk_widget_show(GTK_WIDGET(view)); 39 | gtk_widget_grab_focus(GTK_WIDGET(view)); 40 | } 41 | 42 | // Implements GApplication::local_command_line. 43 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 44 | MyApplication* self = MY_APPLICATION(application); 45 | // Strip out the first argument as it is the binary name. 46 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 47 | 48 | g_autoptr(GError) error = nullptr; 49 | if (!g_application_register(application, nullptr, &error)) { 50 | g_warning("Failed to register: %s", error->message); 51 | *exit_status = 1; 52 | return TRUE; 53 | } 54 | 55 | g_application_activate(application); 56 | *exit_status = 0; 57 | 58 | return TRUE; 59 | } 60 | 61 | // Implements GObject::dispose. 62 | static void my_application_dispose(GObject* object) { 63 | MyApplication* self = MY_APPLICATION(object); 64 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 65 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 66 | } 67 | 68 | static void my_application_class_init(MyApplicationClass* klass) { 69 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 70 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 71 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 72 | } 73 | 74 | static void my_application_init(MyApplication* self) {} 75 | 76 | MyApplication* my_application_new() { 77 | return MY_APPLICATION(g_object_new(my_application_get_type(), 78 | "application-id", APPLICATION_ID, 79 | "flags", G_APPLICATION_NON_UNIQUE, 80 | nullptr)); 81 | } 82 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /linux/packaging/appimage/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: Yolx 2 | 3 | icon: assets/logo.png 4 | 5 | keywords: 6 | - Application 7 | 8 | generic_name: An aria2 client Application 9 | 10 | categories: 11 | - Application 12 | 13 | startup_notify: true 14 | 15 | # 您可以指定要与您的应用捆绑的共享库 16 | # 17 | # flutter_distributor 会自动检测您的应用所依赖的共享库,但您也可以在此处手动指定它们。 18 | # 19 | # 以下示例展示了如何将 libcurl 库与您的应用捆绑在一起 20 | # 21 | # include: 22 | # - libcurl.so.4 23 | include: [] -------------------------------------------------------------------------------- /linux/packaging/deb/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: Yolx 2 | package_name: yolx 3 | maintainer: 4 | name: Yoyo 5 | email: admin@yzzi.icu 6 | co_authors: 7 | - name: Yoyo 8 | email: admin@yzzi.icu 9 | priority: optional 10 | section: x11 11 | installed_size: 6604 12 | essential: false 13 | icon: assets/logo.png 14 | 15 | postinstall_scripts: 16 | - echo "Installed Yolx" 17 | postuninstall_scripts: 18 | - echo "Surprised Pickachu face" 19 | 20 | keywords: 21 | - Application 22 | 23 | generic_name: Downloader Application 24 | 25 | categories: 26 | - Network 27 | 28 | startup_notify: true 29 | 30 | dependencies: 31 | - libappindicator3-1 32 | - libayatana-appindicator3-1 -------------------------------------------------------------------------------- /linux/packaging/rpm/make_config.yaml: -------------------------------------------------------------------------------- 1 | icon: assets/logo.png 2 | summary: An aria2 client application 3 | group: Application/Emulator 4 | vendor: Yoyo 5 | packager: Yoyo 6 | packagerEmail: admin@yzzi.icu 7 | license: GPLv3 8 | url: https://github.com/uiYzzi/Yolx 9 | 10 | display_name: Yolx 11 | 12 | keywords: 13 | - Application 14 | 15 | generic_name: An aria2 client application 16 | 17 | categories: 18 | - Network 19 | 20 | startup_notify: true -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: yolx 2 | description: An aria2 client 3 | 4 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 5 | 6 | version: 0.3.9+1 7 | 8 | environment: 9 | sdk: '>=2.17.0 <3.0.0' 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | flutter_localizations: 15 | sdk: flutter 16 | file_selector: ^1.0.2 17 | uuid: ^4.3.3 18 | path_provider: ^2.1.2 19 | flutter_single_instance: ^0.0.1 20 | path: ^1.8.0 21 | logger: ^2.0.2+1 22 | fluent_ui: ^4.8.6 23 | provider: ^6.0.5 24 | system_theme: ^2.3.1 25 | window_manager: ^0.3.0 26 | url_launcher: ^6.1.7 27 | go_router: ^10.0.0 28 | shared_preferences: ^2.2.2 29 | handy_window: ^0.3.1 30 | tray_manager: ^0.2.1 31 | desktop_drop: ^0.4.4 32 | dotted_decoration: ^2.0.0 33 | flutter_svg: ^2.0.10+1 34 | device_info_plus: ^9.1.2 35 | permission_handler: ^11.3.0 36 | flutter_easyloading: ^3.0.5 37 | 38 | dev_dependencies: 39 | flutter_test: 40 | sdk: flutter 41 | flutter_native_splash: ^2.1.6 42 | flutter_lints: ^2.0.2 43 | flutter_launcher_icons: ^0.13.1 44 | 45 | flutter: 46 | uses-material-design: true 47 | assets: 48 | - assets/ 49 | - lib/resources/ 50 | 51 | flutter_native_splash: 52 | color: "#000000" 53 | image: assets/logo.png 54 | 55 | flutter_launcher_icons: 56 | android: "ic_launcher" 57 | adaptive_icon_background: "#131367" 58 | adaptive_icon_foreground: "assets/icon-foreground.png" 59 | ios: false 60 | image_path: "assets/logo.png" 61 | min_sdk_android: 21 # android min sdk min:16, default 21 62 | windows: 63 | generate: true 64 | image_path: "assets/logo.png" 65 | icon_size: 48 # min:48, max:256, default: 48 66 | macos: 67 | generate: true 68 | image_path: "assets/logo.png" 69 | flutter_intl: 70 | enabled: true 71 | -------------------------------------------------------------------------------- /symbols/app.android-arm.symbols: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/symbols/app.android-arm.symbols -------------------------------------------------------------------------------- /symbols/app.android-arm64.symbols: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/symbols/app.android-arm64.symbols -------------------------------------------------------------------------------- /symbols/app.android-x64.symbols: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/symbols/app.android-x64.symbols -------------------------------------------------------------------------------- /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:yolx/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 | -------------------------------------------------------------------------------- /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/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(yolx LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "yolx") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(SET CMP0063 NEW) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | # Generated plugin build rules, which manage building the plugins and adding 56 | # them to the application. 57 | include(flutter/generated_plugins.cmake) 58 | 59 | 60 | # === Installation === 61 | # Support files are copied into place next to the executable, so that it can 62 | # run in place. This is done instead of making a separate bundle (as on Linux) 63 | # so that building and running from within Visual Studio will work. 64 | set(BUILD_BUNDLE_DIR "$") 65 | # Make the "install" step default, as it's required to run. 66 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 67 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 68 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 69 | endif() 70 | 71 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 72 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 73 | 74 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 75 | COMPONENT Runtime) 76 | 77 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 78 | COMPONENT Runtime) 79 | 80 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 81 | COMPONENT Runtime) 82 | 83 | if(PLUGIN_BUNDLED_LIBRARIES) 84 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 85 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 86 | COMPONENT Runtime) 87 | endif() 88 | 89 | # Fully re-copy the assets directory on each build to avoid having stale files 90 | # from a previous install. 91 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 92 | install(CODE " 93 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 94 | " COMPONENT Runtime) 95 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 96 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 97 | 98 | # Install the AOT library on non-Debug builds only. 99 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 100 | CONFIGURATIONS Profile;Release 101 | COMPONENT Runtime) 102 | install(DIRECTORY "${PROJECT_MYPLUGIN_DIR}" 103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) -------------------------------------------------------------------------------- /windows/bin/plugin/aria2/yolx_aria2c.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/windows/bin/plugin/aria2/yolx_aria2c.exe -------------------------------------------------------------------------------- /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 | set(PROJECT_MYPLUGIN_DIR "${PROJECT_DIR}/windows/bin/" PARENT_SCOPE) 87 | 88 | # === Flutter tool backend === 89 | # _phony_ is a non-existent file to force this command to run every time, 90 | # since currently there's no way to get a full input/output list from the 91 | # flutter tool. 92 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 93 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 94 | add_custom_command( 95 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 96 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 97 | ${CPP_WRAPPER_SOURCES_APP} 98 | ${PHONY_OUTPUT} 99 | COMMAND ${CMAKE_COMMAND} -E env 100 | ${FLUTTER_TOOL_ENVIRONMENT} 101 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 102 | ${FLUTTER_TARGET_PLATFORM} $ 103 | VERBATIM 104 | ) 105 | add_custom_target(flutter_assemble DEPENDS 106 | "${FLUTTER_LIBRARY}" 107 | ${FLUTTER_LIBRARY_HEADERS} 108 | ${CPP_WRAPPER_SOURCES_CORE} 109 | ${CPP_WRAPPER_SOURCES_PLUGIN} 110 | ${CPP_WRAPPER_SOURCES_APP} 111 | ) 112 | -------------------------------------------------------------------------------- /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 | 18 | void RegisterPlugins(flutter::PluginRegistry* registry) { 19 | DesktopDropPluginRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("DesktopDropPlugin")); 21 | FileSelectorWindowsRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 23 | PermissionHandlerWindowsPluginRegisterWithRegistrar( 24 | registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); 25 | ScreenRetrieverPluginRegisterWithRegistrar( 26 | registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); 27 | SystemThemePluginRegisterWithRegistrar( 28 | registry->GetRegistrarForPlugin("SystemThemePlugin")); 29 | TrayManagerPluginRegisterWithRegistrar( 30 | registry->GetRegistrarForPlugin("TrayManagerPlugin")); 31 | UrlLauncherWindowsRegisterWithRegistrar( 32 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 33 | WindowManagerPluginRegisterWithRegistrar( 34 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 35 | } 36 | -------------------------------------------------------------------------------- /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 | desktop_drop 7 | file_selector_windows 8 | permission_handler_windows 9 | screen_retriever 10 | system_theme 11 | tray_manager 12 | url_launcher_windows 13 | window_manager 14 | ) 15 | 16 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 17 | ) 18 | 19 | set(PLUGIN_BUNDLED_LIBRARIES) 20 | 21 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 23 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 26 | endforeach(plugin) 27 | 28 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 29 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 30 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 31 | endforeach(ffi_plugin) 32 | -------------------------------------------------------------------------------- /windows/packaging/exe/inno_setup.iss: -------------------------------------------------------------------------------- 1 | #define MyAppExeName "{{OUTPUT_BASE_FILENAME}}" 2 | #define MyAppAssocName "{{DISPLAY_NAME}}" + " File" 3 | #define MyAppAssocExt ".myp" 4 | #define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt 5 | [Setup] 6 | AppId={{APP_ID}} 7 | AppVersion={{APP_VERSION}} 8 | AppName={{DISPLAY_NAME}} 9 | AppPublisher={{PUBLISHER_NAME}} 10 | AppPublisherURL={{PUBLISHER_URL}} 11 | AppSupportURL={{PUBLISHER_URL}} 12 | AppUpdatesURL={{PUBLISHER_URL}} 13 | DefaultDirName={autopf}\{{INSTALL_DIR_NAME}} 14 | DisableProgramGroupPage=yes 15 | ChangesAssociations=yes 16 | OutputDir=. 17 | OutputBaseFilename={{OUTPUT_BASE_FILENAME}} 18 | Compression=lzma 19 | SolidCompression=yes 20 | SetupIconFile={{SETUP_ICON_FILE}} 21 | WizardStyle=modern 22 | PrivilegesRequired={{PRIVILEGES_REQUIRED}} 23 | ArchitecturesAllowed=x64 24 | ArchitecturesInstallIn64BitMode=x64 25 | 26 | [Languages] 27 | {% for locale in LOCALES %} 28 | {% if locale == 'en' %}Name: "english"; MessagesFile: "compiler:Default.isl"{% endif %} 29 | {% if locale == 'hy' %}Name: "armenian"; MessagesFile: "compiler:Languages\\Armenian.isl"{% endif %} 30 | {% if locale == 'bg' %}Name: "bulgarian"; MessagesFile: "compiler:Languages\\Bulgarian.isl"{% endif %} 31 | {% if locale == 'ca' %}Name: "catalan"; MessagesFile: "compiler:Languages\\Catalan.isl"{% endif %} 32 | {% if locale == 'zh' %}Name: "chinesesimplified"; MessagesFile: "compiler:Languages\\ChineseSimplified.isl"{% endif %} 33 | {% if locale == 'co' %}Name: "corsican"; MessagesFile: "compiler:Languages\\Corsican.isl"{% endif %} 34 | {% if locale == 'cs' %}Name: "czech"; MessagesFile: "compiler:Languages\\Czech.isl"{% endif %} 35 | {% if locale == 'da' %}Name: "danish"; MessagesFile: "compiler:Languages\\Danish.isl"{% endif %} 36 | {% if locale == 'nl' %}Name: "dutch"; MessagesFile: "compiler:Languages\\Dutch.isl"{% endif %} 37 | {% if locale == 'fi' %}Name: "finnish"; MessagesFile: "compiler:Languages\\Finnish.isl"{% endif %} 38 | {% if locale == 'fr' %}Name: "french"; MessagesFile: "compiler:Languages\\French.isl"{% endif %} 39 | {% if locale == 'de' %}Name: "german"; MessagesFile: "compiler:Languages\\German.isl"{% endif %} 40 | {% if locale == 'he' %}Name: "hebrew"; MessagesFile: "compiler:Languages\\Hebrew.isl"{% endif %} 41 | {% if locale == 'is' %}Name: "icelandic"; MessagesFile: "compiler:Languages\\Icelandic.isl"{% endif %} 42 | {% if locale == 'it' %}Name: "italian"; MessagesFile: "compiler:Languages\\Italian.isl"{% endif %} 43 | {% if locale == 'ja' %}Name: "japanese"; MessagesFile: "compiler:Languages\\Japanese.isl"{% endif %} 44 | {% if locale == 'no' %}Name: "norwegian"; MessagesFile: "compiler:Languages\\Norwegian.isl"{% endif %} 45 | {% if locale == 'pl' %}Name: "polish"; MessagesFile: "compiler:Languages\\Polish.isl"{% endif %} 46 | {% if locale == 'pt' %}Name: "portuguese"; MessagesFile: "compiler:Languages\\Portuguese.isl"{% endif %} 47 | {% if locale == 'ru' %}Name: "russian"; MessagesFile: "compiler:Languages\\Russian.isl"{% endif %} 48 | {% if locale == 'sk' %}Name: "slovak"; MessagesFile: "compiler:Languages\\Slovak.isl"{% endif %} 49 | {% if locale == 'sl' %}Name: "slovenian"; MessagesFile: "compiler:Languages\\Slovenian.isl"{% endif %} 50 | {% if locale == 'es' %}Name: "spanish"; MessagesFile: "compiler:Languages\\Spanish.isl"{% endif %} 51 | {% if locale == 'tr' %}Name: "turkish"; MessagesFile: "compiler:Languages\\Turkish.isl"{% endif %} 52 | {% if locale == 'uk' %}Name: "ukrainian"; MessagesFile: "compiler:Languages\\Ukrainian.isl"{% endif %} 53 | {% endfor %} 54 | 55 | [Tasks] 56 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if CREATE_DESKTOP_ICON != true %}unchecked{% else %}checkedonce{% endif %} 57 | Name: "launchAtStartup"; Description: "{cm:AutoStartProgram,{{DISPLAY_NAME}}}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if LAUNCH_AT_STARTUP != true %}unchecked{% else %}checkedonce{% endif %} 58 | [Files] 59 | Source: "{{SOURCE_DIR}}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 60 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 61 | 62 | [Registry] 63 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue 64 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey 65 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0" 66 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1""" 67 | Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".myp"; ValueData: "" 68 | 69 | [Icons] 70 | Name: "{autoprograms}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}" 71 | Name: "{autodesktop}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"; Tasks: desktopicon 72 | Name: "{userstartup}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"; WorkingDir: "{app}"; Tasks: launchAtStartup 73 | [Run] 74 | Filename: "{app}\\{{EXECUTABLE_NAME}}"; Description: "{cm:LaunchProgram,{{DISPLAY_NAME}}}"; Flags: {% if PRIVILEGES_REQUIRED == 'admin' %}runascurrentuser{% endif %} nowait postinstall skipifsilent -------------------------------------------------------------------------------- /windows/packaging/exe/make_config.yaml: -------------------------------------------------------------------------------- 1 | script_template: inno_setup.iss 2 | app_id: 700162B8-7C36-43BC-A167-9C34486E0099 3 | publisher_name: Yoyo 4 | publisher_url: https://github.com/uiYzzi/Yolx 5 | display_name: Yolx 6 | create_desktop_icon: true 7 | install_dir_name: Yolx 8 | privileges_required: lowest 9 | setup_icon_file: ..\..\windows\runner\resources\app_icon.ico 10 | locales: 11 | - en 12 | - zh -------------------------------------------------------------------------------- /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.yolx" "\0" 93 | VALUE "FileDescription", "yolx" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "yolx" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2023 com.yolx. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "yolx.exe" "\0" 98 | VALUE "ProductName", "yolx" "\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"yolx", 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/uiYzzi/Yolx/e97cead00c501d5f79e2924b803576ab478298fe/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | std::string utf8_string; 52 | if (target_length == 0 || target_length > utf8_string.max_size()) { 53 | return utf8_string; 54 | } 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "resource.h" 7 | 8 | namespace { 9 | 10 | /// Window attribute that enables dark mode window decorations. 11 | /// 12 | /// Redefined in case the developer's machine has a Windows SDK older than 13 | /// version 10.0.22000.0. 14 | /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute 15 | #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE 16 | #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 17 | #endif 18 | 19 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 20 | 21 | /// Registry key for app theme preference. 22 | /// 23 | /// A value of 0 indicates apps should use dark mode. A non-zero or missing 24 | /// value indicates apps should use light mode. 25 | constexpr const wchar_t kGetPreferredBrightnessRegKey[] = 26 | L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; 27 | constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; 28 | 29 | // The number of Win32Window objects that currently exist. 30 | static int g_active_window_count = 0; 31 | 32 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 33 | 34 | // Scale helper to convert logical scaler values to physical using passed in 35 | // scale factor 36 | int Scale(int source, double scale_factor) { 37 | return static_cast(source * scale_factor); 38 | } 39 | 40 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 41 | // This API is only needed for PerMonitor V1 awareness mode. 42 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 43 | HMODULE user32_module = LoadLibraryA("User32.dll"); 44 | if (!user32_module) { 45 | return; 46 | } 47 | auto enable_non_client_dpi_scaling = 48 | reinterpret_cast( 49 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 50 | if (enable_non_client_dpi_scaling != nullptr) { 51 | enable_non_client_dpi_scaling(hwnd); 52 | } 53 | FreeLibrary(user32_module); 54 | } 55 | 56 | } // namespace 57 | 58 | // Manages the Win32Window's window class registration. 59 | class WindowClassRegistrar { 60 | public: 61 | ~WindowClassRegistrar() = default; 62 | 63 | // Returns the singleton registar instance. 64 | static WindowClassRegistrar* GetInstance() { 65 | if (!instance_) { 66 | instance_ = new WindowClassRegistrar(); 67 | } 68 | return instance_; 69 | } 70 | 71 | // Returns the name of the window class, registering the class if it hasn't 72 | // previously been registered. 73 | const wchar_t* GetWindowClass(); 74 | 75 | // Unregisters the window class. Should only be called if there are no 76 | // instances of the window. 77 | void UnregisterWindowClass(); 78 | 79 | private: 80 | WindowClassRegistrar() = default; 81 | 82 | static WindowClassRegistrar* instance_; 83 | 84 | bool class_registered_ = false; 85 | }; 86 | 87 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 88 | 89 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 90 | if (!class_registered_) { 91 | WNDCLASS window_class{}; 92 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 93 | window_class.lpszClassName = kWindowClassName; 94 | window_class.style = CS_HREDRAW | CS_VREDRAW; 95 | window_class.cbClsExtra = 0; 96 | window_class.cbWndExtra = 0; 97 | window_class.hInstance = GetModuleHandle(nullptr); 98 | window_class.hIcon = 99 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 100 | window_class.hbrBackground = 0; 101 | window_class.lpszMenuName = nullptr; 102 | window_class.lpfnWndProc = Win32Window::WndProc; 103 | RegisterClass(&window_class); 104 | class_registered_ = true; 105 | } 106 | return kWindowClassName; 107 | } 108 | 109 | void WindowClassRegistrar::UnregisterWindowClass() { 110 | UnregisterClass(kWindowClassName, nullptr); 111 | class_registered_ = false; 112 | } 113 | 114 | Win32Window::Win32Window() { 115 | ++g_active_window_count; 116 | } 117 | 118 | Win32Window::~Win32Window() { 119 | --g_active_window_count; 120 | Destroy(); 121 | } 122 | 123 | bool Win32Window::Create(const std::wstring& title, 124 | const Point& origin, 125 | const Size& size) { 126 | Destroy(); 127 | 128 | const wchar_t* window_class = 129 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 130 | 131 | const POINT target_point = {static_cast(origin.x), 132 | static_cast(origin.y)}; 133 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 134 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 135 | double scale_factor = dpi / 96.0; 136 | 137 | HWND window = CreateWindow( 138 | window_class, title.c_str(), 139 | WS_OVERLAPPEDWINDOW, // do not add WS_VISIBLE since the window will be shown later 140 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 141 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 142 | nullptr, nullptr, GetModuleHandle(nullptr), this); 143 | 144 | if (!window) { 145 | return false; 146 | } 147 | 148 | UpdateTheme(window); 149 | 150 | return OnCreate(); 151 | } 152 | 153 | bool Win32Window::Show() { 154 | return ShowWindow(window_handle_, SW_SHOWNORMAL); 155 | } 156 | 157 | // static 158 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 159 | UINT const message, 160 | WPARAM const wparam, 161 | LPARAM const lparam) noexcept { 162 | if (message == WM_NCCREATE) { 163 | auto window_struct = reinterpret_cast(lparam); 164 | SetWindowLongPtr(window, GWLP_USERDATA, 165 | reinterpret_cast(window_struct->lpCreateParams)); 166 | 167 | auto that = static_cast(window_struct->lpCreateParams); 168 | EnableFullDpiSupportIfAvailable(window); 169 | that->window_handle_ = window; 170 | } else if (Win32Window* that = GetThisFromHandle(window)) { 171 | return that->MessageHandler(window, message, wparam, lparam); 172 | } 173 | 174 | return DefWindowProc(window, message, wparam, lparam); 175 | } 176 | 177 | LRESULT 178 | Win32Window::MessageHandler(HWND hwnd, 179 | UINT const message, 180 | WPARAM const wparam, 181 | LPARAM const lparam) noexcept { 182 | switch (message) { 183 | case WM_DESTROY: 184 | window_handle_ = nullptr; 185 | Destroy(); 186 | if (quit_on_close_) { 187 | PostQuitMessage(0); 188 | } 189 | return 0; 190 | 191 | case WM_DPICHANGED: { 192 | auto newRectSize = reinterpret_cast(lparam); 193 | LONG newWidth = newRectSize->right - newRectSize->left; 194 | LONG newHeight = newRectSize->bottom - newRectSize->top; 195 | 196 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 197 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 198 | 199 | return 0; 200 | } 201 | case WM_SIZE: { 202 | RECT rect = GetClientArea(); 203 | if (child_content_ != nullptr) { 204 | // Size and position the child window. 205 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 206 | rect.bottom - rect.top, TRUE); 207 | } 208 | return 0; 209 | } 210 | 211 | case WM_ACTIVATE: 212 | if (child_content_ != nullptr) { 213 | SetFocus(child_content_); 214 | } 215 | return 0; 216 | 217 | case WM_DWMCOLORIZATIONCOLORCHANGED: 218 | UpdateTheme(hwnd); 219 | return 0; 220 | } 221 | 222 | return DefWindowProc(window_handle_, message, wparam, lparam); 223 | } 224 | 225 | void Win32Window::Destroy() { 226 | OnDestroy(); 227 | 228 | if (window_handle_) { 229 | DestroyWindow(window_handle_); 230 | window_handle_ = nullptr; 231 | } 232 | if (g_active_window_count == 0) { 233 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 234 | } 235 | } 236 | 237 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 238 | return reinterpret_cast( 239 | GetWindowLongPtr(window, GWLP_USERDATA)); 240 | } 241 | 242 | void Win32Window::SetChildContent(HWND content) { 243 | child_content_ = content; 244 | SetParent(content, window_handle_); 245 | RECT frame = GetClientArea(); 246 | 247 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 248 | frame.bottom - frame.top, true); 249 | 250 | SetFocus(child_content_); 251 | } 252 | 253 | RECT Win32Window::GetClientArea() { 254 | RECT frame; 255 | GetClientRect(window_handle_, &frame); 256 | return frame; 257 | } 258 | 259 | HWND Win32Window::GetHandle() { 260 | return window_handle_; 261 | } 262 | 263 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 264 | quit_on_close_ = quit_on_close; 265 | } 266 | 267 | bool Win32Window::OnCreate() { 268 | // No-op; provided for subclasses. 269 | return true; 270 | } 271 | 272 | void Win32Window::OnDestroy() { 273 | // No-op; provided for subclasses. 274 | } 275 | 276 | void Win32Window::UpdateTheme(HWND const window) { 277 | DWORD light_mode; 278 | DWORD light_mode_size = sizeof(light_mode); 279 | LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, 280 | kGetPreferredBrightnessRegValue, 281 | RRF_RT_REG_DWORD, nullptr, &light_mode, 282 | &light_mode_size); 283 | 284 | if (result == ERROR_SUCCESS) { 285 | BOOL enable_dark_mode = light_mode == 0; 286 | DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, 287 | &enable_dark_mode, sizeof(enable_dark_mode)); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /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 | // responsponds 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 | -------------------------------------------------------------------------------- /yolx.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | --------------------------------------------------------------------------------