diff --git a/.metadata b/.metadata index f206c92..ea4c51c 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "67457e669f79e9f8d13d7a68fe09775fefbb79f4" + revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" channel: "stable" project_type: app @@ -13,11 +13,23 @@ project_type: app migration: platforms: - platform: root - create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4 - base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4 + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: android + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: ios + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 - platform: linux - create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4 - base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4 + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: macos + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: windows + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 # User provided section diff --git a/.vscode/settings.json b/.vscode/settings.json index a01de61..b16d741 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "cmake.sourceDirectory": "C:/Users/Yoyo/Documents/GitHub/Yolx/windows" + "cmake.sourceDirectory": "C:/Users/Yoyo/Documents/GitHub/Yolx/windows", + "java.configuration.updateBuildConfiguration": "interactive" } \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..8d58a62 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,85 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.yoyo.yolx" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main { + java.srcDirs += 'src/main/kotlin' + jniLibs.srcDirs=['src/main/jniLibs'] + } + } + + repositories { + flatDir { + dirs 'libs' + } + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.yoyo.yolx" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + ndk {} + } + + buildTypes { + debug { + ndk { + // abiFilters "x86" + } + } + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation fileTree(include: ['*.jar','*.so'], dir: 'libs') + implementation 'org.bouncycastle:bcprov-jdk16:1.46' +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..50cfb13 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/jniLibs/arm64-v8a/libaria2c.so b/android/app/src/main/jniLibs/arm64-v8a/libaria2c.so new file mode 100644 index 0000000..25fe21d Binary files /dev/null and b/android/app/src/main/jniLibs/arm64-v8a/libaria2c.so differ diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libaria2c.so b/android/app/src/main/jniLibs/armeabi-v7a/libaria2c.so new file mode 100644 index 0000000..b3fa103 Binary files /dev/null and b/android/app/src/main/jniLibs/armeabi-v7a/libaria2c.so differ diff --git a/android/app/src/main/jniLibs/x86/libaria2c.so b/android/app/src/main/jniLibs/x86/libaria2c.so new file mode 100644 index 0000000..5f96e57 Binary files /dev/null and b/android/app/src/main/jniLibs/x86/libaria2c.so differ diff --git a/android/app/src/main/jniLibs/x86_64/libaria2c.so b/android/app/src/main/jniLibs/x86_64/libaria2c.so new file mode 100644 index 0000000..f25d50a Binary files /dev/null and b/android/app/src/main/jniLibs/x86_64/libaria2c.so differ diff --git a/android/app/src/main/kotlin/com/example/yolx/MainActivity.kt b/android/app/src/main/kotlin/com/example/yolx/MainActivity.kt new file mode 100644 index 0000000..96ea081 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/yolx/MainActivity.kt @@ -0,0 +1,51 @@ +package com.yoyo.yolx + +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.Settings; +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel +import java.io.File + +class MainActivity: FlutterActivity() { + private val CHANNEL = "com.yoyo.flutter_native_channel/native_methods" + private var channelResult: MethodChannel.Result? =null; + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> + channelResult=result; + if(call.method == "nativeLibraryDir"){ + result.success(applicationInfo.nativeLibraryDir) + }else if(call.method == "requestPermission"){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { //30 + // 先判断有没有权限 + if (!Environment.isExternalStorageManager()) { + //跳转到设置界面引导用户打开 + val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) + intent.data = Uri.parse("package:$packageName") + startActivityForResult(intent, 6666) + }else{ + result.success(true) + } + } + + } else { + result.notImplemented() + } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode==6666){ + if (Environment.isExternalStorageManager()){ + channelResult?.success(true) + } else { + channelResult?.success(false); + } + } + } +} diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..dca93c0 --- /dev/null +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main/res/xml/provider_paths.xml b/android/app/src/main/res/xml/provider_paths.xml new file mode 100644 index 0000000..7825c0c --- /dev/null +++ b/android/app/src/main/res/xml/provider_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..7ea75a2 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,34 @@ +buildscript { + ext.kotlin_version = '1.9.10' + repositories { + // google() + // mavenCentral() + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://maven.aliyun.com/repository/central' } + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + // google() + // mavenCentral() + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://maven.aliyun.com/repository/central' } + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/android/yolx_android.iml b/android/yolx_android.iml new file mode 100644 index 0000000..1899969 --- /dev/null +++ b/android/yolx_android.iml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/main.dart b/lib/main.dart index ba6264d..7c92bb4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,26 +18,14 @@ import 'package:system_theme/system_theme.dart'; import 'package:url_launcher/link.dart'; import 'package:window_manager/window_manager.dart'; import 'package:yolx/utils/aria2_manager.dart'; +import 'package:yolx/utils/common_utils.dart'; import 'package:yolx/utils/log.dart'; import 'theme.dart'; -/// Checks if the current environment is a desktop environment. -bool get isDesktop { - if (kIsWeb) return false; - return [ - TargetPlatform.windows, - TargetPlatform.linux, - TargetPlatform.macOS, - ].contains(defaultTargetPlatform); -} - void main() async { WidgetsFlutterBinding.ensureInitialized(); - if (!await FlutterSingleInstance.platform.isFirstInstance()) { - Log.w("App is already running"); - exit(0); - } + await Global.init(); if (!kIsWeb && [ @@ -48,6 +36,11 @@ void main() async { } if (isDesktop) { + if (!await FlutterSingleInstance.platform.isFirstInstance()) { + Log.w("App is already running"); + exit(0); + } + await WindowManager.instance.ensureInitialized(); windowManager.waitUntilReadyToShow().then((_) async { await windowManager.setTitleBarStyle( @@ -61,7 +54,6 @@ void main() async { await windowManager.setPreventClose(true); await windowManager.setSkipTaskbar(false); }); - Aria2Manager().startServer(); await trayManager.setIcon( Platform.isWindows ? 'assets/logo.ico' : 'assets/logo.png', ); @@ -82,6 +74,8 @@ void main() async { ); await trayManager.setContextMenu(menu); } + await Aria2Manager().initAria2Conf(); + Aria2Manager().startServer(); runApp( MultiProvider( @@ -309,7 +303,7 @@ class _MyHomePageState extends State appBar: NavigationAppBar( automaticallyImplyLeading: false, title: () { - if (kIsWeb) { + if (!isDesktop) { return const Align( alignment: AlignmentDirectional.centerStart, child: Text(appTitle), @@ -322,8 +316,8 @@ class _MyHomePageState extends State ), ); }(), - actions: Row(mainAxisAlignment: MainAxisAlignment.end, children: const [ - if (!kIsWeb) WindowButtons(), + actions: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + if (isDesktop) const WindowButtons(), ]), ), paneBodyBuilder: (item, child) { diff --git a/linux/bin/plugin/aria2/yolx_aria2.conf b/lib/resources/yolx_aria2.conf similarity index 100% rename from linux/bin/plugin/aria2/yolx_aria2.conf rename to lib/resources/yolx_aria2.conf diff --git a/lib/screens/downloading.dart b/lib/screens/downloading.dart index 29304fc..c2f22f8 100644 --- a/lib/screens/downloading.dart +++ b/lib/screens/downloading.dart @@ -83,7 +83,9 @@ class _DownloadingPageState extends State with PageMixin { assert(debugCheckHasFluentTheme(context)); var downloadList = Provider.of(context).downloadingList; return Padding( - padding: const EdgeInsets.all(24.0), + padding: EdgeInsets.all((MediaQuery.sizeOf(context).width < 640.0) + ? 12.0 + : kPageDefaultVerticalPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index c8de6ea..da2c4ea 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -9,6 +9,7 @@ import 'package:yolx/generated/l10n.dart'; import 'package:yolx/utils/aria2_manager.dart'; // ignore: library_prefixes import 'package:yolx/utils/ariar2_http_utils.dart' as Aria2Http; +import 'package:yolx/utils/common_utils.dart'; import 'package:yolx/utils/tracker_http_utils.dart'; import 'package:yolx/widgets/settings_card.dart'; import '../theme.dart'; @@ -123,6 +124,9 @@ class _SettingsState extends State with PageMixin { context.findAncestorWidgetOfExactType()?.supportedLocales; var currentLocale = appTheme.locale ?? Localizations.maybeLocaleOf(context); return ScaffoldPage.scrollable( + padding: EdgeInsets.all((MediaQuery.sizeOf(context).width < 640.0) + ? 12.0 + : kPageDefaultVerticalPadding), header: PageHeader(title: Text(S.of(context).settings)), children: [ Text( @@ -197,32 +201,34 @@ class _SettingsState extends State with PageMixin { ), ), spacer, - SettingsCard( - title: S.of(context).navigationMode, - subtitle: S.of(context).setsTheDisplayModeOfTheNavigationPane, - isExpander: true, - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: List.generate(PaneDisplayMode.values.length, (index) { - final mode = PaneDisplayMode.values[index]; - return Padding( - padding: const EdgeInsetsDirectional.only(bottom: 8.0), - child: RadioButton( - checked: appTheme.displayMode == mode, - onChanged: (value) async { - if (value) appTheme.displayMode = mode; + if (isDesktop || isTablet(MediaQuery.of(context))) ...[ + SettingsCard( + title: S.of(context).navigationMode, + subtitle: S.of(context).setsTheDisplayModeOfTheNavigationPane, + isExpander: true, + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: List.generate(PaneDisplayMode.values.length, (index) { + final mode = PaneDisplayMode.values[index]; + return Padding( + padding: const EdgeInsetsDirectional.only(bottom: 8.0), + child: RadioButton( + checked: appTheme.displayMode == mode, + onChanged: (value) async { + if (value) appTheme.displayMode = mode; - await Global.prefs.setInt('NavigationMode', mode.index); - }, - content: Text( - mode.toString().replaceAll('PaneDisplayMode.', ''), + await Global.prefs.setInt('NavigationMode', mode.index); + }, + content: Text( + mode.toString().replaceAll('PaneDisplayMode.', ''), + ), ), - ), - ); - }), + ); + }), + ), ), - ), - spacer, + spacer, + ], SettingsCard( title: S.of(context).navigationIndicator, subtitle: S.of(context).setsTheStyleOfTheNavigationIndicator, diff --git a/lib/screens/stopped.dart b/lib/screens/stopped.dart index 2258029..13dd4d1 100644 --- a/lib/screens/stopped.dart +++ b/lib/screens/stopped.dart @@ -61,7 +61,9 @@ class _StoppedPageState extends State with PageMixin { var downloadListModel = Provider.of(context); var downloadList = downloadListModel.stoppedList; return Padding( - padding: const EdgeInsets.all(24.0), + padding: EdgeInsets.all((MediaQuery.sizeOf(context).width < 640.0) + ? 12.0 + : kPageDefaultVerticalPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/screens/waiting.dart b/lib/screens/waiting.dart index e2a7401..8b5b5bd 100644 --- a/lib/screens/waiting.dart +++ b/lib/screens/waiting.dart @@ -60,7 +60,9 @@ class _WaitingPageState extends State with PageMixin { assert(debugCheckHasFluentTheme(context)); var downloadList = Provider.of(context).waitingList; return Padding( - padding: const EdgeInsets.all(24.0), + padding: EdgeInsets.all((MediaQuery.sizeOf(context).width < 640.0) + ? 12.0 + : kPageDefaultVerticalPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/utils/aria2_manager.dart b/lib/utils/aria2_manager.dart index d87dcfd..7cd652b 100644 --- a/lib/utils/aria2_manager.dart +++ b/lib/utils/aria2_manager.dart @@ -7,14 +7,12 @@ import 'package:yolx/common/global.dart'; import 'package:yolx/utils/common_utils.dart'; import 'package:yolx/utils/file_utils.dart'; import 'package:yolx/utils/log.dart'; +import 'package:yolx/utils/native_channel_utils.dart'; import 'ariar2_http_utils.dart' as Aria2Http; class Aria2Manager { late Future cmdProcess; late int processPid = 0; - getAria2rootPath() async { - return await getPlugAssetsDir('aria2'); - } getAria2ExePath() async { if (Platform.isWindows || Platform.isLinux) { @@ -24,15 +22,21 @@ class Aria2Manager { ariaName = 'yolx_aria2c.exe'; } return '$dir/$ariaName'; + } else if (Platform.isAndroid) { + final libDir = await nativeLibraryDir(); + var libPath = '$libDir/libaria2c.so'; + File file = File(libPath); + if (!file.existsSync()) { + Log.e("aria2 not found:$libPath"); + } + return libPath; } } getAria2ConfPath() async { - if (Platform.isWindows || Platform.isLinux) { - String dir = await getPlugAssetsDir('aria2'); - String confName = 'yolx_aria2.conf'; - return '$dir${Global.pathSeparator}$confName'; - } + String dir = await getPlugAssetsDir('aria2'); + String confName = 'yolx_aria2.conf'; + return '$dir${Global.pathSeparator}$confName'; } getAria2Session() async { @@ -40,6 +44,12 @@ class Aria2Manager { return '${appDocumentsCacheDirectory.path}${Global.pathSeparator}download.session'; } + initAria2Conf() async { + String confPath = await getAria2ConfPath(); + List aria2ConfLines = await readDefAria2Conf(); + writeLinesToFile(confPath, aria2ConfLines.join("\n")); + } + void startServer() async { closeServer(); Global.rpcUrl = rpcURLValue.replaceAll('{port}', Global.rpcPort.toString()); @@ -110,7 +120,7 @@ class Aria2Manager { final processResult = Process.runSync('taskkill', ['/F', '/T', '/IM', 'yolx_aria2c.exe']); killSuccess = processResult.exitCode == 0; - } else if (Platform.isLinux) { + } else if (Platform.isLinux || Platform.isAndroid) { final processResult = Process.runSync('killall', ['yolx_aria2c']); killSuccess = processResult.exitCode == 0; } diff --git a/lib/utils/common_utils.dart b/lib/utils/common_utils.dart index afc6c36..358f14d 100644 --- a/lib/utils/common_utils.dart +++ b/lib/utils/common_utils.dart @@ -1,9 +1,29 @@ import 'dart:io'; import 'dart:math'; +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/foundation.dart'; import 'package:yolx/common/const.dart'; import 'package:yolx/model/download_item.dart'; +bool get isDesktop { + if (kIsWeb) return false; + return [ + TargetPlatform.windows, + TargetPlatform.linux, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); +} + +bool isTablet(MediaQueryData queryData) { + double devicePixelRatio = queryData.devicePixelRatio; + double screenWidth = queryData.size.shortestSide; + + // 判断设备是否为平板 + return (devicePixelRatio < 2.0 && screenWidth > 600) || + (devicePixelRatio >= 2.0 && screenWidth > 960); +} + permission777(filePath) { Process.runSync('chmod', ['-R', '777', filePath]); } diff --git a/lib/utils/file_utils.dart b/lib/utils/file_utils.dart index 5657134..bd14d97 100644 --- a/lib/utils/file_utils.dart +++ b/lib/utils/file_utils.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; import 'package:yolx/common/global.dart'; import 'package:yolx/generated/l10n.dart'; @@ -9,6 +10,26 @@ Future getLocalFile(String filename) async { return File('${directory.path}${Global.pathSeparator}$filename'); } +Future> readDefAria2Conf() async { + String text = await rootBundle.loadString("lib/resources/yolx_aria2.conf"); + return text.split('\n'); +} + +writeLinesToFile(String path, String text) { + File file = File(path); + if (!file.existsSync()) { + file.createSync(recursive: true); + } + file.writeAsStringSync(text, flush: true); +} + +createDir(String dir) { + Directory directory = Directory(dir); + if (!directory.existsSync()) { + directory.create(recursive: true); + } +} + getPlugAssetsDir(String plugName) async { if (Platform.isWindows || Platform.isLinux) { String plugDir = @@ -18,6 +39,11 @@ getPlugAssetsDir(String plugName) async { // String basename = path.basename(exePath); pathList[pathList.length - 1] = plugDir; return pathList.join(Global.pathSeparator); + } else if (Platform.isAndroid) { + Directory? cacheDir = await getExternalStorageDirectory(); + String plugDir = '${cacheDir?.path}${Global.pathSeparator}$plugName'; + createDir(plugDir); + return plugDir; } return null; } diff --git a/lib/utils/native_channel_utils.dart b/lib/utils/native_channel_utils.dart new file mode 100644 index 0000000..9912115 --- /dev/null +++ b/lib/utils/native_channel_utils.dart @@ -0,0 +1,12 @@ +import 'package:flutter/services.dart'; + +const MethodChannel _channel = + MethodChannel('com.yoyo.flutter_native_channel/native_methods'); + +Future nativeLibraryDir() async { + return await _channel.invokeMethod('nativeLibraryDir'); +} + +requestPermission() async { + return await _channel.invokeMethod('requestPermission'); +} diff --git a/lib/widgets/settings_card.dart b/lib/widgets/settings_card.dart index 3bb2dc7..86e851f 100644 --- a/lib/widgets/settings_card.dart +++ b/lib/widgets/settings_card.dart @@ -1,4 +1,5 @@ import 'package:fluent_ui/fluent_ui.dart'; +import 'package:yolx/utils/common_utils.dart'; class SettingsCard extends StatelessWidget { final String title; @@ -26,20 +27,40 @@ class SettingsCard extends StatelessWidget { child: Row( children: [ const SizedBox(width: 4), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + if (isDesktop || isTablet(MediaQuery.of(context))) ...[ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + Text(subtitle), + ], ), - Text(subtitle), - ], - ), - const Spacer(), - content, + ), + const Spacer(), + content, + ] else ...[ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + Text(subtitle), + content, + ], + ), + ), + ] ], ), ); diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 66400df..bd6a808 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -7,7 +7,7 @@ project(runner LANGUAGES CXX) set(BINARY_NAME "yolx") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.example.yolx") +set(APPLICATION_ID "com.yoyo.yolx") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/pubspec.yaml b/pubspec.yaml index 99179f4..d611f3d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ flutter: uses-material-design: true assets: - assets/ + - lib/resources/ flutter_native_splash: color: "#000000" diff --git a/windows/bin/plugin/aria2/yolx_aria2.conf b/windows/bin/plugin/aria2/yolx_aria2.conf deleted file mode 100644 index 70653a0..0000000 --- a/windows/bin/plugin/aria2/yolx_aria2.conf +++ /dev/null @@ -1,92 +0,0 @@ -############################### -# Yolx Windows Aria2 config file -# -# @see https://aria2.github.io/manual/en/html/aria2c.html -# -############################### - - -################ RPC ################ -# Enable JSON-RPC/XML-RPC server. -enable-rpc=true -# Add Access-Control-Allow-Origin header field with value * to the RPC response. -rpc-allow-origin-all=true -# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces. -rpc-listen-all=true - - -################ File system ################ -# Save a control file(*.aria2) every SEC seconds. -auto-save-interval=10 -# Enable disk cache. -disk-cache=64M -# Specify file allocation method. -file-allocation=falloc -# No file allocation is made for files whose size is smaller than SIZE -no-file-allocation-limit=64M -# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds. -save-session-interval=10 -# Force save, even if the task is completed, to save information to the session file -force-save=false - -################ Task ################ -# Exclude seed only downloads when counting concurrent active downloads -bt-detach-seed-only=true -# Verify the peer using certificates specified in --ca-certificate option. -check-certificate=false -# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times -# without getting a single byte, then force the download to fail. -max-file-not-found=10 -# Set number of tries. -max-tries=0 -# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response. -retry-wait=10 -# 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. -connect-timeout=10 -# Set timeout in seconds. -timeout=10 -# aria2 does not split less than 2*SIZE byte range. -min-split-size=1M -# Send Accept: deflate, gzip request header. -http-accept-gzip=true -# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file. -remote-time=true -# Set interval in seconds to output download progress summary. Setting 0 suppresses the output. -summary-interval=0 -# 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*. -content-disposition-default-utf8=true - - -################ BT Task ################ -# Enable Local Peer Discovery. -bt-enable-lpd=true -# Requires BitTorrent message payload encryption with arc4. -# bt-force-encryption=true -# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file. -bt-hash-check-seed=true -# Specify the maximum number of peers per torrent. -bt-max-peers=128 -# Try to download first and last pieces of each file first. This is useful for previewing files. -bt-prioritize-piece=head -# Removes the unselected files when download is completed in BitTorrent. -bt-remove-unselected-file=true -# Seed previously downloaded files without verifying piece hashes. -bt-seed-unverified=false -# 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. -bt-tracker-connect-timeout=10 -# Set timeout in seconds. -bt-tracker-timeout=10 -# Set host and port as an entry point to IPv4 DHT network. -dht-entry-point=dht.transmissionbt.com:6881 -# Set host and port as an entry point to IPv6 DHT network. -dht-entry-point6=dht.transmissionbt.com:6881 -# Enable IPv4 DHT functionality. It also enables UDP tracker support. -enable-dht=true -# Enable IPv6 DHT functionality. -enable-dht6=true -# Enable Peer Exchange extension. -enable-peer-exchange=true -# Specify the string used during the bitorrent extended handshake for the peer's client version. -peer-agent=Transmission/3.00 -# Specify the prefix of peer ID. -peer-id-prefix=-TR3000-