diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..d4083aa
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# 所有文本文件统一使用 LF
+* text=auto eol=lf
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4593f89
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,85 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+.frontend
+.frontend/
+
+# custom
+CLAUDE.md
+claude_project.md
+PROJECT_REQUIREMENTS.md
+DESIGN_OVERVIEW.md
+DESIGN.md
+refactory.md
+DESIGN-copy.md
+*.jsonl
+example.dart
+install.sh
+dist/
+
+# Local secrets/config that should not be published.
+.env
+.env.*
+!.env.example
+*.pem
+*.key
+*.crt
+*.cer
+*.der
+*.p12
+*.pfx
+*.keystore
+*.jks
+key.properties
+android/key.properties
+android/app/key.properties
+**/google-services.json
+**/GoogleService-Info.plist
+lib/firebase_options.dart
+*credential*.json
+*credentials*.json
+*secret*.json
+*secrets*.json
+*.local.json
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+/coverage/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/.metadata b/.metadata
new file mode 100644
index 0000000..6b44107
--- /dev/null
+++ b/.metadata
@@ -0,0 +1,39 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "db50e20168db8fee486b9abf32fc912de3bc5b6a"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
+ base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
+ - platform: android
+ create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
+ base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
+ - platform: linux
+ create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
+ base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
+ - platform: web
+ create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
+ base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
+ - platform: windows
+ create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
+ base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..0d29021
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..be3943c
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,14 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+.cxx/
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
new file mode 100644
index 0000000..7c15f2d
--- /dev/null
+++ b/android/app/build.gradle.kts
@@ -0,0 +1,106 @@
+plugins {
+ id("com.android.application")
+ id("kotlin-android")
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+import java.util.Properties
+import java.io.FileInputStream
+
+// 1. Create a Properties object
+val localProperties = Properties()
+val localPropertiesFile = rootProject.file("local.properties")
+
+// 2. Load the file if it exists
+if (localPropertiesFile.exists()) {
+ localProperties.load(FileInputStream(localPropertiesFile))
+}
+
+// 3. Helper function to read the value as an Int
+fun getLocalProperty(key: String, defaultValue: Int): Int {
+ val value = localProperties.getProperty(key)
+ return value?.toIntOrNull() ?: defaultValue
+}
+
+// 1. 加载 key.properties
+val keystoreProperties = Properties()
+val keystorePropertiesFile = rootProject.file("key.properties")
+if (keystorePropertiesFile.exists()) {
+ keystoreProperties.load(FileInputStream(keystorePropertiesFile))
+}
+
+android {
+ namespace = "com.limo.cloudreve4_flutter"
+ compileSdk = getLocalProperty("flutter.compileSdkVersion", 36)
+ ndkVersion = flutter.ndkVersion
+
+ // 2. 配置签名选项
+ signingConfigs {
+ create("release") {
+ keyAlias = keystoreProperties["keyAlias"] as String?
+ keyPassword = keystoreProperties["keyPassword"] as String?
+ storeFile = keystoreProperties["storeFile"]?.let { file(it) }
+ storePassword = keystoreProperties["storePassword"] as String?
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.limo.cloudreve4_flutter"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = getLocalProperty("flutter.minSdkVersion", 24)
+ targetSdk = getLocalProperty("flutter.targetSdkVersion", 35)
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ packaging {
+ resources {
+ // 如果遇到重复的 .so 文件,优先取第一个
+ pickFirst("lib/**/libmpv.so")
+ pickFirst("lib/**/libmediakitandroidhelper.so")
+ }
+ }
+
+ buildTypes {
+ release {
+ // 1. 将原来的 getByName("debug") 替换为 getByName("release")
+ signingConfig = signingConfigs.getByName("release")
+
+ // 2. 建议同时开启混淆,这能大幅减小网盘应用的体积并保护代码
+ isMinifyEnabled = true
+ isShrinkResources = true
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
+ }
+
+ applicationVariants.all {
+ val variant = this
+ outputs.all {
+ val output = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
+ val appName = "cloudreve4_flutter"
+ val versionName = variant.versionName
+ val versionCode = variant.versionCode
+ val type = variant.name // release 或 debug
+
+ val abi = output.getFilter(com.android.build.OutputFile.ABI) ?: "universal"
+
+ output.outputFileName = "${appName}_v${versionName}_${versionCode}_${abi}_release.apk"
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
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..4dfa863
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/kotlin/com/example/cloudreve4_flutter/MainActivity.kt b/android/app/src/main/kotlin/com/example/cloudreve4_flutter/MainActivity.kt
new file mode 100644
index 0000000..a282c06
--- /dev/null
+++ b/android/app/src/main/kotlin/com/example/cloudreve4_flutter/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.limo.cloudreve4_flutter
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
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-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
new file mode 100644
index 0000000..4fe3d15
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/launcher_icon.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-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
new file mode 100644
index 0000000..10bdbcf
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/launcher_icon.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-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
new file mode 100644
index 0000000..3075dc4
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.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-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
new file mode 100644
index 0000000..735734b
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.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/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
new file mode 100644
index 0000000..473776d
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.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/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.kts b/android/build.gradle.kts
new file mode 100644
index 0000000..dbee657
--- /dev/null
+++ b/android/build.gradle.kts
@@ -0,0 +1,24 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory =
+ rootProject.layout.buildDirectory
+ .dir("../../build")
+ .get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000..fbee1d8
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,2 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e4ef43f
--- /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-8.14-all.zip
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
new file mode 100644
index 0000000..ca7fe06
--- /dev/null
+++ b/android/settings.gradle.kts
@@ -0,0 +1,26 @@
+pluginManagement {
+ val flutterSdkPath =
+ run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+ id("com.android.application") version "8.11.1" apply false
+ id("org.jetbrains.kotlin.android") version "2.2.20" apply false
+}
+
+include(":app")
diff --git a/assets/fonts/NotoSansSC-Bold.ttf b/assets/fonts/NotoSansSC-Bold.ttf
new file mode 100644
index 0000000..17380a9
Binary files /dev/null and b/assets/fonts/NotoSansSC-Bold.ttf differ
diff --git a/assets/fonts/NotoSansSC-Medium.ttf b/assets/fonts/NotoSansSC-Medium.ttf
new file mode 100644
index 0000000..901afd4
Binary files /dev/null and b/assets/fonts/NotoSansSC-Medium.ttf differ
diff --git a/assets/fonts/SourceCodePro-Regular.ttf b/assets/fonts/SourceCodePro-Regular.ttf
new file mode 100644
index 0000000..fd02539
Binary files /dev/null and b/assets/fonts/SourceCodePro-Regular.ttf differ
diff --git a/assets/icons/tray_icon.ico b/assets/icons/tray_icon.ico
new file mode 100644
index 0000000..4185bb6
Binary files /dev/null and b/assets/icons/tray_icon.ico differ
diff --git a/assets/icons/tray_icon.png b/assets/icons/tray_icon.png
new file mode 100644
index 0000000..8d0b5e4
Binary files /dev/null and b/assets/icons/tray_icon.png differ
diff --git a/assets/images/app_logo.png b/assets/images/app_logo.png
new file mode 100644
index 0000000..c317978
Binary files /dev/null and b/assets/images/app_logo.png differ
diff --git a/devtools_options.yaml b/devtools_options.yaml
new file mode 100644
index 0000000..fa0b357
--- /dev/null
+++ b/devtools_options.yaml
@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:
diff --git a/flutter_gen.yaml b/flutter_gen.yaml
new file mode 100644
index 0000000..c2218db
--- /dev/null
+++ b/flutter_gen.yaml
@@ -0,0 +1,3 @@
+# 用于 easy_localization 生成翻译文件的配置
+
+output_dir: lib/core/i18n
diff --git a/lib/config/api_config.dart b/lib/config/api_config.dart
new file mode 100644
index 0000000..712cbf9
--- /dev/null
+++ b/lib/config/api_config.dart
@@ -0,0 +1,14 @@
+/// API配置
+class ApiConfig {
+ static const int connectTimeout = 5;
+ static const int receiveTimeout = 60;
+ static const int sendTimeout = 60;
+
+ /// API基础URL
+ static const String defaultBaseUrl = 'https://pan.gongyun.org/api/v4';
+
+ /// 获取API基础URL
+ static Future get baseUrl async {
+ return defaultBaseUrl;
+ }
+}
diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart
new file mode 100644
index 0000000..889a37e
--- /dev/null
+++ b/lib/config/app_config.dart
@@ -0,0 +1,26 @@
+/// 应用配置
+class AppConfig {
+ /// 应用名称
+ static const String appName = '公云存储';
+
+ /// 应用版本
+ static const String version = '1.0.0';
+
+ /// 构建号
+ static const int buildNumber = 1;
+
+ /// 是否为调试模式
+ static const bool debugMode = bool.fromEnvironment('dart.vm.product');
+
+ /// 默认分页大小
+ static const int defaultPageSize = 50;
+
+ /// 最大分页大小
+ static const int maxPageSize = 200;
+
+ /// 上传并发数
+ static const int uploadConcurrency = 3;
+
+ /// 下载并发数
+ static const int downloadConcurrency = 3;
+}
diff --git a/lib/core/constants/app_constants.dart b/lib/core/constants/app_constants.dart
new file mode 100644
index 0000000..4108dfc
--- /dev/null
+++ b/lib/core/constants/app_constants.dart
@@ -0,0 +1,37 @@
+/// 应用常量
+class AppConstants {
+ // 存储键
+ static const String keyAccessToken = 'access_token';
+ static const String keyRefreshToken = 'refresh_token';
+ static const String keyAccessExpires = 'access_expires';
+ static const String keyRefreshExpires = 'refresh_expires';
+ static const String keyRememberMe = 'remember_me';
+ static const String keyThemeMode = 'theme_mode';
+ static const String keyLanguage = 'language';
+
+ // 文件类型
+ static const int fileTypeFile = 0;
+ static const int fileTypeFolder = 1;
+
+ // 默认图标
+ static const String defaultAvatar = 'assets/images/default_avatar.png';
+
+ // 时间格式
+ static const String dateFormat = 'yyyy-MM-dd';
+ static const String dateTimeFormat = 'yyyy-MM-dd HH:mm:ss';
+ static const String timeFormat = 'HH:mm:ss';
+}
+
+/// API响应码
+class ApiCode {
+ static const int success = 0;
+ static const int continueCode = 203;
+ static const int credentialInvalid = 40020;
+ static const int incorrectPassword = 40069;
+ static const int lockConflict = 40073;
+ static const int staleVersion = 40076;
+ static const int credentialRequired = 401;
+ static const int permissionDenied = 403;
+ static const int notFound = 404;
+ static const int internalError = 500;
+}
diff --git a/lib/core/constants/quick_access_defaults.dart b/lib/core/constants/quick_access_defaults.dart
new file mode 100644
index 0000000..c486e1c
--- /dev/null
+++ b/lib/core/constants/quick_access_defaults.dart
@@ -0,0 +1,127 @@
+import 'dart:convert';
+import 'package:flutter/material.dart';
+import 'package:lucide_icons/lucide_icons.dart';
+
+class QuickAccessConfig {
+ final String id;
+ final String label;
+ final IconData icon;
+ final String path;
+ final Color color;
+ final bool isDefault;
+
+ const QuickAccessConfig({
+ required this.id,
+ required this.label,
+ required this.icon,
+ required this.path,
+ required this.color,
+ this.isDefault = false,
+ });
+
+ QuickAccessConfig copyWith({String? label, String? path, IconData? icon, Color? color}) =>
+ QuickAccessConfig(
+ id: id,
+ label: label ?? this.label,
+ icon: icon ?? this.icon,
+ path: path ?? this.path,
+ color: color ?? this.color,
+ isDefault: isDefault,
+ );
+
+ static const storageKey = 'quick_access_shortcuts_v2';
+
+ static const defaults = [
+ QuickAccessConfig(id: 'img', label: '图片', icon: LucideIcons.image, path: '/Images', color: Color(0xFFF0ABFC), isDefault: true),
+ QuickAccessConfig(id: 'vid', label: '视频', icon: LucideIcons.video, path: '/Videos', color: Color(0xFFFCD34D), isDefault: true),
+ QuickAccessConfig(id: 'doc', label: '文档', icon: LucideIcons.fileText, path: '/Documents', color: Color(0xFF93C5FD), isDefault: true),
+ QuickAccessConfig(id: 'mus', label: '音乐', icon: LucideIcons.music, path: '/Music', color: Color(0xFF86EFAC), isDefault: true),
+ ];
+
+ static const iconPool = [
+ LucideIcons.image,
+ LucideIcons.video,
+ LucideIcons.fileText,
+ LucideIcons.music,
+ LucideIcons.folder,
+ LucideIcons.download,
+ LucideIcons.archive,
+ LucideIcons.code,
+ LucideIcons.bookOpen,
+ LucideIcons.camera,
+ LucideIcons.film,
+ LucideIcons.headphones,
+ ];
+
+ static const colorPool = [
+ Color(0xFFF0ABFC),
+ Color(0xFFFCD34D),
+ Color(0xFF93C5FD),
+ Color(0xFF86EFAC),
+ Color(0xFFFCA5A5),
+ Color(0xFFFDBA74),
+ Color(0xFFC4B5FD),
+ Color(0xFF67E8F9),
+ ];
+
+ Map toJson() => {
+ 'id': id,
+ 'label': label,
+ 'iconCode': icon.codePoint,
+ 'path': path,
+ 'color': color.toARGB32(),
+ 'isDefault': isDefault,
+ };
+
+ factory QuickAccessConfig.fromJson(Map json) {
+ final iconCode = json['iconCode'] as int? ?? LucideIcons.folder.codePoint;
+ final matchedIcon = iconPool.cast().firstWhere(
+ (i) => i!.codePoint == iconCode,
+ orElse: () => LucideIcons.folder,
+ )!;
+ return QuickAccessConfig(
+ id: json['id'] as String? ?? DateTime.now().millisecondsSinceEpoch.toString(),
+ label: json['label'] as String,
+ icon: matchedIcon,
+ path: json['path'] as String,
+ color: Color(json['color'] as int? ?? 0xFF93C5FD),
+ isDefault: json['isDefault'] as bool? ?? false,
+ );
+ }
+
+ static List parseSaved(String saved) {
+ try {
+ final list = jsonDecode(saved) as List;
+ return list.map((e) => QuickAccessConfig.fromJson(e as Map)).toList();
+ } catch (_) {
+ return List.from(defaults);
+ }
+ }
+
+ static String serialize(List items) {
+ return jsonEncode(items.map((e) => e.toJson()).toList());
+ }
+
+ /// 迁移旧格式(分号分隔的路径列表)
+ static List migrateV1(String saved) {
+ final parts = saved.split(';');
+ final items = [];
+ for (int i = 0; i < defaults.length; i++) {
+ if (i < parts.length && parts[i].isNotEmpty) {
+ items.add(defaults[i].copyWith(path: parts[i]));
+ } else {
+ items.add(defaults[i]);
+ }
+ }
+ return items;
+ }
+}
+
+extension ColorDarken on Color {
+ Color darken(double amount) {
+ final hsl = HSLColor.fromColor(this);
+ return hsl
+ .withLightness((hsl.lightness - amount).clamp(0.0, 1.0))
+ .toColor();
+ }
+}
diff --git a/lib/core/constants/storage_keys.dart b/lib/core/constants/storage_keys.dart
new file mode 100644
index 0000000..8918ed3
--- /dev/null
+++ b/lib/core/constants/storage_keys.dart
@@ -0,0 +1,30 @@
+/// 存储键常量
+class StorageKeys {
+ // 设置相关
+ static const String themeMode = 'theme_mode';
+ static const String customBaseUrl = 'custom_base_url';
+ static const String servers = 'flutter_servers';
+ static const String lastSelectedServer = 'last_selected_server_label';
+
+ // 上传相关
+ static const String uploadQueue = 'upload_queue';
+ static const String uploadTasks = 'upload_tasks';
+
+ // 下载相关
+ static const String downloadTasks = 'download_tasks';
+ static const String downloadWifiOnly = 'download_wifi_only';
+ static const String downloadRetries = 'download_retries';
+
+ // 任务记录
+ static const String taskRetentionDays = 'task_retention_days';
+
+ // 缓存相关
+ static const String cacheSettings = 'cache_settings';
+
+ // Gravatar 镜像
+ static const String gravatarMirrorEnabled = 'gravatar_mirror_enabled';
+ static const String gravatarMirrorUrl = 'gravatar_mirror_url';
+
+ // 搜索历史
+ static const String searchHistory = 'search_history';
+}
diff --git a/lib/core/exceptions/app_exception.dart b/lib/core/exceptions/app_exception.dart
new file mode 100644
index 0000000..1149a7f
--- /dev/null
+++ b/lib/core/exceptions/app_exception.dart
@@ -0,0 +1,61 @@
+/// 应用异常基类
+class AppException implements Exception {
+ final String message;
+ final int? code;
+ final dynamic data;
+
+ AppException(this.message, {this.code, this.data});
+
+ @override
+ String toString() => 'AppException: $message (code: $code)';
+}
+
+/// 网络异常
+class NetworkException extends AppException {
+ NetworkException(super.message, {super.code, super.data});
+}
+
+/// 认证异常
+class AuthException extends AppException {
+ AuthException(super.message, {super.code, super.data});
+}
+
+/// 服务器异常
+class ServerException extends AppException {
+ ServerException(super.message, {super.code, super.data});
+}
+
+/// Token过期异常
+class TokenExpiredException extends AuthException {
+ TokenExpiredException()
+ : super('Token已过期,请重新登录', code: 401);
+}
+
+/// RefreshToken过期异常
+class RefreshTokenExpiredException extends AuthException {
+ RefreshTokenExpiredException()
+ : super('登录已过期,请重新登录', code: 40020);
+}
+
+/// 未授权异常
+class UnauthorizedException extends AuthException {
+ UnauthorizedException() : super('未授权,请先登录', code: 403);
+}
+
+/// 文件不存在异常
+class FileNotFoundException extends AppException {
+ FileNotFoundException() : super('文件不存在', code: 404);
+}
+
+/// 存储空间不足异常
+class StorageSpaceException extends AppException {
+ StorageSpaceException() : super('存储空间不足', code: 507);
+}
+
+/// 两步验证需要异常
+class TwoFactorRequiredException extends AuthException {
+ final String sessionId;
+
+ TwoFactorRequiredException(this.sessionId)
+ : super('需要两步验证', code: 203);
+}
diff --git a/lib/core/utils/app_logger.dart b/lib/core/utils/app_logger.dart
new file mode 100644
index 0000000..0c06827
--- /dev/null
+++ b/lib/core/utils/app_logger.dart
@@ -0,0 +1,161 @@
+import 'dart:io';
+
+import 'package:logger/logger.dart';
+import 'package:path/path.dart' as p;
+import 'package:path_provider/path_provider.dart';
+
+/// 应用日志类
+class AppLogger {
+ AppLogger._();
+ static Logger? _logger;
+ static File? _logFile;
+
+ /// 初始化日志,必须在 main 中 await
+ static Future init() async {
+ if (_logger != null) return;
+
+ // 1. 获取日志存储路径 (Windows: $HOME/AppData/Roaming/com.limo/cloudreve4_flutter/logs)
+ final appDir = await getApplicationSupportDirectory();
+ final logDir = Directory(p.join(appDir.path, 'logs'));
+ if (!await logDir.exists()) {
+ await logDir.create(recursive: true);
+ }
+ _logFile = File(p.join(logDir.path, 'log.txt'));
+
+ // 2. 配置多路输出:同时输出到控制台和文件
+ _logger = Logger(
+ printer: PrettyPrinter(
+ methodCount: 0,
+ errorMethodCount: 5,
+ lineLength: 80,
+ colors: true,
+ printEmojis: true,
+ dateTimeFormat: DateTimeFormat.dateAndTime,
+ ),
+ output: MultiOutput([
+ ConsoleOutput(),
+ CustomFileOutput(
+ file: _logFile!,
+ ),
+ ]),
+ filter: ProductionFilter(),
+ );
+ }
+
+ // 使用 getter 确保 logger 已初始化,防止空指针
+ static Logger get _instance {
+ _logger ??= Logger(
+ printer: PrettyPrinter(
+ methodCount: 0,
+ colors: true,
+ printEmojis: true,
+ dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,
+ ),
+ );
+ return _logger!;
+ }
+
+ /// Debug 级别日志
+ static void d(String message) => _instance.d(message);
+
+ /// Info 级别日志
+ static void i(String message) => _instance.i(message);
+
+ /// Warning 级别日志
+ static void w(String message) => _instance.w(message);
+
+ /// Error 级别日志
+ static void e(String message) => _instance.e(message);
+
+ /// Debug 级别日志(支持格式化)
+ static void df(String message, List