diff --git a/README.md b/README.md
index 191f589..c406aa8 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,114 @@
# web_android_shell
-H5 壳子项目。
+Android 平板专用 H5 壳应用 — Monorepo 多品牌架构。
+
+## 项目结构
+
+```
+web_android_shell/
+├── apps/ # 品牌应用(每个品牌一个 Flutter App)
+│ └── quanxue/ # 全学通
+├── packages/
+│ ├── web_shell_core/ # 核心库(WebView 引擎 + Bridge + 服务)
+│ └── web_android_shell/ # 旧版入口(已迁移至 apps/quanxue)
+├── flavors/ # 品牌配置 + 品牌资源
+│ ├── quanxue.yaml # 品牌配置
+│ └── quanxue/ # 品牌资源(图标、启动页)
+│ ├── icon.png
+│ ├── icon_foreground.png
+│ └── splash.png
+├── tool/
+│ ├── generate_app.dart # 一键生成新品牌应用
+│ └── flutter_run_fresh.ps1 # Windows 调试脚本(自动杀旧进程)
+└── doc/ # 项目文档
+```
+
+## 快速开始
+
+### 1. 运行已有品牌
+
+```bash
+cd apps/quanxue
+flutter run
+```
+
+### 2. 生成新品牌
+
+```bash
+# 1) 在 flavors/ 下创建品牌配置
+cp flavors/quanxue.yaml flavors/新品牌.yaml
+# 2) 修改配置中的 app_name, application_id, app_key, theme, branding
+# 3) 准备品牌资源(参见下方规格要求)
+mkdir flavors/新品牌
+cp 你的图标.png flavors/新品牌/icon.png
+cp 你的前景图.png flavors/新品牌/icon_foreground.png
+cp 你的启动图.png flavors/新品牌/splash.png
+# 4) 运行生成脚本
+dart run tool/generate_app.dart 新品牌
+```
+
+生成脚本会自动完成:
+- 创建 Flutter 应用 → `apps/新品牌/`
+- 添加 `web_shell_core` 依赖
+- 覆写 `MainActivity` 继承 `CoreShellActivity`
+- 更新 `AndroidManifest.xml` 应用名
+- 生成品牌入口 `main.dart`
+- 复制品牌资源到生成目录
+- 添加 `flutter_launcher_icons` / `flutter_native_splash` 依赖
+- 自动生成应用图标和启动页
+
+### 3. 品牌配置格式
+
+```yaml
+app_name: "全学通" # 应用名
+application_id: "com.wanmake.quanxue" # 包名
+app_key: "quanxue_prod" # 业务标识
+theme:
+ accent_color: "0xFF3ED37B" # 主题色
+ bg_color: "0xFFFFFFFF" # 背景色
+ text_color: "0xFF1F2937" # 主文字色
+ muted_text_color: "0xFF6B7280" # 次要文字色
+branding:
+ icon: "icon.png" # 应用图标(相对于 flavors/<品牌>/)
+ icon_background: "#FFFFFF" # 自适应图标背景色
+ icon_foreground: "icon_foreground.png" # 自适应图标前景
+ splash: "splash.png" # 启动页图片
+ splash_color: "#FFFFFF" # 启动页背景色
+```
+
+### 4. 品牌资源规格
+
+| 资源 | 文件名 | 最小尺寸 | 格式 | 说明 |
+|---|---|---|---|---|
+| 应用图标 | `icon.png` | 1024×1024 | PNG | 正方形,用于生成各尺寸 launcher icon |
+| 自适应图标前景 | `icon_foreground.png` | 1024×1024 | PNG (透明背景) | 主体内容需居中,四周留 **25% 安全区** |
+| 启动页图片 | `splash.png` | 1152×1152 | PNG | 用于生成 Android 12+ 和旧版启动页 |
+
+> **提示:** 资源文件放置在 `flavors/<品牌名>/` 目录下,`branding` 中的路径相对于此目录。如果省略 `branding` 段,脚本会跳过图标和启动页生成。
## 调试说明
-这个项目在部分设备上,如果直接用 `Ctrl+C` 结束 `flutter run`,设备里的上一次 App 进程可能还留在后台,下一次运行时会影响内嵌 WebView 启动。
+部分教育平板设备使用 `Ctrl+C` 结束 `flutter run` 后,旧进程可能留在后台影响 WebView 启动。
-更稳的做法:
-
-- 调试结束时优先在 `flutter run` 里按 `q`
-- 或者使用项目自带脚本,先自动杀掉旧进程再启动
+**推荐做法:**
+- 调试结束时在 `flutter run` 控制台按 `q` 退出
+- 或使用调试脚本自动杀旧进程再启动:
```powershell
-.\tool\flutter_run_fresh.ps1
+.\tool\flutter_run_fresh.ps1 # 自动选设备
+.\tool\flutter_run_fresh.ps1 -d F136A # 指定设备
```
-如果要指定设备,也可以继续透传给 `flutter run`:
+## 技术栈
-```powershell
-.\tool\flutter_run_fresh.ps1 -d F136A
-```
+| 组件 | 技术 |
+|---|---|
+| 框架 | Flutter 3.x (Dart 3.11+) |
+| WebView | `webview_flutter` + `webview_flutter_android` |
+| 宿主能力 | `image_picker` · `file_picker` · `permission_handler` · `url_launcher` |
+| 原生层 | Kotlin Plugin + Java `CoreShellActivity` |
+| 代码规范 | `very_good_analysis` |
-脚本会自动:
+## 平台约束
-- 读取 `android/local.properties` 中的 `sdk.dir`
-- 调用 `adb shell am force-stop com.yuanxuan.webshell.web_android_shell`
-- 然后执行 `flutter run`
+**仅支持 Android 平板。** iOS / Web / Desktop 平台已移除。
diff --git a/analysis_options.yaml b/analysis_options.yaml
index d4e0f0c..f13d6ae 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,28 +1,9 @@
-# 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
+# Dart 分析器配置,用于在开发阶段发现错误、警告和代码规范问题。
+# 可通过 `flutter analyze` 执行静态检查。
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # 如需自定义规则,可在此处开启或关闭指定 lint。
+ rules:
+ # avoid_print: false # 取消注释后可关闭 `avoid_print` 规则。
+ # prefer_single_quotes: true # 取消注释后可开启 `prefer_single_quotes` 规则。
diff --git a/apps/quanxue/.gitignore b/apps/quanxue/.gitignore
new file mode 100644
index 0000000..3820a95
--- /dev/null
+++ b/apps/quanxue/.gitignore
@@ -0,0 +1,45 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# 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/apps/quanxue/.metadata b/apps/quanxue/.metadata
new file mode 100644
index 0000000..05a325e
--- /dev/null
+++ b/apps/quanxue/.metadata
@@ -0,0 +1,30 @@
+# 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: "90673a4eef275d1a6692c26ac80d6d746d41a73a"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
+ base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
+ - platform: android
+ create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
+ base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
+
+ # 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/apps/quanxue/README.md b/apps/quanxue/README.md
new file mode 100644
index 0000000..213693b
--- /dev/null
+++ b/apps/quanxue/README.md
@@ -0,0 +1,17 @@
+# quanxue
+
+A new Flutter project.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
+- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev/), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/apps/quanxue/analysis_options.yaml b/apps/quanxue/analysis_options.yaml
new file mode 100644
index 0000000..0d29021
--- /dev/null
+++ b/apps/quanxue/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/apps/quanxue/android/.gitignore b/apps/quanxue/android/.gitignore
new file mode 100644
index 0000000..be3943c
--- /dev/null
+++ b/apps/quanxue/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/apps/quanxue/android/app/build.gradle.kts
similarity index 82%
rename from android/app/build.gradle.kts
rename to apps/quanxue/android/app/build.gradle.kts
index 7868f7c..5badcf2 100644
--- a/android/app/build.gradle.kts
+++ b/apps/quanxue/android/app/build.gradle.kts
@@ -1,45 +1,44 @@
-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")
-}
-
-android {
- namespace = "com.yuanxuan.webshell.web_android_shell"
- compileSdk = flutter.compileSdkVersion
- ndkVersion = flutter.ndkVersion
-
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- }
-
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_17.toString()
- }
-
+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")
+}
+
+android {
+ namespace = "com.wanmake.quanxue"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ 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.yuanxuan.webshell.web_android_shell"
+ applicationId = "com.wanmake.quanxue"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
- // Keep a stable targetSdk for better compatibility with older system WebView builds.
- targetSdk = 34
+ targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
-
- buildTypes {
- 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.getByName("debug")
- }
- }
-}
-
-flutter {
- source = "../.."
-}
+
+ buildTypes {
+ 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.getByName("debug")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/apps/quanxue/android/app/src/debug/AndroidManifest.xml b/apps/quanxue/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/apps/quanxue/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/apps/quanxue/android/app/src/main/AndroidManifest.xml b/apps/quanxue/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a0b8770
--- /dev/null
+++ b/apps/quanxue/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/quanxue/android/app/src/main/java/com/wanmake/quanxue/MainActivity.java b/apps/quanxue/android/app/src/main/java/com/wanmake/quanxue/MainActivity.java
new file mode 100644
index 0000000..b46b12b
--- /dev/null
+++ b/apps/quanxue/android/app/src/main/java/com/wanmake/quanxue/MainActivity.java
@@ -0,0 +1,6 @@
+package com.wanmake.quanxue;
+
+import com.yuanxuan.webshell.core.web_shell_core.CoreShellActivity;
+
+public class MainActivity extends CoreShellActivity {
+}
diff --git a/apps/quanxue/android/app/src/main/res/drawable-hdpi/android12splash.png b/apps/quanxue/android/app/src/main/res/drawable-hdpi/android12splash.png
new file mode 100644
index 0000000..093727a
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-hdpi/android12splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/apps/quanxue/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9054ac5
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-hdpi/splash.png b/apps/quanxue/android/app/src/main/res/drawable-hdpi/splash.png
new file mode 100644
index 0000000..093727a
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-hdpi/splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-mdpi/android12splash.png b/apps/quanxue/android/app/src/main/res/drawable-mdpi/android12splash.png
new file mode 100644
index 0000000..21677fe
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-mdpi/android12splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/apps/quanxue/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..78c4b1a
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-mdpi/splash.png b/apps/quanxue/android/app/src/main/res/drawable-mdpi/splash.png
new file mode 100644
index 0000000..21677fe
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-mdpi/splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-night-hdpi/android12splash.png b/apps/quanxue/android/app/src/main/res/drawable-night-hdpi/android12splash.png
new file mode 100644
index 0000000..093727a
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-night-hdpi/android12splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-night-mdpi/android12splash.png b/apps/quanxue/android/app/src/main/res/drawable-night-mdpi/android12splash.png
new file mode 100644
index 0000000..21677fe
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-night-mdpi/android12splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-night-xhdpi/android12splash.png b/apps/quanxue/android/app/src/main/res/drawable-night-xhdpi/android12splash.png
new file mode 100644
index 0000000..f86ccfb
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-night-xhdpi/android12splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png b/apps/quanxue/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png
new file mode 100644
index 0000000..ec5e150
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png b/apps/quanxue/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png
new file mode 100644
index 0000000..fb0ffb9
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-v21/background.png b/apps/quanxue/android/app/src/main/res/drawable-v21/background.png
new file mode 100644
index 0000000..8e21404
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-v21/background.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-v21/launch_background.xml b/apps/quanxue/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..3cc4948
--- /dev/null
+++ b/apps/quanxue/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,9 @@
+
+
+ -
+
+
+ -
+
+
+
diff --git a/apps/quanxue/android/app/src/main/res/drawable-xhdpi/android12splash.png b/apps/quanxue/android/app/src/main/res/drawable-xhdpi/android12splash.png
new file mode 100644
index 0000000..f86ccfb
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-xhdpi/android12splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/apps/quanxue/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..0aafa46
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-xhdpi/splash.png b/apps/quanxue/android/app/src/main/res/drawable-xhdpi/splash.png
new file mode 100644
index 0000000..f86ccfb
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-xhdpi/splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-xxhdpi/android12splash.png b/apps/quanxue/android/app/src/main/res/drawable-xxhdpi/android12splash.png
new file mode 100644
index 0000000..ec5e150
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-xxhdpi/android12splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/apps/quanxue/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..fab1938
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-xxhdpi/splash.png b/apps/quanxue/android/app/src/main/res/drawable-xxhdpi/splash.png
new file mode 100644
index 0000000..ec5e150
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-xxhdpi/splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-xxxhdpi/android12splash.png b/apps/quanxue/android/app/src/main/res/drawable-xxxhdpi/android12splash.png
new file mode 100644
index 0000000..fb0ffb9
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-xxxhdpi/android12splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/apps/quanxue/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9f56d26
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable-xxxhdpi/splash.png b/apps/quanxue/android/app/src/main/res/drawable-xxxhdpi/splash.png
new file mode 100644
index 0000000..fb0ffb9
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable-xxxhdpi/splash.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable/background.png b/apps/quanxue/android/app/src/main/res/drawable/background.png
new file mode 100644
index 0000000..8e21404
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/drawable/background.png differ
diff --git a/apps/quanxue/android/app/src/main/res/drawable/launch_background.xml b/apps/quanxue/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..3cc4948
--- /dev/null
+++ b/apps/quanxue/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,9 @@
+
+
+ -
+
+
+ -
+
+
+
diff --git a/apps/quanxue/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/quanxue/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..c79c58a
--- /dev/null
+++ b/apps/quanxue/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/apps/quanxue/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/quanxue/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..9960f24
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/apps/quanxue/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/quanxue/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..398bcb9
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/apps/quanxue/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/quanxue/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..3818b9a
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/apps/quanxue/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/quanxue/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..e7e712e
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/apps/quanxue/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/quanxue/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..eb17a16
Binary files /dev/null and b/apps/quanxue/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/apps/quanxue/android/app/src/main/res/values-night-v31/styles.xml b/apps/quanxue/android/app/src/main/res/values-night-v31/styles.xml
new file mode 100644
index 0000000..1b8e45f
--- /dev/null
+++ b/apps/quanxue/android/app/src/main/res/values-night-v31/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/apps/quanxue/android/app/src/main/res/values-night/styles.xml b/apps/quanxue/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..dbc9ea9
--- /dev/null
+++ b/apps/quanxue/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/apps/quanxue/android/app/src/main/res/values-v31/styles.xml b/apps/quanxue/android/app/src/main/res/values-v31/styles.xml
new file mode 100644
index 0000000..8012d02
--- /dev/null
+++ b/apps/quanxue/android/app/src/main/res/values-v31/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/apps/quanxue/android/app/src/main/res/values/colors.xml b/apps/quanxue/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c5d5899
--- /dev/null
+++ b/apps/quanxue/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/apps/quanxue/android/app/src/main/res/values/styles.xml b/apps/quanxue/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..0d1fa8f
--- /dev/null
+++ b/apps/quanxue/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/apps/quanxue/android/app/src/profile/AndroidManifest.xml b/apps/quanxue/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/apps/quanxue/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/apps/quanxue/android/build.gradle.kts b/apps/quanxue/android/build.gradle.kts
new file mode 100644
index 0000000..dbee657
--- /dev/null
+++ b/apps/quanxue/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/apps/quanxue/android/gradle.properties b/apps/quanxue/android/gradle.properties
new file mode 100644
index 0000000..fbee1d8
--- /dev/null
+++ b/apps/quanxue/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/apps/quanxue/android/gradle/wrapper/gradle-wrapper.properties b/apps/quanxue/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e4ef43f
--- /dev/null
+++ b/apps/quanxue/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/apps/quanxue/android/settings.gradle.kts b/apps/quanxue/android/settings.gradle.kts
new file mode 100644
index 0000000..ca7fe06
--- /dev/null
+++ b/apps/quanxue/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/apps/quanxue/assets/branding/icon.png b/apps/quanxue/assets/branding/icon.png
new file mode 100644
index 0000000..f19711a
Binary files /dev/null and b/apps/quanxue/assets/branding/icon.png differ
diff --git a/apps/quanxue/assets/branding/icon_foreground.png b/apps/quanxue/assets/branding/icon_foreground.png
new file mode 100644
index 0000000..977ad82
Binary files /dev/null and b/apps/quanxue/assets/branding/icon_foreground.png differ
diff --git a/apps/quanxue/assets/branding/splash.png b/apps/quanxue/assets/branding/splash.png
new file mode 100644
index 0000000..1a95930
Binary files /dev/null and b/apps/quanxue/assets/branding/splash.png differ
diff --git a/apps/quanxue/flutter_launcher_icons.yaml b/apps/quanxue/flutter_launcher_icons.yaml
new file mode 100644
index 0000000..61717f8
--- /dev/null
+++ b/apps/quanxue/flutter_launcher_icons.yaml
@@ -0,0 +1,5 @@
+flutter_launcher_icons:
+ android: true
+ image_path: "assets/branding/icon.png"
+ adaptive_icon_background: "#FFFFFF"
+ adaptive_icon_foreground: "assets/branding/icon_foreground.png"
diff --git a/apps/quanxue/flutter_native_splash.yaml b/apps/quanxue/flutter_native_splash.yaml
new file mode 100644
index 0000000..bd05f40
--- /dev/null
+++ b/apps/quanxue/flutter_native_splash.yaml
@@ -0,0 +1,6 @@
+flutter_native_splash:
+ color: "#FFFFFF"
+ image: "assets/branding/splash.png"
+ android_12:
+ image: "assets/branding/splash.png"
+ icon_background_color: "#FFFFFF"
diff --git a/apps/quanxue/lib/main.dart b/apps/quanxue/lib/main.dart
new file mode 100644
index 0000000..7746930
--- /dev/null
+++ b/apps/quanxue/lib/main.dart
@@ -0,0 +1,15 @@
+import 'package:flutter/material.dart';
+import 'package:web_shell_core/web_shell_core.dart';
+
+void main() {
+ runShellApp(
+ ShellEnvironment(
+ appName: '全学通',
+ appKey: 'quanxue_prod',
+ accentColor: const Color(0xFF3ED37B),
+ backgroundColor: const Color(0xFFFFFFFF),
+ textColor: const Color(0xFF1F2937),
+ mutedTextColor: const Color(0xFF6B7280),
+ ),
+ );
+}
diff --git a/apps/quanxue/pubspec.lock b/apps/quanxue/pubspec.lock
new file mode 100644
index 0000000..b771d72
--- /dev/null
+++ b/apps/quanxue/pubspec.lock
@@ -0,0 +1,689 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ ansicolor:
+ dependency: transitive
+ description:
+ name: ansicolor
+ sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.0.3"
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.0.9"
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.7.0"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.13.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.2"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.1"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.0.4"
+ cli_util:
+ dependency: transitive
+ description:
+ name: cli_util
+ sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.4.2"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.1.2"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.19.1"
+ cross_file:
+ dependency: transitive
+ description:
+ name: cross_file
+ sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.3.5+2"
+ csslib:
+ dependency: transitive
+ description:
+ name: csslib
+ sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.0.2"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.0.8"
+ dbus:
+ dependency: transitive
+ description:
+ name: dbus
+ sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.7.12"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.3.3"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.2.0"
+ file_picker:
+ dependency: transitive
+ description:
+ name: file_picker
+ sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "10.3.10"
+ file_selector_linux:
+ dependency: transitive
+ description:
+ name: file_selector_linux
+ sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.9.4"
+ file_selector_macos:
+ dependency: transitive
+ description:
+ name: file_selector_macos
+ sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.9.5"
+ file_selector_platform_interface:
+ dependency: transitive
+ description:
+ name: file_selector_platform_interface
+ sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.7.0"
+ file_selector_windows:
+ dependency: transitive
+ description:
+ name: file_selector_windows
+ sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.9.3+5"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_launcher_icons:
+ dependency: "direct dev"
+ description:
+ name: flutter_launcher_icons
+ sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.14.4"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.0.0"
+ flutter_native_splash:
+ dependency: "direct dev"
+ description:
+ name: flutter_native_splash
+ sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.7"
+ flutter_plugin_android_lifecycle:
+ dependency: transitive
+ description:
+ name: flutter_plugin_android_lifecycle
+ sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.0.33"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ html:
+ dependency: transitive
+ description:
+ name: html
+ sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.15.6"
+ http:
+ dependency: transitive
+ description:
+ name: http
+ sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.6.0"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.1.2"
+ image:
+ dependency: transitive
+ description:
+ name: image
+ sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.8.0"
+ image_picker:
+ dependency: transitive
+ description:
+ name: image_picker
+ sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.2.1"
+ image_picker_android:
+ dependency: transitive
+ description:
+ name: image_picker_android
+ sha256: eda9b91b7e266d9041084a42d605a74937d996b87083395c5e47835916a86156
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.8.13+14"
+ image_picker_for_web:
+ dependency: transitive
+ description:
+ name: image_picker_for_web
+ sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.1.1"
+ image_picker_ios:
+ dependency: transitive
+ description:
+ name: image_picker_ios
+ sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.8.13+6"
+ image_picker_linux:
+ dependency: transitive
+ description:
+ name: image_picker_linux
+ sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.2"
+ image_picker_macos:
+ dependency: transitive
+ description:
+ name: image_picker_macos
+ sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.2+1"
+ image_picker_platform_interface:
+ dependency: transitive
+ description:
+ name: image_picker_platform_interface
+ sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.11.1"
+ image_picker_windows:
+ dependency: transitive
+ description:
+ name: image_picker_windows
+ sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.2"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.11.0"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "11.0.2"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.10"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.2"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.1.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.12.18"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.13.0"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.17.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.0.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.9.1"
+ permission_handler:
+ dependency: transitive
+ description:
+ name: permission_handler
+ sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "12.0.1"
+ permission_handler_android:
+ dependency: transitive
+ description:
+ name: permission_handler_android
+ sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "13.0.1"
+ permission_handler_apple:
+ dependency: transitive
+ description:
+ name: permission_handler_apple
+ sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "9.4.7"
+ permission_handler_html:
+ dependency: transitive
+ description:
+ name: permission_handler_html
+ sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.1.3+5"
+ permission_handler_platform_interface:
+ dependency: transitive
+ description:
+ name: permission_handler_platform_interface
+ sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.3.0"
+ permission_handler_windows:
+ dependency: transitive
+ description:
+ name: permission_handler_windows
+ sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.1"
+ petitparser:
+ dependency: transitive
+ description:
+ name: petitparser
+ sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "7.0.2"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.8"
+ posix:
+ dependency: transitive
+ description:
+ name: posix
+ sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.5.0"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.10.2"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.12.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.4"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.1"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.2.2"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.7.9"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.0"
+ universal_io:
+ dependency: transitive
+ description:
+ name: universal_io
+ sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.3.1"
+ url_launcher:
+ dependency: transitive
+ description:
+ name: url_launcher
+ sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.3.2"
+ url_launcher_android:
+ dependency: transitive
+ description:
+ name: url_launcher_android
+ sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.3.28"
+ url_launcher_ios:
+ dependency: transitive
+ description:
+ name: url_launcher_ios
+ sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.4.1"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.2.2"
+ url_launcher_macos:
+ dependency: transitive
+ description:
+ name: url_launcher_macos
+ sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.2.5"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.3.2"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.2"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.1.5"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.2.0"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "15.0.2"
+ web:
+ dependency: transitive
+ description:
+ name: web
+ sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.1.1"
+ web_shell_core:
+ dependency: "direct main"
+ description:
+ path: "../../packages/web_shell_core"
+ relative: true
+ source: path
+ version: "0.0.1"
+ webview_flutter:
+ dependency: transitive
+ description:
+ name: webview_flutter
+ sha256: a3da219916aba44947d3a5478b1927876a09781174b5a2b67fa5be0555154bf9
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.13.1"
+ webview_flutter_android:
+ dependency: transitive
+ description:
+ name: webview_flutter_android
+ sha256: "2a03df01df2fd30b075d1e7f24c28aee593f2e5d5ac4c3c4283c5eda63717b24"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.10.13"
+ webview_flutter_platform_interface:
+ dependency: transitive
+ description:
+ name: webview_flutter_platform_interface
+ sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.14.0"
+ webview_flutter_wkwebview:
+ dependency: transitive
+ description:
+ name: webview_flutter_wkwebview
+ sha256: "0d85e8bc5db9a7c49f6ff57cbeafc6cd8216ad9c9ebc70b2c4579d955698933a"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.24.1"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.15.0"
+ xml:
+ dependency: transitive
+ description:
+ name: xml
+ sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.6.1"
+ yaml:
+ dependency: transitive
+ description:
+ name: yaml
+ sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.1.3"
+sdks:
+ dart: ">=3.11.0 <4.0.0"
+ flutter: ">=3.38.0"
diff --git a/apps/quanxue/pubspec.yaml b/apps/quanxue/pubspec.yaml
new file mode 100644
index 0000000..c5c5545
--- /dev/null
+++ b/apps/quanxue/pubspec.yaml
@@ -0,0 +1,93 @@
+name: quanxue
+description: "A new Flutter project."
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+# In Windows, build-name is used as the major, minor, and patch parts
+# of the product and file versions while build-number is used as the build suffix.
+version: 1.0.0+1
+
+environment:
+ sdk: ^3.11.0
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+ flutter:
+ sdk: flutter
+
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^1.0.8
+ web_shell_core:
+ path: ../../packages/web_shell_core
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+ # The "flutter_lints" package below contains a set of recommended lints to
+ # encourage good coding practices. The lint set provided by the package is
+ # activated in the `analysis_options.yaml` file located at the root of your
+ # package. See that file for information about deactivating specific lint
+ # rules and activating additional ones.
+ flutter_lints: ^6.0.0
+ flutter_launcher_icons: ^0.14.4
+ flutter_native_splash: ^2.4.7
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
+
+ # To add assets to your application, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/to/resolution-aware-images
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/to/asset-from-package
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/to/font-from-package
diff --git a/doc/architecture.md b/doc/architecture.md
new file mode 100644
index 0000000..9f93302
--- /dev/null
+++ b/doc/architecture.md
@@ -0,0 +1,93 @@
+# 架构设计
+
+## 整体架构
+
+```
+┌──────────────────────────────────────────────────────┐
+│ apps/quanxue apps/品牌B apps/品牌C │ 品牌应用层
+│ (16 行 main.dart) │ 只传 ShellEnvironment
+├──────────────────────────────────────────────────────┤
+│ web_shell_core │ 核心库
+│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
+│ │ config │ │ engine │ │ bridge │ │
+│ │ 环境配置 │ │ 兼容引擎 │ │ JS 桥接 │ │
+│ └──────────┘ └──────────┘ └──────────┘ │
+│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
+│ │ services │ │ ui │ │ testing │ │
+│ │ 宿主服务 │ │ 壳层界面 │ │ 测试钩子 │ │
+│ └──────────┘ └──────────┘ └──────────┘ │
+├──────────────────────────────────────────────────────┤
+│ CoreShellActivity (Java) │ 原生层
+│ 进程隔离 · WebView 信息查询 · 深度重置 │
+└──────────────────────────────────────────────────────┘
+```
+
+## 核心流程
+
+### 1. 启动流程
+
+```
+main() → runShellApp(env)
+ → WidgetsFlutterBinding.ensureInitialized()
+ → 设置屏幕方向(竖屏锁定)
+ → 进入沉浸式模式
+ → runApp(ShellApp)
+ → Android? → WebShellPage(WebView 容器)
+ → 其他? → UnsupportedPlatformPage(兜底页)
+```
+
+### 2. WebView 启动与恢复
+
+```
+WebShellPage.initState()
+ → 查询 Android WebView 信息(SDK / 包名 / 版本号)
+ → 生成兼容性策略(renderModes / useWideViewPort / aggressiveRecovery)
+ → 创建 WebView(默认 texture 模式)
+ → 首帧就绪后加载初始 URL
+ → 启动看门狗计时器
+ → 超时? → 切换渲染模式(hybrid)→ 深度清理 → 自动重试
+ → 再超时? → 展示错误页 + 兼容性提示
+```
+
+### 3. JS Bridge 协议
+
+```
+H5 页面 Flutter 壳
+ │ │
+ │ AppShellChannel. │
+ │ postMessage(JSON) │
+ │ ──────────────────────→ │ 解析 action + payload
+ │ │ 执行对应 handler
+ │ window. │
+ │ __appShellReceiveResponse│
+ │ ←────────────────────── │ 返回 { requestId, success, data/error }
+```
+
+**支持的 Action:**
+
+| Action | 说明 | 返回 |
+|---|---|---|
+| `pickImage` | 从图库选图(支持多选) | `[{name, uri, mimeType, size, dataUrl}]` |
+| `captureImage` | 相机拍照 | `{name, uri, mimeType, size, dataUrl}` |
+| `pickFile` | 文件选择 | `[{name, uri, mimeType, size, dataUrl}]` |
+| `openExternal` | 打开外部应用 | `boolean` |
+| `requestPermissions` | 请求系统权限 | `{type: statusName}` |
+| `reloadPage` | 重新加载当前页面 | `true` |
+| `goBack` | 返回上一页 | `boolean` |
+| `closeApp` | 关闭应用 | 无(直接退出) |
+
+## 兼容性策略
+
+| 条件 | 渲染模式 | 恢复策略 |
+|---|---|---|
+| SDK ≥ 29 + WebView ≥ 113 | texture 优先 | 标准恢复 |
+| SDK ≤ 28 或 WebView < 113 | hybrid 优先 | 激进恢复(2 次重试) |
+| F136A 设备 | hybrid 优先 | 激进恢复 + 建议更新 WebView |
+
+## 新增品牌
+
+1. 创建 `flavors/品牌名.yaml`
+2. 运行 `dart run tool/generate_app.dart 品牌名`
+3. 脚本自动生成完整的 Flutter App 在 `apps/品牌名/`
+4. 修改图标后运行 `flutter pub run flutter_launcher_icons`
+5. 构建 APK:`flutter build apk --release`
diff --git a/doc/images/foreground_spec.png b/doc/images/foreground_spec.png
new file mode 100644
index 0000000..e24d23b
Binary files /dev/null and b/doc/images/foreground_spec.png differ
diff --git a/doc/images/icon_spec.png b/doc/images/icon_spec.png
new file mode 100644
index 0000000..96a3655
Binary files /dev/null and b/doc/images/icon_spec.png differ
diff --git a/doc/images/splash_spec.png b/doc/images/splash_spec.png
new file mode 100644
index 0000000..b1be568
Binary files /dev/null and b/doc/images/splash_spec.png differ
diff --git a/doc/plan.md b/doc/plan.md
deleted file mode 100644
index f3fe258..0000000
--- a/doc/plan.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Role
-你是一个拥有 8 年经验的资深 Flutter & Android 系统架构师。你擅长对已有的 Flutter 遗留项目进行工程化改造,特别是接入多风味(Flavors)和白标(White-label)架构。
-
-# Context & Goal
-我目前已经有了一个开发好的 Flutter 壳项目。现在需要对这个**已有项目**进行 Flavors 改造,使其能够通过不同的打包命令,输出两个完全独立的 App。
-**注意:本项目是纯 Android 定制平板项目,绝对不需要任何 iOS 相关的配置和代码!**
-
-这两个 App 是:
-1. 劝学 (Flavor: `quanxue`) - 包名: `com.wanmake.quanxue`,应用名: "劝学"
-2. 点智学 (Flavor: `dianzhi`) - 包名: `com.wanmake.dianzhi`,应用名: "点智学"
-
-# Execution Steps (请提供修改现有文件的差异代码与执行步骤)
-
-## Step 1: Dart 侧运行环境变量改造
-由于是已有项目,请帮我生成一个优雅的单例配置类 `AppEnvironment`。
-1. 使用 `const String.fromEnvironment('APP_FLAVOR')` 来捕获打包时传入的参数。
-2. 根据捕获到的 Flavor,向外暴露当前 App 的主题色、`appName` 和对应的接口 `baseUrl`。
-3. 请给出 `main.dart` 中如何初始化并读取这个配置类的极简示例。
-
-## Step 2: Android 端 Gradle 深度改造
-请直接输出 `android/app/build.gradle` 需要修改的补丁代码(Patch):
-1. 移除 `defaultConfig` 中的硬编码 `applicationId`。
-2. 增加 `flavorDimensions "app_type"`。
-3. 编写 `productFlavors` 代码块,配置 `quanxue` 和 `dianzhi`,分别注入其 `applicationId`。
-4. 使用 `resValue` 将 "劝学" 和 "点智学" 注入为 `app_name`。
-
-## Step 3: AndroidManifest.xml 动态化
-请给出 `android/app/src/main/AndroidManifest.xml` 的修改代码:
-1. 将 `` 标签的 `android:label` 属性修改为读取注入的 `@string/app_name`。
-2. 确保没有任何硬编码的应用名称残留。
-
-## Step 4: 资源配置文件 (YAML) 生成
-1. 请帮我生成 4 个 YAML 配置文件,用于配合 `flutter_launcher_icons` 和 `flutter_native_splash` 插件。
-2. 为 `quanxue` 和 `dianzhi` 分别配置独立的图标和启动页路径,路径统一指向 `assets/branding/{flavor}/...`。
-3. **关键:** 在这 4 个 YAML 文件中,必须显式设置 `android: true` 并且 **`ios: false`**。
-
-## Step 5: 打包与测试脚本
-提供完整的终端测试与打包命令示例,仅限 Android 平台(包含如何传入 `--flavor` 和 `--dart-define` 打包 APK )。
diff --git a/doc/品牌资源规范.md b/doc/品牌资源规范.md
new file mode 100644
index 0000000..920ab3f
--- /dev/null
+++ b/doc/品牌资源规范.md
@@ -0,0 +1,57 @@
+# 品牌 UI 资源交付规范
+
+本文档面向 UI 设计师,说明了在使用 `web_android_shell` 框架生成各品牌 Android 壳应用时,需要提供的图片素材规格。
+
+**文件存放路径**:`flavors/<品牌名>/`
+
+---
+
+## 1. 交付总览
+
+| 资源名称 | 文件名 | 格式要求 | 尺寸 (px) | 核心要求 |
+|---|---|---|---|---|
+| **应用图标** | `icon.png` | PNG *(不透明)* | 1024 × 1024 | 正方形铺满(用于老版本系统图标)。 |
+| **自适应前景** | `icon_foreground.png` | **PNG (背景透明)** | 1024 × 1024 | 主体图形居中。四周必须保留 **33% 的透明安全区**。 |
+| **启动页图像** | `splash.png` | PNG *(含透明或实色)* | 1152 × 1152 | 核心 Logo 必须完全置于正中心的 **768×768 直径圆形**范围内。 |
+| **单色前景** *(可选)* | `icon_monochrome.png` | **PNG (背景透明)** | 1024 × 1024 | 专为 Android 13+ 提供。必须是纯黑色或纯白色(无渐变),靠透明度展现轮廓。 |
+
+---
+
+## 2. 详细规格说明与示意图
+
+### 2.1 应用图标 (`icon.png`)
+
+这是最基础的图标,用于向后兼容较老版本的 Android 系统。
+
+* **尺寸**:1024 × 1024 像素(也可提供最低 512 × 512 的设计底线)。
+* **要求**:不需要圆角或圆形裁切(系统会自动裁切),直接提交带有背景颜色的**纯正方向**图形。
+
+
+
+---
+
+### 2.2 自适应图标前景 (`icon_foreground.png`)
+
+自 Android 8.0 起,系统采用了由「背景层」+「前景层」自由组合实现动态效果的自适应图标(Adaptive Icon)。该图即为此时使用的「前景层」。
+
+* **格式**:**必须是带透明背景的 PNG (PNG-24/32)**,不能使用 JPEG,否则其白色底会遮盖住背景颜色。
+* **安全区设计**:
+ * 画布总尺寸保持 1024 × 1024。
+ * **主体内容必须集中在中央的 682 × 682 的圆形安全区内**。
+ * 外围的透明留白(约占据画布边长的 33%)将被系统底层用于视差动画和异形遮罩裁切。超出中央安全圈的内容**将被无情裁掉**。
+
+
+
+---
+
+### 2.3 启动页图片 (`splash.png`)
+
+自 Android 12 开始,系统强制接管开屏动画(SplashScreen API),对核心图像的位置和大小有极其严苛的要求,否则在 2K/4K 等高清分辨率平板上会造成拉伸模糊或主体被截断。
+
+* **画布尺寸**:**绝对正中心对其的 1152 × 1152 像素**。
+* **排版留白**:
+ * 核心 Logo 及其相关文字,必须完全置于中心一个 **直径为 768 px 的虚拟圆圈** 内。
+ * 中心圆之外的区域,将根据不同机型屏幕分辨率被裁掉。
+* **背景机制**:通常启动页背景色是实色(在 `flavors/xxx.yaml` 配置的 `splash_color`),因此该图建议为透明背景(Logo 独占);如果该图自带实色背景也没有关系,只要确保主体内容满足 768 圆圈即可。
+
+
diff --git a/flavors/quanxue.yaml b/flavors/quanxue.yaml
new file mode 100644
index 0000000..94c24e5
--- /dev/null
+++ b/flavors/quanxue.yaml
@@ -0,0 +1,14 @@
+app_name: "全学通"
+application_id: "com.wanmake.quanxue"
+app_key: "quanxue_prod"
+theme:
+ accent_color: "0xFF3ED37B"
+ bg_color: "0xFFFFFFFF"
+ text_color: "0xFF1F2937"
+ muted_text_color: "0xFF6B7280"
+branding:
+ icon: "icon.png"
+ icon_background: "#FFFFFF"
+ icon_foreground: "icon_foreground.png"
+ splash: "splash.png"
+ splash_color: "#FFFFFF"
diff --git a/flavors/quanxue/icon.png b/flavors/quanxue/icon.png
new file mode 100644
index 0000000..f19711a
Binary files /dev/null and b/flavors/quanxue/icon.png differ
diff --git a/flavors/quanxue/icon_foreground.png b/flavors/quanxue/icon_foreground.png
new file mode 100644
index 0000000..977ad82
Binary files /dev/null and b/flavors/quanxue/icon_foreground.png differ
diff --git a/flavors/quanxue/splash.png b/flavors/quanxue/splash.png
new file mode 100644
index 0000000..1a95930
Binary files /dev/null and b/flavors/quanxue/splash.png differ
diff --git a/ios/.gitignore b/ios/.gitignore
deleted file mode 100644
index ad322bc..0000000
--- a/ios/.gitignore
+++ /dev/null
@@ -1,34 +0,0 @@
-**/dgph
-*.mode1v3
-*.mode2v3
-*.moved-aside
-*.pbxuser
-*.perspectivev3
-**/*sync/
-.sconsign.dblite
-.tags*
-**/.vagrant/
-**/DerivedData/
-Icon?
-**/Pods/
-**/.symlinks/
-profile
-xcuserdata
-**/.generated/
-Flutter/App.framework
-Flutter/Flutter.framework
-Flutter/Flutter.podspec
-Flutter/Generated.xcconfig
-Flutter/ephemeral/
-Flutter/app.flx
-Flutter/app.zip
-Flutter/flutter_assets/
-Flutter/flutter_export_environment.sh
-ServiceDefinitions.json
-Runner/GeneratedPluginRegistrant.*
-
-# Exceptions to above rules.
-!default.mode1v3
-!default.mode2v3
-!default.pbxuser
-!default.perspectivev3
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
deleted file mode 100644
index 256cf28..0000000
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- App
- CFBundleIdentifier
- io.flutter.flutter.app
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- App
- CFBundlePackageType
- FMWK
- CFBundleShortVersionString
- 1.0
- CFBundleSignature
- ????
- CFBundleVersion
- 1.0
-
-
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
deleted file mode 100644
index dfd2626..0000000
--- a/ios/Flutter/Debug.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
-#include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
deleted file mode 100644
index a97381a..0000000
--- a/ios/Flutter/Release.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
-#include "Generated.xcconfig"
diff --git a/ios/Podfile b/ios/Podfile
deleted file mode 100644
index 620e46e..0000000
--- a/ios/Podfile
+++ /dev/null
@@ -1,43 +0,0 @@
-# Uncomment this line to define a global platform for your project
-# platform :ios, '13.0'
-
-# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
-ENV['COCOAPODS_DISABLE_STATS'] = 'true'
-
-project 'Runner', {
- 'Debug' => :debug,
- 'Profile' => :release,
- 'Release' => :release,
-}
-
-def flutter_root
- generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
- unless File.exist?(generated_xcode_build_settings_path)
- raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
- end
-
- File.foreach(generated_xcode_build_settings_path) do |line|
- matches = line.match(/FLUTTER_ROOT\=(.*)/)
- return matches[1].strip if matches
- end
- raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
-end
-
-require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
-
-flutter_ios_podfile_setup
-
-target 'Runner' do
- use_frameworks!
-
- flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
- target 'RunnerTests' do
- inherit! :search_paths
- end
-end
-
-post_install do |installer|
- installer.pods_project.targets.each do |target|
- flutter_additional_ios_build_settings(target)
- end
-end
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
deleted file mode 100644
index 338afe6..0000000
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,620 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 54;
- objects = {
-
-/* Begin PBXBuildFile section */
- 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
- 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
- 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
- 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
- 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
- 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
- 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 97C146E61CF9000F007C117D /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 97C146ED1CF9000F007C117D;
- remoteInfo = Runner;
- };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXCopyFilesBuildPhase section */
- 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
- 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
- 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
- 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
- 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
- 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
- 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
- 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
- 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
- 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
- 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
- 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 97C146EB1CF9000F007C117D /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 331C8082294A63A400263BE5 /* RunnerTests */ = {
- isa = PBXGroup;
- children = (
- 331C807B294A618700263BE5 /* RunnerTests.swift */,
- );
- path = RunnerTests;
- sourceTree = "";
- };
- 9740EEB11CF90186004384FC /* Flutter */ = {
- isa = PBXGroup;
- children = (
- 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
- 9740EEB21CF90195004384FC /* Debug.xcconfig */,
- 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
- 9740EEB31CF90195004384FC /* Generated.xcconfig */,
- );
- name = Flutter;
- sourceTree = "";
- };
- 97C146E51CF9000F007C117D = {
- isa = PBXGroup;
- children = (
- 9740EEB11CF90186004384FC /* Flutter */,
- 97C146F01CF9000F007C117D /* Runner */,
- 97C146EF1CF9000F007C117D /* Products */,
- 331C8082294A63A400263BE5 /* RunnerTests */,
- );
- sourceTree = "";
- };
- 97C146EF1CF9000F007C117D /* Products */ = {
- isa = PBXGroup;
- children = (
- 97C146EE1CF9000F007C117D /* Runner.app */,
- 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
- 97C146F01CF9000F007C117D /* Runner */ = {
- isa = PBXGroup;
- children = (
- 97C146FA1CF9000F007C117D /* Main.storyboard */,
- 97C146FD1CF9000F007C117D /* Assets.xcassets */,
- 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
- 97C147021CF9000F007C117D /* Info.plist */,
- 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
- 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
- 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
- 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
- 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
- );
- path = Runner;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
- 331C8080294A63A400263BE5 /* RunnerTests */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
- buildPhases = (
- 331C807D294A63A400263BE5 /* Sources */,
- 331C807F294A63A400263BE5 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- 331C8086294A63A400263BE5 /* PBXTargetDependency */,
- );
- name = RunnerTests;
- productName = RunnerTests;
- productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
- productType = "com.apple.product-type.bundle.unit-test";
- };
- 97C146ED1CF9000F007C117D /* Runner */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
- buildPhases = (
- 9740EEB61CF901F6004384FC /* Run Script */,
- 97C146EA1CF9000F007C117D /* Sources */,
- 97C146EB1CF9000F007C117D /* Frameworks */,
- 97C146EC1CF9000F007C117D /* Resources */,
- 9705A1C41CF9048500538489 /* Embed Frameworks */,
- 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = Runner;
- productName = Runner;
- productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
- productType = "com.apple.product-type.application";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 97C146E61CF9000F007C117D /* Project object */ = {
- isa = PBXProject;
- attributes = {
- BuildIndependentTargetsInParallel = YES;
- LastUpgradeCheck = 1510;
- ORGANIZATIONNAME = "";
- TargetAttributes = {
- 331C8080294A63A400263BE5 = {
- CreatedOnToolsVersion = 14.0;
- TestTargetID = 97C146ED1CF9000F007C117D;
- };
- 97C146ED1CF9000F007C117D = {
- CreatedOnToolsVersion = 7.3.1;
- LastSwiftMigration = 1100;
- };
- };
- };
- buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
- compatibilityVersion = "Xcode 9.3";
- developmentRegion = en;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 97C146E51CF9000F007C117D;
- productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 97C146ED1CF9000F007C117D /* Runner */,
- 331C8080294A63A400263BE5 /* RunnerTests */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
- 331C807F294A63A400263BE5 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 97C146EC1CF9000F007C117D /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
- 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
- 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
- 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
- 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
- isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
- );
- name = "Thin Binary";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
- };
- 9740EEB61CF901F6004384FC /* Run Script */ = {
- isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "Run Script";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
- };
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 331C807D294A63A400263BE5 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 97C146EA1CF9000F007C117D /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
- 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
- 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
- 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 97C146ED1CF9000F007C117D /* Runner */;
- targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
- };
-/* End PBXTargetDependency section */
-
-/* Begin PBXVariantGroup section */
- 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 97C146FB1CF9000F007C117D /* Base */,
- );
- name = Main.storyboard;
- sourceTree = "";
- };
- 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 97C147001CF9000F007C117D /* Base */,
- );
- name = LaunchScreen.storyboard;
- sourceTree = "";
- };
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
- 249021D3217E4FDB00AE95B9 /* Profile */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = NO;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SUPPORTED_PLATFORMS = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = Profile;
- };
- 249021D4217E4FDB00AE95B9 /* Profile */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.webshell.webAndroidShell;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Profile;
- };
- 331C8088294A63A400263BE5 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.webshell.webAndroidShell.RunnerTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = Debug;
- };
- 331C8089294A63A400263BE5 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.webshell.webAndroidShell.RunnerTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = Release;
- };
- 331C808A294A63A400263BE5 /* Profile */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.webshell.webAndroidShell.RunnerTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = Profile;
- };
- 97C147031CF9000F007C117D /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = NO;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- };
- name = Debug;
- };
- 97C147041CF9000F007C117D /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = NO;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SUPPORTED_PLATFORMS = iphoneos;
- SWIFT_COMPILATION_MODE = wholemodule;
- SWIFT_OPTIMIZATION_LEVEL = "-O";
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = Release;
- };
- 97C147061CF9000F007C117D /* Debug */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.webshell.webAndroidShell;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Debug;
- };
- 97C147071CF9000F007C117D /* Release */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.webshell.webAndroidShell;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 331C8088294A63A400263BE5 /* Debug */,
- 331C8089294A63A400263BE5 /* Release */,
- 331C808A294A63A400263BE5 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 97C147031CF9000F007C117D /* Debug */,
- 97C147041CF9000F007C117D /* Release */,
- 249021D3217E4FDB00AE95B9 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 97C147061CF9000F007C117D /* Debug */,
- 97C147071CF9000F007C117D /* Release */,
- 249021D4217E4FDB00AE95B9 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = 97C146E61CF9000F007C117D /* Project object */;
-}
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index c4b79bd..0000000
--- a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index fc6bf80..0000000
--- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
deleted file mode 100644
index af0309c..0000000
--- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- PreviewsEnabled
-
-
-
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
deleted file mode 100644
index bbabc4e..0000000
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 59c6d39..0000000
--- a/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index fc6bf80..0000000
--- a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
deleted file mode 100644
index af0309c..0000000
--- a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- PreviewsEnabled
-
-
-
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
deleted file mode 100644
index ed1c097..0000000
--- a/ios/Runner/AppDelegate.swift
+++ /dev/null
@@ -1,16 +0,0 @@
-import Flutter
-import UIKit
-
-@main
-@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
- override func application(
- _ application: UIApplication,
- didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
- ) -> Bool {
- return super.application(application, didFinishLaunchingWithOptions: launchOptions)
- }
-
- func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
- GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
- }
-}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index 1950fd8..0000000
--- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,122 +0,0 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "Icon-App-83.5x83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon-App-1024x1024@1x.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
deleted file mode 100644
index dc9ada4..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
deleted file mode 100644
index 7353c41..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
deleted file mode 100644
index 797d452..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
deleted file mode 100644
index 6ed2d93..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
deleted file mode 100644
index 4cd7b00..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
deleted file mode 100644
index fe73094..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
deleted file mode 100644
index 321773c..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
deleted file mode 100644
index 797d452..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
deleted file mode 100644
index 502f463..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
deleted file mode 100644
index 0ec3034..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
deleted file mode 100644
index 0ec3034..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
deleted file mode 100644
index e9f5fea..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
deleted file mode 100644
index 84ac32a..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
deleted file mode 100644
index 8953cba..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
deleted file mode 100644
index 0467bf1..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
deleted file mode 100644
index d08a4de..0000000
--- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "filename" : "LaunchImage.png",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "LaunchImage@2x.png",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "filename" : "LaunchImage@3x.png",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
deleted file mode 100644
index 9da19ea..0000000
Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
deleted file mode 100644
index 9da19ea..0000000
Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
deleted file mode 100644
index 9da19ea..0000000
Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
deleted file mode 100644
index 65a94b5..0000000
--- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Launch Screen Assets
-
-You can customize the launch screen with your own desired assets by replacing the image files in this directory.
-
-You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard
deleted file mode 100644
index 497371e..0000000
--- a/ios/Runner/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
deleted file mode 100644
index bbb83ca..0000000
--- a/ios/Runner/Base.lproj/Main.storyboard
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
deleted file mode 100644
index 77f90b9..0000000
--- a/ios/Runner/Info.plist
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
- CADisableMinimumFrameDurationOnPhone
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleDisplayName
- web_android_shell
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- web_android_shell
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
- CFBundleSignature
- ????
- CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
- LSRequiresIPhoneOS
-
- UIApplicationSceneManifest
-
- UIApplicationSupportsMultipleScenes
-
- UISceneConfigurations
-
- UIWindowSceneSessionRoleApplication
-
-
- UISceneClassName
- UIWindowScene
- UISceneConfigurationName
- flutter
- UISceneDelegateClassName
- $(PRODUCT_MODULE_NAME).SceneDelegate
- UISceneStoryboardFile
- Main
-
-
-
-
- UIApplicationSupportsIndirectInputEvents
-
- UILaunchStoryboardName
- LaunchScreen
- UIMainStoryboardFile
- Main
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
-
-
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
deleted file mode 100644
index fae207f..0000000
--- a/ios/Runner/Runner-Bridging-Header.h
+++ /dev/null
@@ -1 +0,0 @@
-#import "GeneratedPluginRegistrant.h"
diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift
deleted file mode 100644
index b79be9b..0000000
--- a/ios/Runner/SceneDelegate.swift
+++ /dev/null
@@ -1,6 +0,0 @@
-import Flutter
-import UIKit
-
-class SceneDelegate: FlutterSceneDelegate {
-
-}
diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift
deleted file mode 100644
index 4d206de..0000000
--- a/ios/RunnerTests/RunnerTests.swift
+++ /dev/null
@@ -1,12 +0,0 @@
-import Flutter
-import UIKit
-import XCTest
-
-class RunnerTests: XCTestCase {
-
- func testExample() {
- // If you add code to the Runner application, consider adding tests here.
- // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
- }
-
-}
diff --git a/packages/web_android_shell/.gitignore b/packages/web_android_shell/.gitignore
new file mode 100644
index 0000000..6f0d006
--- /dev/null
+++ b/packages/web_android_shell/.gitignore
@@ -0,0 +1,45 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# 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/packages/web_android_shell/.metadata
similarity index 100%
rename from .metadata
rename to packages/web_android_shell/.metadata
diff --git a/packages/web_android_shell/README.md b/packages/web_android_shell/README.md
new file mode 100644
index 0000000..f5015e2
--- /dev/null
+++ b/packages/web_android_shell/README.md
@@ -0,0 +1,7 @@
+# web_android_shell(已迁移)
+
+> ⚠️ 本包为旧版入口,已迁移至 Monorepo 架构。
+
+当前用途:为旧版 `MainActivity` 提供壳层入口,调用 `web_shell_core` 启动应用。
+
+新品牌应用请使用 `apps/` 目录下的独立应用,参考 [`apps/quanxue/`](../../apps/quanxue/)。
diff --git a/packages/web_android_shell/analysis_options.yaml b/packages/web_android_shell/analysis_options.yaml
new file mode 100644
index 0000000..f13d6ae
--- /dev/null
+++ b/packages/web_android_shell/analysis_options.yaml
@@ -0,0 +1,9 @@
+# Dart 分析器配置,用于在开发阶段发现错误、警告和代码规范问题。
+# 可通过 `flutter analyze` 执行静态检查。
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # 如需自定义规则,可在此处开启或关闭指定 lint。
+ rules:
+ # avoid_print: false # 取消注释后可关闭 `avoid_print` 规则。
+ # prefer_single_quotes: true # 取消注释后可开启 `prefer_single_quotes` 规则。
diff --git a/android/.gitignore b/packages/web_android_shell/android/.gitignore
similarity index 100%
rename from android/.gitignore
rename to packages/web_android_shell/android/.gitignore
diff --git a/packages/web_android_shell/android/app/build.gradle.kts b/packages/web_android_shell/android/app/build.gradle.kts
new file mode 100644
index 0000000..5dfc1c2
--- /dev/null
+++ b/packages/web_android_shell/android/app/build.gradle.kts
@@ -0,0 +1,45 @@
+plugins {
+ id("com.android.application")
+ id("kotlin-android")
+ // Flutter Gradle 插件必须放在 Android 与 Kotlin Gradle 插件之后应用。
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+android {
+ namespace = "com.yuanxuan.webshell.web_android_shell"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+
+ defaultConfig {
+ // 待补充:请替换成你自己的唯一应用标识。
+ applicationId = "com.yuanxuan.webshell.web_android_shell"
+ // 下面这些值可以按应用实际需求调整。
+ // 更多说明可参考:https://flutter.dev/to/review-gradle-config。
+ minSdk = flutter.minSdkVersion
+ // 为兼容旧版系统 WebView,这里保持稳定的 targetSdk。
+ targetSdk = 34
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // 待补充:为 release 构建配置正式签名。
+ // 当前先使用 debug 签名,确保 `flutter run --release` 可直接运行。
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/android/app/src/debug/AndroidManifest.xml b/packages/web_android_shell/android/app/src/debug/AndroidManifest.xml
similarity index 100%
rename from android/app/src/debug/AndroidManifest.xml
rename to packages/web_android_shell/android/app/src/debug/AndroidManifest.xml
diff --git a/android/app/src/main/AndroidManifest.xml b/packages/web_android_shell/android/app/src/main/AndroidManifest.xml
similarity index 100%
rename from android/app/src/main/AndroidManifest.xml
rename to packages/web_android_shell/android/app/src/main/AndroidManifest.xml
diff --git a/android/app/src/main/java/com/yuanxuan/webshell/web_android_shell/MainActivity.java b/packages/web_android_shell/android/app/src/main/java/com/yuanxuan/webshell/web_android_shell/MainActivity.java
similarity index 97%
rename from android/app/src/main/java/com/yuanxuan/webshell/web_android_shell/MainActivity.java
rename to packages/web_android_shell/android/app/src/main/java/com/yuanxuan/webshell/web_android_shell/MainActivity.java
index 53cf90a..ee38a3e 100644
--- a/android/app/src/main/java/com/yuanxuan/webshell/web_android_shell/MainActivity.java
+++ b/packages/web_android_shell/android/app/src/main/java/com/yuanxuan/webshell/web_android_shell/MainActivity.java
@@ -126,7 +126,7 @@ public class MainActivity extends FlutterActivity {
Process.killProcess(previousPid);
SystemClock.sleep(180);
} catch (RuntimeException ignored) {
- // Ignore kill failures and continue startup with the current process.
+ // 忽略终止旧进程失败的情况,并继续使用当前进程启动。
}
}
@@ -179,7 +179,7 @@ public class MainActivity extends FlutterActivity {
deleteDatabase("webview.db");
deleteDatabase("webviewCache.db");
} catch (RuntimeException ignored) {
- // Ignore cleanup failures on legacy devices and continue with startup.
+ // 忽略旧设备上的清理失败,继续执行启动流程。
}
}
@@ -201,7 +201,7 @@ public class MainActivity extends FlutterActivity {
}
}
- //noinspection ResultOfMethodCallIgnored
+
target.delete();
}
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/web_android_shell/android/app/src/main/res/drawable-v21/launch_background.xml
similarity index 100%
rename from android/app/src/main/res/drawable-v21/launch_background.xml
rename to packages/web_android_shell/android/app/src/main/res/drawable-v21/launch_background.xml
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/packages/web_android_shell/android/app/src/main/res/drawable/launch_background.xml
similarity index 100%
rename from android/app/src/main/res/drawable/launch_background.xml
rename to packages/web_android_shell/android/app/src/main/res/drawable/launch_background.xml
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/web_android_shell/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from android/app/src/main/res/mipmap-hdpi/ic_launcher.png
rename to packages/web_android_shell/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/web_android_shell/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from android/app/src/main/res/mipmap-mdpi/ic_launcher.png
rename to packages/web_android_shell/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/web_android_shell/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
rename to packages/web_android_shell/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/web_android_shell/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
rename to packages/web_android_shell/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/web_android_shell/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
rename to packages/web_android_shell/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
diff --git a/android/app/src/main/res/values-night/styles.xml b/packages/web_android_shell/android/app/src/main/res/values-night/styles.xml
similarity index 100%
rename from android/app/src/main/res/values-night/styles.xml
rename to packages/web_android_shell/android/app/src/main/res/values-night/styles.xml
diff --git a/android/app/src/main/res/values/styles.xml b/packages/web_android_shell/android/app/src/main/res/values/styles.xml
similarity index 100%
rename from android/app/src/main/res/values/styles.xml
rename to packages/web_android_shell/android/app/src/main/res/values/styles.xml
diff --git a/android/app/src/profile/AndroidManifest.xml b/packages/web_android_shell/android/app/src/profile/AndroidManifest.xml
similarity index 100%
rename from android/app/src/profile/AndroidManifest.xml
rename to packages/web_android_shell/android/app/src/profile/AndroidManifest.xml
diff --git a/android/build.gradle.kts b/packages/web_android_shell/android/build.gradle.kts
similarity index 100%
rename from android/build.gradle.kts
rename to packages/web_android_shell/android/build.gradle.kts
diff --git a/android/gradle.properties b/packages/web_android_shell/android/gradle.properties
similarity index 100%
rename from android/gradle.properties
rename to packages/web_android_shell/android/gradle.properties
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/packages/web_android_shell/android/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
rename from android/gradle/wrapper/gradle-wrapper.properties
rename to packages/web_android_shell/android/gradle/wrapper/gradle-wrapper.properties
diff --git a/android/settings.gradle.kts b/packages/web_android_shell/android/settings.gradle.kts
similarity index 100%
rename from android/settings.gradle.kts
rename to packages/web_android_shell/android/settings.gradle.kts
diff --git a/lib/main.dart b/packages/web_android_shell/lib/main.dart
similarity index 95%
rename from lib/main.dart
rename to packages/web_android_shell/lib/main.dart
index f12a3fc..f10f27b 100644
--- a/lib/main.dart
+++ b/packages/web_android_shell/lib/main.dart
@@ -65,9 +65,7 @@ bool _supportsEmbeddedWebView({bool isWeb = kIsWeb, TargetPlatform? platform}) {
}
final TargetPlatform target = platform ?? defaultTargetPlatform;
- return target == TargetPlatform.android ||
- target == TargetPlatform.iOS ||
- target == TargetPlatform.macOS;
+ return target == TargetPlatform.android;
}
Future main() async {
@@ -327,7 +325,7 @@ class _WebShellPageState extends State
@override
void initState() {
super.initState();
- debugPrint('WebShell initState, initialUrl=$_initialUrl');
+ debugPrint('WebShell 初始化,初始地址=$_initialUrl');
WidgetsBinding.instance.addObserver(this);
_androidCompatibilityFuture = _prepareAndroidCompatibility();
_recreateWebView();
@@ -377,7 +375,7 @@ class _WebShellPageState extends State
_androidWebViewInfo = _AndroidWebViewInfo.fromMap(rawInfo);
}
} catch (error, stackTrace) {
- debugPrint('Load Android WebView info failed: $error\n$stackTrace');
+ debugPrint('读取 Android WebView 信息失败:$error\n$stackTrace');
}
_androidCompatibilityPlan = _AndroidCompatibilityPlan.fromInfo(
@@ -385,11 +383,11 @@ class _WebShellPageState extends State
);
_renderModeIndex = 0;
debugPrint(
- 'WebShell Android WebView info: '
- '${_androidWebViewInfo?.summary ?? 'unavailable'}',
+ 'WebShell Android WebView 信息:'
+ '${_androidWebViewInfo?.summary ?? '不可用'}',
);
debugPrint(
- 'WebShell compatibility plan: ${_androidCompatibilityPlan.describe()}',
+ 'WebShell 兼容策略:${_androidCompatibilityPlan.describe()}',
);
}
@@ -404,7 +402,7 @@ class _WebShellPageState extends State
_hasAppliedCompatibilityPlan = true;
if (_configuredRenderMode == _activeRenderMode) {
debugPrint(
- 'WebShell compatibility plan keeps current WebView '
+ 'WebShell 兼容策略保持当前 WebView '
'(${_activeRenderMode.logName})',
);
return;
@@ -419,7 +417,7 @@ class _WebShellPageState extends State
}
_renderModeIndex = nextIndex;
- debugPrint('WebShell switched render mode to ${_activeRenderMode.logName}');
+ debugPrint('WebShell 已切换渲染模式为 ${_activeRenderMode.logName}');
return true;
}
@@ -454,7 +452,7 @@ class _WebShellPageState extends State
_controllerSetupFuture = _configureController(controller, generation);
_configuredRenderMode = renderMode;
debugPrint(
- 'WebShell recreate WebView #$generation with ${renderMode.logName}',
+ 'WebShell 正在以 ${renderMode.logName} 重建 WebView #$generation',
);
PlatformWebViewWidgetCreationParams widgetParams =
PlatformWebViewWidgetCreationParams(
@@ -525,7 +523,7 @@ class _WebShellPageState extends State
);
await androidController.setOnConsoleMessage((message) {
debugPrint(
- 'WebView console [${message.level.name}] ${message.message}',
+ 'WebView 控制台 [${message.level.name}] ${message.message}',
);
});
}
@@ -580,7 +578,7 @@ class _WebShellPageState extends State
return;
}
if (!_isNetworkUrl(url)) {
- debugPrint('WebView ignore non-network page start: $url');
+ debugPrint('WebView 忽略非网络页面开始事件:$url');
return;
}
_hasStartedRemoteMainFrame = true;
@@ -606,7 +604,7 @@ class _WebShellPageState extends State
return;
}
if (!_isNetworkUrl(url)) {
- debugPrint('WebView ignore non-network page finish: $url');
+ debugPrint('WebView 忽略非网络页面完成事件:$url');
return;
}
_recordWebViewEvent('页面加载完成:$url');
@@ -633,13 +631,13 @@ class _WebShellPageState extends State
final Uri? requestUri = error.request?.uri;
if (!_shouldTreatHttpErrorAsMainFrame(error)) {
debugPrint(
- 'Ignore subresource HTTP error: '
- '${statusCode ?? 'unknown'} ${requestUri ?? 'unknown'}',
+ '忽略子资源 HTTP 错误:'
+ '${statusCode ?? '未知'} ${requestUri ?? '未知'}',
);
return;
}
_recordWebViewEvent(
- 'HTTP 错误:${statusCode ?? 'unknown'} ${requestUri ?? _currentUrl}',
+ 'HTTP 错误:${statusCode ?? '未知'} ${requestUri ?? _currentUrl}',
);
_setMainFrameError(
title: statusCode == null ? '服务器响应异常' : '服务器异常 $statusCode',
@@ -692,7 +690,7 @@ class _WebShellPageState extends State
if (!mounted) {
return;
}
- debugPrint('WebShell first frame ready, start initial load');
+ debugPrint('WebShell 首帧已就绪,开始初始加载');
await _startLoadSequence(rebuildWebView: false, resetRetryCount: true);
}
@@ -741,7 +739,7 @@ class _WebShellPageState extends State
void _recordWebViewEvent(String event) {
_lastWebViewEvent = event;
- debugPrint('WebView $event');
+ debugPrint('WebView 事件:$event');
}
void _armStartupWatchdog() {
@@ -815,25 +813,25 @@ class _WebShellPageState extends State
try {
await controllerSetupFuture;
} catch (error, stackTrace) {
- debugPrint('Await WebView controller setup failed: $error\n$stackTrace');
+ debugPrint('等待 WebView 控制器初始化失败:$error\n$stackTrace');
}
try {
await _controller.clearCache();
} catch (error, stackTrace) {
- debugPrint('Clear WebView cache failed: $error\n$stackTrace');
+ debugPrint('清理 WebView 缓存失败:$error\n$stackTrace');
}
try {
await _controller.clearLocalStorage();
} catch (error, stackTrace) {
- debugPrint('Clear WebView local storage failed: $error\n$stackTrace');
+ debugPrint('清理 WebView 本地存储失败:$error\n$stackTrace');
}
try {
await _cookieManager.clearCookies();
} catch (error, stackTrace) {
- debugPrint('Clear WebView cookies failed: $error\n$stackTrace');
+ debugPrint('清理 WebView Cookie 失败:$error\n$stackTrace');
}
if (!deepReset || defaultTargetPlatform != TargetPlatform.android) {
@@ -845,7 +843,7 @@ class _WebShellPageState extends State
'resetAndroidWebViewState',
);
} catch (error, stackTrace) {
- debugPrint('Reset Android WebView state failed: $error\n$stackTrace');
+ debugPrint('重置 Android WebView 状态失败:$error\n$stackTrace');
}
}
@@ -940,9 +938,9 @@ class _WebShellPageState extends State
Future _injectAppShellBridge(String url) async {
try {
await _controller.runJavaScript(_buildAppShellBridgeScript());
- debugPrint('Injected AppShell bridge for $url');
+ debugPrint('已为 $url 注入 AppShell 桥接');
} catch (error, stackTrace) {
- debugPrint('Inject AppShell bridge failed: $error\n$stackTrace');
+ debugPrint('注入 AppShell 桥接失败:$error\n$stackTrace');
}
}
@@ -966,7 +964,7 @@ class _WebShellPageState extends State
return;
}
- debugPrint('AppShell bridge request: action=$action payload=$payload');
+ debugPrint('AppShell 桥接请求:action=$action payload=$payload');
late final Object? data;
switch (action) {
@@ -1005,7 +1003,7 @@ class _WebShellPageState extends State
data: data,
);
} catch (error, stackTrace) {
- debugPrint('Handle AppShell bridge failed: $error\n$stackTrace');
+ debugPrint('处理 AppShell 桥接请求失败:$error\n$stackTrace');
if (requestId.isNotEmpty) {
await _sendBridgeResponse(
requestId: requestId,
@@ -1148,14 +1146,14 @@ class _WebShellPageState extends State
);
} catch (bridgeError, stackTrace) {
debugPrint(
- 'Send AppShell bridge response failed: $bridgeError\n$stackTrace',
+ '发送 AppShell 桥接响应失败:$bridgeError\n$stackTrace',
);
}
}
Future> _handleFileSelector(FileSelectorParams params) async {
debugPrint(
- 'WebView file selector: '
+ 'WebView 文件选择: '
'accept=${params.acceptTypes}, '
'capture=${params.isCaptureEnabled}, '
'mode=${params.mode.name}',
@@ -1212,7 +1210,7 @@ class _WebShellPageState extends State
.map((path) => Uri.file(path).toString())
.toList();
} catch (error, stackTrace) {
- debugPrint('Handle file selector failed: $error\n$stackTrace');
+ debugPrint('处理文件选择失败:$error\n$stackTrace');
return [];
}
}
@@ -1221,7 +1219,7 @@ class _WebShellPageState extends State
PlatformWebViewPermissionRequest request,
) async {
debugPrint(
- 'WebView permission request: '
+ 'WebView 权限请求:'
'${request.types.map((type) => type.name).join(', ')}',
);
@@ -1252,7 +1250,7 @@ class _WebShellPageState extends State
Future _handleGeolocationPermissionRequest(
GeolocationPermissionsRequestParams request,
) async {
- debugPrint('WebView geolocation permission request: ${request.origin}');
+ debugPrint('WebView 地理位置权限请求:${request.origin}');
final PermissionStatus status = await Permission.location.request();
return GeolocationPermissionsResponse(
allow: status.isGranted,
@@ -1278,7 +1276,7 @@ class _WebShellPageState extends State
maxHeight: _pickedImageMaxHeight,
);
} catch (error, stackTrace) {
- debugPrint('Pick camera image failed: $error\n$stackTrace');
+ debugPrint('调用相机拍照失败:$error\n$stackTrace');
if (showPermissionAlert) {
await _showWebAlert('无法打开系统相机,请稍后重试');
}
@@ -1355,7 +1353,7 @@ class _WebShellPageState extends State
try {
return await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (error, stackTrace) {
- debugPrint('Open external uri failed: $error\n$stackTrace');
+ debugPrint('外部打开 URI 失败:$error\n$stackTrace');
return false;
}
}
@@ -1385,7 +1383,7 @@ class _WebShellPageState extends State
try {
await _controller.runJavaScript('window.alert(${jsonEncode(message)});');
} catch (error, stackTrace) {
- debugPrint('Show web alert failed: $error\n$stackTrace');
+ debugPrint('展示网页弹窗失败:$error\n$stackTrace');
}
}
@@ -1689,14 +1687,14 @@ class _WebShellPageState extends State
await _waitForWebViewMount(generation);
debugPrint(
- 'WebShell start real URL load on WebView #$generation '
+ 'WebShell 开始在 WebView #$generation 加载真实地址 '
'(${_activeRenderMode.logName}): $_initialUrl',
);
try {
_armStartupWatchdog();
await _controller.loadRequest(_initialUri);
} catch (error, stackTrace) {
- debugPrint('Start initial URL load failed: $error\n$stackTrace');
+ debugPrint('初始地址加载失败:$error\n$stackTrace');
_setMainFrameError(
title: '地址无法加载',
message: '无法打开 $_initialUrl,请检查地址格式或网络后重试。',
@@ -1760,7 +1758,7 @@ class _WebShellPageState extends State
_armStartupWatchdog();
await _controller.reload();
} catch (error, stackTrace) {
- debugPrint('Reload current page failed: $error\n$stackTrace');
+ debugPrint('重新加载当前页面失败:$error\n$stackTrace');
await _startLoadSequence(rebuildWebView: true, resetRetryCount: true);
}
}
@@ -1873,7 +1871,7 @@ class UnsupportedPlatformPage extends StatelessWidget {
),
const SizedBox(height: 12),
Text(
- '请在 Android、iOS 或 macOS 上运行当前项目。\n$_initialUrl',
+ '当前项目仅支持 Android 平板运行。\n$_initialUrl',
textAlign: TextAlign.center,
style: const TextStyle(
height: 1.6,
diff --git a/packages/web_android_shell/pubspec.lock b/packages/web_android_shell/pubspec.lock
new file mode 100644
index 0000000..1997430
--- /dev/null
+++ b/packages/web_android_shell/pubspec.lock
@@ -0,0 +1,578 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.7.0"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.13.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.2"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.1"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.1.2"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.19.1"
+ cross_file:
+ dependency: transitive
+ description:
+ name: cross_file
+ sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.3.5+2"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.0.8"
+ dbus:
+ dependency: transitive
+ description:
+ name: dbus
+ sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.7.12"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.3.3"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.2.0"
+ file_picker:
+ dependency: "direct main"
+ description:
+ name: file_picker
+ sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "10.3.10"
+ file_selector_linux:
+ dependency: transitive
+ description:
+ name: file_selector_linux
+ sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.9.4"
+ file_selector_macos:
+ dependency: transitive
+ description:
+ name: file_selector_macos
+ sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.9.5"
+ file_selector_platform_interface:
+ dependency: transitive
+ description:
+ name: file_selector_platform_interface
+ sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.7.0"
+ file_selector_windows:
+ dependency: transitive
+ description:
+ name: file_selector_windows
+ sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.9.3+5"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.0.0"
+ flutter_plugin_android_lifecycle:
+ dependency: transitive
+ description:
+ name: flutter_plugin_android_lifecycle
+ sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.0.33"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ http:
+ dependency: transitive
+ description:
+ name: http
+ sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.6.0"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.1.2"
+ image_picker:
+ dependency: "direct main"
+ description:
+ name: image_picker
+ sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.2.1"
+ image_picker_android:
+ dependency: transitive
+ description:
+ name: image_picker_android
+ sha256: eda9b91b7e266d9041084a42d605a74937d996b87083395c5e47835916a86156
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.8.13+14"
+ image_picker_for_web:
+ dependency: transitive
+ description:
+ name: image_picker_for_web
+ sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.1.1"
+ image_picker_ios:
+ dependency: transitive
+ description:
+ name: image_picker_ios
+ sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.8.13+6"
+ image_picker_linux:
+ dependency: transitive
+ description:
+ name: image_picker_linux
+ sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.2"
+ image_picker_macos:
+ dependency: transitive
+ description:
+ name: image_picker_macos
+ sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.2+1"
+ image_picker_platform_interface:
+ dependency: transitive
+ description:
+ name: image_picker_platform_interface
+ sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.11.1"
+ image_picker_windows:
+ dependency: transitive
+ description:
+ name: image_picker_windows
+ sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.2"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "11.0.2"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.10"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.2"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.1.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.12.18"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.13.0"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.17.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.0.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.9.1"
+ permission_handler:
+ dependency: "direct main"
+ description:
+ name: permission_handler
+ sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "12.0.1"
+ permission_handler_android:
+ dependency: transitive
+ description:
+ name: permission_handler_android
+ sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "13.0.1"
+ permission_handler_apple:
+ dependency: transitive
+ description:
+ name: permission_handler_apple
+ sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "9.4.7"
+ permission_handler_html:
+ dependency: transitive
+ description:
+ name: permission_handler_html
+ sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.1.3+5"
+ permission_handler_platform_interface:
+ dependency: transitive
+ description:
+ name: permission_handler_platform_interface
+ sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.3.0"
+ permission_handler_windows:
+ dependency: transitive
+ description:
+ name: permission_handler_windows
+ sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.1"
+ petitparser:
+ dependency: transitive
+ description:
+ name: petitparser
+ sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "7.0.2"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.8"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.10.2"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.12.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.4"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.1"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.2.2"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.7.9"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.0"
+ url_launcher:
+ dependency: "direct main"
+ description:
+ name: url_launcher
+ sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.3.2"
+ url_launcher_android:
+ dependency: transitive
+ description:
+ name: url_launcher_android
+ sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.3.28"
+ url_launcher_ios:
+ dependency: transitive
+ description:
+ name: url_launcher_ios
+ sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.4.1"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.2.2"
+ url_launcher_macos:
+ dependency: transitive
+ description:
+ name: url_launcher_macos
+ sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.2.5"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.3.2"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.2"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.1.5"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.2.0"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "15.0.2"
+ web:
+ dependency: transitive
+ description:
+ name: web
+ sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.1.1"
+ webview_flutter:
+ dependency: "direct main"
+ description:
+ name: webview_flutter
+ sha256: a3da219916aba44947d3a5478b1927876a09781174b5a2b67fa5be0555154bf9
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.13.1"
+ webview_flutter_android:
+ dependency: "direct main"
+ description:
+ name: webview_flutter_android
+ sha256: "2a03df01df2fd30b075d1e7f24c28aee593f2e5d5ac4c3c4283c5eda63717b24"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.10.13"
+ webview_flutter_platform_interface:
+ dependency: transitive
+ description:
+ name: webview_flutter_platform_interface
+ sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.14.0"
+ webview_flutter_wkwebview:
+ dependency: transitive
+ description:
+ name: webview_flutter_wkwebview
+ sha256: "2df8fd9ada04d699b9db8e79aa783a16e5d89b69e5b74009b87e16b59912cf98"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.24.0"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.15.0"
+ xml:
+ dependency: transitive
+ description:
+ name: xml
+ sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.6.1"
+sdks:
+ dart: ">=3.11.0 <4.0.0"
+ flutter: ">=3.38.0"
diff --git a/packages/web_android_shell/pubspec.yaml b/packages/web_android_shell/pubspec.yaml
new file mode 100644
index 0000000..2780208
--- /dev/null
+++ b/packages/web_android_shell/pubspec.yaml
@@ -0,0 +1,35 @@
+name: web_android_shell
+description: "H5壳子项目."
+# 阻止误发布到 pub.dev。
+publish_to: 'none'
+
+# Android 应用版本号:`build-name` 对应 `versionName`,`build-number` 对应 `versionCode`。
+version: 1.0.0+1
+
+environment:
+ sdk: ^3.11.0
+
+# 应用运行依赖。
+dependencies:
+ flutter:
+ sdk: flutter
+
+ # Material 图标已覆盖主要场景;此依赖保留以兼容历史代码。
+ cupertino_icons: ^1.0.8
+ webview_flutter: ^4.13.1
+ webview_flutter_android: ^4.10.13
+ image_picker: ^1.2.1
+ file_picker: ^10.3.10
+ permission_handler: ^12.0.1
+ url_launcher: ^6.3.2
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+ # 推荐的 Flutter lint 规则集合。
+ flutter_lints: ^6.0.0
+
+flutter:
+ # 启用 Material 图标字体。
+ uses-material-design: true
diff --git a/test/widget_test.dart b/packages/web_android_shell/test/widget_test.dart
similarity index 100%
rename from test/widget_test.dart
rename to packages/web_android_shell/test/widget_test.dart
diff --git a/packages/web_android_shell/tool/flutter_run_fresh.ps1 b/packages/web_android_shell/tool/flutter_run_fresh.ps1
new file mode 100644
index 0000000..e4591e7
--- /dev/null
+++ b/packages/web_android_shell/tool/flutter_run_fresh.ps1
@@ -0,0 +1,187 @@
+[CmdletBinding()]
+param(
+ [ValidateSet('Auto', 'Disable', 'Enable', 'Keep')]
+ [string]$WebViewMultiprocess = 'Auto',
+
+ [Parameter(ValueFromRemainingArguments = $true)]
+ [string[]]$FlutterArgs
+)
+
+$ErrorActionPreference = 'Stop'
+
+$projectRoot = Split-Path -Parent $PSScriptRoot
+$localPropertiesPath = Join-Path $projectRoot 'android\local.properties'
+$packageName = 'com.yuanxuan.webshell.web_android_shell'
+
+function Get-LocalPropertyValue {
+ param(
+ [string]$Path,
+ [string]$Name
+ )
+
+ if (-not (Test-Path $Path)) {
+ return $null
+ }
+
+ $escapedName = [regex]::Escape($Name)
+ foreach ($line in Get-Content $Path) {
+ if ($line -match "^$escapedName=(.+)$") {
+ return $matches[1].Trim() -replace '\\\\', '\'
+ }
+ }
+
+ return $null
+}
+
+function Get-DeviceIdFromArgs {
+ param([string[]]$ArgsToScan)
+
+ for ($index = 0; $index -lt $ArgsToScan.Length; $index += 1) {
+ $arg = $ArgsToScan[$index]
+ if ($arg -eq '-d' -or $arg -eq '--device-id') {
+ if ($index + 1 -lt $ArgsToScan.Length) {
+ return $ArgsToScan[$index + 1]
+ }
+ }
+
+ if ($arg.StartsWith('--device-id=')) {
+ return $arg.Substring('--device-id='.Length)
+ }
+ }
+
+ return $null
+}
+
+function Get-DeviceModel {
+ param(
+ [string]$AdbPath,
+ [string[]]$AdbArgsPrefix
+ )
+
+ return ((& $AdbPath @AdbArgsPrefix shell getprop ro.product.model) | Out-String).Trim()
+}
+
+function Get-WebViewUpdateState {
+ param(
+ [string]$AdbPath,
+ [string[]]$AdbArgsPrefix
+ )
+
+ return (& $AdbPath @AdbArgsPrefix shell dumpsys webviewupdate | Out-String)
+}
+
+function Get-WebViewInfo {
+ param(
+ [string]$AdbPath,
+ [string[]]$AdbArgsPrefix
+ )
+
+ $state = Get-WebViewUpdateState -AdbPath $AdbPath -AdbArgsPrefix $AdbArgsPrefix
+ $multiprocessEnabled = $state -match 'Multiprocess enabled:\s+true'
+ $versionName = $null
+
+ if ($state -match 'Current WebView package \(name, version\): \([^\),]+,\s*([^\)]+)\)') {
+ $versionName = $matches[1].Trim()
+ }
+
+ $majorVersion = $null
+ if ($versionName) {
+ $majorToken = $versionName.Split('.')[0]
+ $parsedMajor = 0
+ if ([int]::TryParse($majorToken, [ref]$parsedMajor)) {
+ $majorVersion = $parsedMajor
+ }
+ }
+
+ return @{
+ MultiprocessEnabled = $multiprocessEnabled
+ VersionName = $versionName
+ MajorVersion = $majorVersion
+ RawState = $state
+ }
+}
+
+function Set-WebViewMultiprocessState {
+ param(
+ [string]$AdbPath,
+ [string[]]$AdbArgsPrefix,
+ [bool]$Enabled
+ )
+
+ $command = if ($Enabled) { 'enable-multiprocess' } else { 'disable-multiprocess' }
+ & $AdbPath @AdbArgsPrefix shell cmd webviewupdate $command | Out-Host
+ Start-Sleep -Seconds 1
+}
+
+$sdkDir = Get-LocalPropertyValue -Path $localPropertiesPath -Name 'sdk.dir'
+$adbPath = $null
+
+if ($sdkDir) {
+ $candidate = Join-Path $sdkDir 'platform-tools\adb.exe'
+ if (Test-Path $candidate) {
+ $adbPath = $candidate
+ }
+}
+
+if (-not $adbPath) {
+ $adbCommand = Get-Command adb.exe -ErrorAction SilentlyContinue
+ if ($adbCommand) {
+ $adbPath = $adbCommand.Source
+ }
+}
+
+if (-not $adbPath) {
+ throw "未找到 adb.exe,请检查 android/local.properties 中的 sdk.dir。"
+}
+
+$deviceId = Get-DeviceIdFromArgs -ArgsToScan $FlutterArgs
+$adbArgsPrefix = @()
+
+if ($deviceId) {
+ $adbArgsPrefix = @('-s', $deviceId)
+}
+
+Write-Host "Using adb: $adbPath"
+if ($deviceId) {
+ Write-Host "Target device: $deviceId"
+}
+
+& $adbPath @adbArgsPrefix start-server | Out-Null
+
+$deviceModel = Get-DeviceModel -AdbPath $adbPath -AdbArgsPrefix $adbArgsPrefix
+$webViewInfo = Get-WebViewInfo -AdbPath $adbPath -AdbArgsPrefix $adbArgsPrefix
+
+Write-Host "Device model: $deviceModel"
+if ($webViewInfo.VersionName) {
+ Write-Host "System WebView: $($webViewInfo.VersionName)"
+}
+Write-Host "WebView multiprocess enabled: $($webViewInfo.MultiprocessEnabled)"
+
+$shouldDisableMultiprocess = $false
+switch ($WebViewMultiprocess) {
+ 'Disable' {
+ $shouldDisableMultiprocess = $true
+ }
+ 'Auto' {
+ $shouldDisableMultiprocess =
+ $deviceModel -eq 'F136A' -or
+ ($webViewInfo.MajorVersion -ne $null -and $webViewInfo.MajorVersion -le 101)
+ }
+}
+
+if ($shouldDisableMultiprocess -and $webViewInfo.MultiprocessEnabled) {
+ Write-Host "Disabling Android WebView multiprocess for compatibility..."
+ Set-WebViewMultiprocessState -AdbPath $adbPath -AdbArgsPrefix $adbArgsPrefix -Enabled:$false
+} elseif ($WebViewMultiprocess -eq 'Enable' -and -not $webViewInfo.MultiprocessEnabled) {
+ Write-Host "Re-enabling Android WebView multiprocess..."
+ Set-WebViewMultiprocessState -AdbPath $adbPath -AdbArgsPrefix $adbArgsPrefix -Enabled:$true
+}
+
+Write-Host "Force-stopping previous app process: $packageName"
+& $adbPath @adbArgsPrefix shell am force-stop $packageName | Out-Null
+
+Start-Sleep -Milliseconds 800
+
+Write-Host "Starting flutter run..."
+& flutter run @FlutterArgs
+exit $LASTEXITCODE
diff --git a/packages/web_shell_core/.gitignore b/packages/web_shell_core/.gitignore
new file mode 100644
index 0000000..b9d7f25
--- /dev/null
+++ b/packages/web_shell_core/.gitignore
@@ -0,0 +1,33 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# 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
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.flutter-plugins-dependencies
+/build/
+/coverage/
diff --git a/packages/web_shell_core/.metadata b/packages/web_shell_core/.metadata
new file mode 100644
index 0000000..d767a0e
--- /dev/null
+++ b/packages/web_shell_core/.metadata
@@ -0,0 +1,33 @@
+# 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: "90673a4eef275d1a6692c26ac80d6d746d41a73a"
+ channel: "stable"
+
+project_type: plugin
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
+ base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
+ - platform: android
+ create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
+ base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
+ - platform: ios
+ create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
+ base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
+
+ # 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/packages/web_shell_core/CHANGELOG.md b/packages/web_shell_core/CHANGELOG.md
new file mode 100644
index 0000000..93b39fc
--- /dev/null
+++ b/packages/web_shell_core/CHANGELOG.md
@@ -0,0 +1,16 @@
+# Changelog
+
+## 0.0.1
+
+### 新增
+- 从 `web_android_shell` 提取核心库,支持多品牌白标架构
+- `runShellApp()` 唯一入口 + `ShellEnvironment` 品牌配置
+- 15 个模块文件拆分(config / engine / bridge / services / ui)
+- Android WebView 兼容性自动检测(`AndroidWebViewInfo` + `AndroidCompatibilityPlan`)
+- 双渲染模式(texture / hybrid)自动切换 + 启动看门狗恢复链
+- `window.AppShell` JS Bridge 协议(8 种 Action)
+- 旧相机 JS 兼容层(`openCamera` / `captureImage` monkey-patch)
+- 媒体序列化支持 `base64` / `dataUrl` / `uri` 三种格式
+- 54 个单元测试 + Widget 测试
+- `CoreShellActivity` 原生层(进程隔离 + WebView 信息查询 + 深度重置)
+- 全中文 `debugPrint` 日志
diff --git a/packages/web_shell_core/LICENSE b/packages/web_shell_core/LICENSE
new file mode 100644
index 0000000..ba75c69
--- /dev/null
+++ b/packages/web_shell_core/LICENSE
@@ -0,0 +1 @@
+TODO: Add your license here.
diff --git a/packages/web_shell_core/README.md b/packages/web_shell_core/README.md
new file mode 100644
index 0000000..9910ce6
--- /dev/null
+++ b/packages/web_shell_core/README.md
@@ -0,0 +1,92 @@
+# web_shell_core
+
+Android 平板专用的 H5 壳核心库。所有品牌应用共享此库,只需传入 `ShellEnvironment` 即可启动。
+
+## 功能
+
+| 模块 | 说明 |
+|---|---|
+| **WebView 引擎** | 自动兼容低版本 Android WebView,支持 texture / hybrid 双渲染模式自动切换 |
+| **启动恢复** | 看门狗超时检测 → 渲染模式降级 → 深度清理 → 自动重试 |
+| **JS Bridge** | `window.AppShell` 协议,支持 8 种 Action(pickImage · captureImage · pickFile · openExternal · requestPermissions · reloadPage · goBack · closeApp) |
+| **旧相机兼容** | Monkey-patch `openCamera` / `captureImage` 兼容老 H5 页面 |
+| **媒体服务** | 相机拍照 · 图库选图 · 文件选择 · base64 / dataUrl / uri 三种序列化格式 |
+| **权限服务** | camera · microphone · location · photos · videos · storage 统一映射 |
+| **导航服务** | URL scheme 白名单路由,非 WebView 协议自动跳转外部应用 |
+| **壳层 UI** | 启动加载动画 · 错误恢复页 · 进度条 · 不支持平台兜底页 |
+
+## 使用方式
+
+```dart
+import 'package:web_shell_core/web_shell_core.dart';
+
+void main() {
+ runShellApp(
+ ShellEnvironment(
+ appName: '全学通',
+ appKey: 'quanxue_prod',
+ accentColor: Color(0xFF3ED37B),
+ backgroundColor: Color(0xFFFFFFFF),
+ textColor: Color(0xFF1F2937),
+ mutedTextColor: Color(0xFF6B7280),
+ initialUrl: 'example.com/login', // 可选,不传使用默认地址
+ ),
+ );
+}
+```
+
+## 代码结构
+
+```
+lib/
+├── core_app.dart # 库入口 + part 指令 + runShellApp()
+├── web_shell_core.dart # 公开 API 导出
+└── src/
+ ├── config/
+ │ ├── shell_environment.dart # 品牌配置数据类
+ │ └── url_resolver.dart # URL 解析与归一化
+ ├── engine/
+ │ ├── compatibility.dart # Android WebView 兼容检测
+ │ └── recovery.dart # 启动看门狗 + 错误映射
+ ├── bridge/
+ │ ├── bridge_protocol.dart # JS Bridge 注入与响应
+ │ ├── bridge_actions.dart # Action handler(占位)
+ │ └── legacy_camera_compat.dart # 旧相机 JS 兼容层
+ ├── services/
+ │ ├── media_service.dart # 相机/图库/文件 + 序列化
+ │ ├── permission_service.dart # 权限类型映射
+ │ └── navigation_service.dart # URL 路由 + 外链跳转
+ ├── ui/
+ │ ├── shell_app.dart # MaterialApp 入口
+ │ ├── shell_page.dart # WebView 主页面
+ │ ├── launch_overlay.dart # 启动加载动画
+ │ ├── error_overlay.dart # 错误恢复页
+ │ ├── progress_bar.dart # 顶部进度条
+ │ └── unsupported_platform_page.dart # 平台兜底页
+ └── testing/
+ └── test_hooks.dart # 测试钩子(@visibleForTesting)
+```
+
+## 测试
+
+```bash
+cd packages/web_shell_core
+flutter test
+```
+
+当前 **67 个测试用例**,覆盖:
+- 平台检测 · URL 解析 · 兼容性策略 · 错误映射
+- Bridge 注入/响应/异常处理 · 媒体序列化 · 权限映射
+- 导航路由 · 所有独立 UI 组件
+
+## 平台约束
+
+仅支持 **Android**。其他平台会展示兜底提示页。
+
+## Android 原生层
+
+`CoreShellActivity`(Java)提供:
+- WebView 数据目录隔离(避免多进程冲突)
+- 旧进程自动终止
+- WebView 信息查询(SDK 版本、WebView 包名/版本号)
+- WebView 状态深度重置
diff --git a/packages/web_shell_core/analysis_options.yaml b/packages/web_shell_core/analysis_options.yaml
new file mode 100644
index 0000000..71b1554
--- /dev/null
+++ b/packages/web_shell_core/analysis_options.yaml
@@ -0,0 +1,5 @@
+include: package:very_good_analysis/analysis_options.yaml
+
+analyzer:
+ exclude:
+ - coverage/**
diff --git a/packages/web_shell_core/android/.gitignore b/packages/web_shell_core/android/.gitignore
new file mode 100644
index 0000000..161bdcd
--- /dev/null
+++ b/packages/web_shell_core/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/packages/web_shell_core/android/build.gradle.kts b/packages/web_shell_core/android/build.gradle.kts
new file mode 100644
index 0000000..e80385f
--- /dev/null
+++ b/packages/web_shell_core/android/build.gradle.kts
@@ -0,0 +1,76 @@
+group = "com.yuanxuan.webshell.core.web_shell_core"
+version = "1.0-SNAPSHOT"
+
+buildscript {
+ val kotlinVersion = "2.2.20"
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath("com.android.tools.build:gradle:8.11.1")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+plugins {
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+android {
+ namespace = "com.yuanxuan.webshell.core.web_shell_core"
+
+ compileSdk = 36
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+
+ sourceSets {
+ getByName("main") {
+ java.srcDirs("src/main/kotlin")
+ }
+ getByName("test") {
+ java.srcDirs("src/test/kotlin")
+ }
+ }
+
+ defaultConfig {
+ minSdk = 24
+ }
+
+ testOptions {
+ unitTests {
+ isIncludeAndroidResources = true
+ all {
+ it.useJUnitPlatform()
+
+ it.outputs.upToDateWhen { false }
+
+ it.testLogging {
+ events("passed", "skipped", "failed", "standardOut", "standardError")
+ showStandardStreams = true
+ }
+ }
+ }
+ }
+}
+
+dependencies {
+ testImplementation("org.jetbrains.kotlin:kotlin-test")
+ testImplementation("org.mockito:mockito-core:5.0.0")
+}
diff --git a/packages/web_shell_core/android/settings.gradle b/packages/web_shell_core/android/settings.gradle
new file mode 100644
index 0000000..33eea3d
--- /dev/null
+++ b/packages/web_shell_core/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'web_shell_core'
diff --git a/packages/web_shell_core/android/settings.gradle.kts b/packages/web_shell_core/android/settings.gradle.kts
new file mode 100644
index 0000000..33eea3d
--- /dev/null
+++ b/packages/web_shell_core/android/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = 'web_shell_core'
diff --git a/packages/web_shell_core/android/src/main/AndroidManifest.xml b/packages/web_shell_core/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e9c4757
--- /dev/null
+++ b/packages/web_shell_core/android/src/main/AndroidManifest.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/web_shell_core/android/src/main/java/com/yuanxuan/webshell/core/web_shell_core/CoreShellActivity.java b/packages/web_shell_core/android/src/main/java/com/yuanxuan/webshell/core/web_shell_core/CoreShellActivity.java
new file mode 100644
index 0000000..2d09d58
--- /dev/null
+++ b/packages/web_shell_core/android/src/main/java/com/yuanxuan/webshell/core/web_shell_core/CoreShellActivity.java
@@ -0,0 +1,241 @@
+package com.yuanxuan.webshell.core.web_shell_core;
+
+import android.app.ActivityManager;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.webkit.CookieManager;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
+
+import androidx.annotation.NonNull;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
+import io.flutter.plugin.common.MethodChannel;
+
+public class CoreShellActivity extends FlutterActivity {
+ private static final String DEVICE_CHANNEL = "app_shell/device";
+ private static final String DEBUG_PROCESS_PREFS = "app_shell_debug_process";
+ private static final String DEBUG_PROCESS_PID_KEY = "pid";
+ private String webViewDataDirectorySuffix;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ terminatePreviousDebugProcessIfNeeded();
+ prepareWebViewDataDirectory();
+ super.onCreate(savedInstanceState);
+ registerCurrentDebugProcess();
+ }
+
+ @Override
+ protected void onDestroy() {
+ clearCurrentDebugProcessRegistration();
+ super.onDestroy();
+ }
+
+ @Override
+ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+ super.configureFlutterEngine(flutterEngine);
+ new MethodChannel(
+ flutterEngine.getDartExecutor().getBinaryMessenger(),
+ DEVICE_CHANNEL
+ ).setMethodCallHandler((call, result) -> {
+ switch (call.method) {
+ case "getAndroidWebViewInfo":
+ result.success(buildAndroidWebViewInfo());
+ break;
+ case "resetAndroidWebViewState":
+ resetAndroidWebViewState(result);
+ break;
+ default:
+ result.notImplemented();
+ break;
+ }
+ });
+ }
+
+ private Map buildAndroidWebViewInfo() {
+ final Map info = new HashMap<>();
+ info.put("sdkInt", Build.VERSION.SDK_INT);
+ info.put("manufacturer", Build.MANUFACTURER);
+ info.put("brand", Build.BRAND);
+ info.put("model", Build.MODEL);
+ if (webViewDataDirectorySuffix != null) {
+ info.put("webViewDataDirectorySuffix", webViewDataDirectorySuffix);
+ }
+
+ final PackageInfo currentWebViewPackage = getCurrentWebViewPackageCompat();
+ if (currentWebViewPackage != null) {
+ info.put("webViewPackageName", currentWebViewPackage.packageName);
+ if (currentWebViewPackage.versionName != null) {
+ info.put("webViewVersionName", currentWebViewPackage.versionName);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ info.put(
+ "webViewLongVersionCode",
+ currentWebViewPackage.getLongVersionCode()
+ );
+ } else {
+ info.put(
+ "webViewLongVersionCode",
+ (long) currentWebViewPackage.versionCode
+ );
+ }
+ }
+ return info;
+ }
+
+ private void prepareWebViewDataDirectory() {
+ webViewDataDirectorySuffix = null;
+ if (!isDebuggableBuild() || Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ return;
+ }
+
+ clearLegacyDebugWebViewData();
+ }
+
+ private void terminatePreviousDebugProcessIfNeeded() {
+ if (!isDebuggableBuild()) {
+ return;
+ }
+
+ final SharedPreferences preferences = getSharedPreferences(
+ DEBUG_PROCESS_PREFS,
+ MODE_PRIVATE
+ );
+ final int currentPid = Process.myPid();
+ final int previousPid = preferences.getInt(DEBUG_PROCESS_PID_KEY, -1);
+ if (previousPid <= 0 || previousPid == currentPid) {
+ return;
+ }
+
+ if (!isSamePackageProcessRunning(previousPid)) {
+ preferences.edit().remove(DEBUG_PROCESS_PID_KEY).apply();
+ return;
+ }
+
+ try {
+ Process.killProcess(previousPid);
+ SystemClock.sleep(180);
+ } catch (RuntimeException ignored) {
+ // 忽略终止旧进程失败的情况,并继续使用当前进程启动。
+ }
+ }
+
+ private void registerCurrentDebugProcess() {
+ if (!isDebuggableBuild()) {
+ return;
+ }
+
+ getSharedPreferences(DEBUG_PROCESS_PREFS, MODE_PRIVATE)
+ .edit()
+ .putInt(DEBUG_PROCESS_PID_KEY, Process.myPid())
+ .apply();
+ }
+
+ private void clearCurrentDebugProcessRegistration() {
+ if (!isDebuggableBuild()) {
+ return;
+ }
+
+ final SharedPreferences preferences = getSharedPreferences(
+ DEBUG_PROCESS_PREFS,
+ MODE_PRIVATE
+ );
+ if (preferences.getInt(DEBUG_PROCESS_PID_KEY, -1) != Process.myPid()) {
+ return;
+ }
+ preferences.edit().remove(DEBUG_PROCESS_PID_KEY).apply();
+ }
+
+ private boolean isSamePackageProcessRunning(int pid) {
+ final ActivityManager activityManager =
+ (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+ if (activityManager == null) {
+ return false;
+ }
+
+ for (final ActivityManager.RunningAppProcessInfo processInfo
+ : activityManager.getRunningAppProcesses()) {
+ if (processInfo.pid == pid) {
+ return getPackageName().equals(processInfo.processName);
+ }
+ }
+ return false;
+ }
+
+ private void clearLegacyDebugWebViewData() {
+ try {
+ final File appWebViewDir = new File(getApplicationInfo().dataDir, "app_webview");
+ deleteRecursively(appWebViewDir);
+ deleteDatabase("webview.db");
+ deleteDatabase("webviewCache.db");
+ } catch (RuntimeException ignored) {
+ // 忽略旧设备上的清理失败,继续执行启动流程。
+ }
+ }
+
+ private boolean isDebuggableBuild() {
+ return (getApplicationInfo().flags & android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ }
+
+ private void deleteRecursively(File target) {
+ if (target == null || !target.exists()) {
+ return;
+ }
+
+ if (target.isDirectory()) {
+ final File[] children = target.listFiles();
+ if (children != null) {
+ for (final File child : children) {
+ deleteRecursively(child);
+ }
+ }
+ }
+
+
+ target.delete();
+ }
+
+ private PackageInfo getCurrentWebViewPackageCompat() {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ return WebView.getCurrentWebViewPackage();
+ }
+ } catch (RuntimeException ignored) {
+ return null;
+ }
+ return null;
+ }
+
+ private void resetAndroidWebViewState(@NonNull MethodChannel.Result result) {
+ try {
+ WebStorage.getInstance().deleteAllData();
+
+ final CookieManager cookieManager = CookieManager.getInstance();
+ cookieManager.removeSessionCookies(null);
+ cookieManager.removeAllCookies(value -> {
+ try {
+ cookieManager.flush();
+ WebView.clearClientCertPreferences(() -> result.success(true));
+ } catch (RuntimeException error) {
+ result.error(
+ "webview_reset_failed",
+ error.toString(),
+ null
+ );
+ }
+ });
+ } catch (RuntimeException error) {
+ result.error("webview_reset_failed", error.toString(), null);
+ }
+ }
+}
diff --git a/packages/web_shell_core/android/src/main/kotlin/com/yuanxuan/webshell/core/web_shell_core/WebShellCorePlugin.kt b/packages/web_shell_core/android/src/main/kotlin/com/yuanxuan/webshell/core/web_shell_core/WebShellCorePlugin.kt
new file mode 100644
index 0000000..6d7289e
--- /dev/null
+++ b/packages/web_shell_core/android/src/main/kotlin/com/yuanxuan/webshell/core/web_shell_core/WebShellCorePlugin.kt
@@ -0,0 +1,38 @@
+package com.yuanxuan.webshell.core.web_shell_core
+
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.common.MethodChannel.Result
+
+/** WebShellCorePlugin 插件实现。 */
+class WebShellCorePlugin :
+ FlutterPlugin,
+ MethodCallHandler {
+ // 用于 Flutter 与原生 Android 通信的 MethodChannel。
+ //
+ // 这里保留本地引用,用于在 Flutter Engine 绑定时注册插件,
+ // 并在 Flutter Engine 分离时正确注销。
+ private lateinit var channel: MethodChannel
+
+ override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ channel = MethodChannel(flutterPluginBinding.binaryMessenger, "web_shell_core")
+ channel.setMethodCallHandler(this)
+ }
+
+ override fun onMethodCall(
+ call: MethodCall,
+ result: Result
+ ) {
+ if (call.method == "getPlatformVersion") {
+ result.success("Android ${android.os.Build.VERSION.RELEASE}")
+ } else {
+ result.notImplemented()
+ }
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ channel.setMethodCallHandler(null)
+ }
+}
diff --git a/packages/web_shell_core/android/src/test/kotlin/com/yuanxuan/webshell/core/web_shell_core/WebShellCorePluginTest.kt b/packages/web_shell_core/android/src/test/kotlin/com/yuanxuan/webshell/core/web_shell_core/WebShellCorePluginTest.kt
new file mode 100644
index 0000000..cede880
--- /dev/null
+++ b/packages/web_shell_core/android/src/test/kotlin/com/yuanxuan/webshell/core/web_shell_core/WebShellCorePluginTest.kt
@@ -0,0 +1,26 @@
+package com.yuanxuan.webshell.core.web_shell_core
+
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import org.mockito.Mockito
+import kotlin.test.Test
+
+/*
+ * 这是一个简单的单元测试示例,用于验证插件 Kotlin 实现中的基础逻辑。
+ *
+ * 你可以在对应 Android 工程目录下执行 `./gradlew testDebugUnitTest` 运行,
+ * 也可以直接在支持 JUnit 的 IDE 中运行。
+ */
+
+internal class WebShellCorePluginTest {
+ @Test
+ fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
+ val plugin = WebShellCorePlugin()
+
+ val call = MethodCall("getPlatformVersion", null)
+ val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
+ plugin.onMethodCall(call, mockResult)
+
+ Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
+ }
+}
diff --git a/packages/web_shell_core/lib/core_app.dart b/packages/web_shell_core/lib/core_app.dart
new file mode 100644
index 0000000..ad081aa
--- /dev/null
+++ b/packages/web_shell_core/lib/core_app.dart
@@ -0,0 +1,93 @@
+/// WebShell 核心库:为 H5 壳应用提供 WebView 引擎、原生桥接与平台服务。
+library;
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:file_picker/file_picker.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter/services.dart';
+import 'package:image_picker/image_picker.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:url_launcher/url_launcher.dart';
+import 'package:webview_flutter/webview_flutter.dart';
+import 'package:webview_flutter_android/webview_flutter_android.dart';
+
+// ── 配置 ──
+part 'src/config/shell_environment.dart';
+part 'src/config/url_resolver.dart';
+
+// ── 引擎 ──
+part 'src/engine/compatibility.dart';
+part 'src/engine/recovery.dart';
+
+// ── 桥接 ──
+part 'src/bridge/bridge_protocol.dart';
+part 'src/bridge/bridge_actions.dart';
+part 'src/bridge/legacy_camera_compat.dart';
+
+// ── 服务 ──
+part 'src/services/media_service.dart';
+part 'src/services/permission_service.dart';
+part 'src/services/navigation_service.dart';
+
+// ── 界面 ──
+part 'src/ui/shell_app.dart';
+part 'src/ui/shell_page.dart';
+part 'src/ui/launch_overlay.dart';
+part 'src/ui/error_overlay.dart';
+part 'src/ui/progress_bar.dart';
+part 'src/ui/unsupported_platform_page.dart';
+part 'src/testing/test_hooks.dart';
+
+// ── 全局环境 ──
+late ShellEnvironment _env;
+
+Color get _shellAccentColor => _env.accentColor;
+Color get _shellBackgroundColor => _env.backgroundColor;
+Color get _shellTextColor => _env.textColor;
+Color get _shellMutedTextColor => _env.mutedTextColor;
+
+// ── 入口 ──
+
+/// 启动壳应用的唯一入口。
+/// 各品牌应用的 `main.dart` 调用此函数,并传入自己的 [ShellEnvironment]。
+Future runShellApp(ShellEnvironment environment) async {
+ _env = environment;
+ _initializeUrls();
+ WidgetsFlutterBinding.ensureInitialized();
+ await SystemChrome.setPreferredOrientations(const [
+ DeviceOrientation.portraitUp,
+ DeviceOrientation.portraitDown,
+ ]);
+ await _enterImmersiveMode();
+ runApp(const ShellApp());
+}
+
+Future _enterImmersiveMode() async {
+ await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
+ SystemChrome.setSystemUIOverlayStyle(
+ const SystemUiOverlayStyle(
+ statusBarColor: Colors.transparent,
+ statusBarIconBrightness: Brightness.light,
+ statusBarBrightness: Brightness.dark,
+ systemNavigationBarColor: Colors.black,
+ systemNavigationBarDividerColor: Colors.black,
+ systemNavigationBarIconBrightness: Brightness.light,
+ ),
+ );
+}
+
+bool _supportsEmbeddedWebView({
+ bool isWeb = kIsWeb,
+ TargetPlatform? platform,
+}) {
+ if (isWeb) {
+ return false;
+ }
+
+ final target = platform ?? defaultTargetPlatform;
+ return target == TargetPlatform.android;
+}
diff --git a/packages/web_shell_core/lib/src/bridge/bridge_actions.dart b/packages/web_shell_core/lib/src/bridge/bridge_actions.dart
new file mode 100644
index 0000000..c6459df
--- /dev/null
+++ b/packages/web_shell_core/lib/src/bridge/bridge_actions.dart
@@ -0,0 +1,5 @@
+part of '../../core_app.dart';
+
+// 当前无需在 `bridge_actions.dart` 中编写独立代码 ——
+// 桥接动作处理逻辑直接内嵌在 `shell_page.dart` 的 `_handleBridgeMessage` 方法中。
+// 此文件预留给后续将动作处理器拆分出去时使用。
diff --git a/packages/web_shell_core/lib/src/bridge/bridge_protocol.dart b/packages/web_shell_core/lib/src/bridge/bridge_protocol.dart
new file mode 100644
index 0000000..9a098a3
--- /dev/null
+++ b/packages/web_shell_core/lib/src/bridge/bridge_protocol.dart
@@ -0,0 +1,108 @@
+part of '../../core_app.dart';
+
+const String _appShellBridgeChannel = 'AppShellBridge';
+const String _appShellBridgeVersion = '1.0.0';
+
+Future _injectAppShellBridge(
+ WebViewController controller,
+ String url,
+) async {
+ try {
+ await controller.runJavaScript(_buildAppShellBridgeScript());
+ debugPrint('已为 $url 注入 AppShell 桥接');
+ } on Object catch (error, stackTrace) {
+ debugPrint('注入 AppShell 桥接失败:$error\n$stackTrace');
+ }
+}
+
+Future _sendBridgeResponse(
+ WebViewController controller, {
+ required String requestId,
+ required bool success,
+ Object? data,
+ String? error,
+}) async {
+ final response = {
+ 'requestId': requestId,
+ 'success': success,
+ 'data': data,
+ 'error': error,
+ };
+
+ try {
+ await controller.runJavaScript(
+ 'window.__appShellReceiveResponse(${jsonEncode(response)});',
+ );
+ } on Object catch (bridgeError, stackTrace) {
+ debugPrint(
+ '发送 AppShell 桥接响应失败:$bridgeError\n$stackTrace',
+ );
+ }
+}
+
+String _buildAppShellBridgeScript() {
+ return '''
+(() => {
+ const channel = window.$_appShellBridgeChannel;
+ if (!channel || typeof channel.postMessage !== 'function') {
+ return;
+ }
+
+ if (window.AppShell && window.AppShell.__nativeShellVersion === '$_appShellBridgeVersion') {
+ return;
+ }
+
+ const pending = new Map();
+
+ window.__appShellReceiveResponse = function(response) {
+ if (!response || !response.requestId) {
+ return;
+ }
+
+ const task = pending.get(response.requestId);
+ if (!task) {
+ return;
+ }
+
+ pending.delete(response.requestId);
+
+ if (response.success) {
+ task.resolve(response.data ?? null);
+ return;
+ }
+
+ task.reject(new Error(response.error || 'Native request failed'));
+ };
+
+ const send = (action, payload = {}) => new Promise((resolve, reject) => {
+ const requestId =
+ Date.now().toString(36) + Math.random().toString(36).slice(2);
+ pending.set(requestId, { resolve, reject });
+ channel.postMessage(JSON.stringify({ requestId, action, payload }));
+ });
+
+ window.AppShell = {
+ __nativeShellVersion: '$_appShellBridgeVersion',
+ isNativeShell: true,
+ version: '$_appShellBridgeVersion',
+ pickImage: (options = {}) => send('pickImage', options),
+ captureImage: (options = {}) => send('captureImage', options),
+ pickFile: (options = {}) => send('pickFile', options),
+ openExternal: (url) => send('openExternal', { url }),
+ requestPermissions: (types = []) => send('requestPermissions', { types }),
+ reloadPage: () => send('reloadPage'),
+ goBack: () => send('goBack'),
+ closeApp: () => send('closeApp'),
+ };
+
+ window.dispatchEvent(new CustomEvent('app-shell-ready', {
+ detail: {
+ version: '$_appShellBridgeVersion',
+ isNativeShell: true,
+ },
+ }));
+
+$_legacyCameraCompatScript
+})();
+''';
+}
diff --git a/packages/web_shell_core/lib/src/bridge/legacy_camera_compat.dart b/packages/web_shell_core/lib/src/bridge/legacy_camera_compat.dart
new file mode 100644
index 0000000..5def953
--- /dev/null
+++ b/packages/web_shell_core/lib/src/bridge/legacy_camera_compat.dart
@@ -0,0 +1,132 @@
+part of '../../core_app.dart';
+
+// 旧版 H5 相机兼容层 JavaScript 代码
+// 通过替换 `window.openCamera` / `capturePhoto` 的方式兼容老页面。
+
+const String _legacyCameraCompatScript = '''
+ if (!window.__appShellLegacyCameraCompatInstalled) {
+ window.__appShellLegacyCameraCompatInstalled = true;
+
+ const installLegacyCameraCompat = () => {
+ if (typeof window.openCamera !== 'function') {
+ return false;
+ }
+ if (window.openCamera.__appShellCompatWrapped) {
+ return true;
+ }
+
+ const originalOpenCamera = window.openCamera.bind(window);
+ const originalCapturePhoto =
+ typeof window.capturePhoto === 'function'
+ ? window.capturePhoto.bind(window)
+ : null;
+
+ const deliverCaptureResult = (args, result) => {
+ const detail = {
+ args: Array.from(args),
+ result,
+ };
+
+ if (typeof window.handleNativeCapture === 'function') {
+ window.handleNativeCapture(detail);
+ return true;
+ }
+
+ const targetId =
+ detail.args[0] ??
+ window.currentUploadId ??
+ null;
+
+ if (
+ targetId != null &&
+ result &&
+ result.dataUrl &&
+ typeof window.addPreview === 'function'
+ ) {
+ window.currentUploadId = targetId;
+ window.addPreview(targetId, result.dataUrl);
+ return true;
+ }
+
+ window.dispatchEvent(
+ new CustomEvent('app-shell-image-captured', {
+ detail,
+ }),
+ );
+ return false;
+ };
+
+ const closeLegacyCameraModal = () => {
+ if (typeof window.closeCamera === 'function') {
+ try {
+ window.closeCamera();
+ } catch (_) {}
+ }
+ };
+
+ const wrappedOpenCamera = async function(...args) {
+ try {
+ const result = await window.AppShell.captureImage({
+ responseType: 'dataUrl',
+ });
+ const delivered = deliverCaptureResult(args, result);
+ if (!delivered) {
+ return result;
+ }
+ closeLegacyCameraModal();
+ return result;
+ } catch (error) {
+ console.warn(
+ '[AppShell] legacy openCamera fallback to page implementation',
+ error,
+ );
+ return originalOpenCamera(...args);
+ }
+ };
+
+ wrappedOpenCamera.__appShellCompatWrapped = true;
+ window.openCamera = wrappedOpenCamera;
+
+ if (originalCapturePhoto) {
+ const wrappedCapturePhoto = function(...args) {
+ if (
+ window.currentUploadId != null &&
+ window.__lastAppShellCaptureResult &&
+ window.__lastAppShellCaptureResult.dataUrl &&
+ typeof window.addPreview === 'function'
+ ) {
+ window.addPreview(
+ window.currentUploadId,
+ window.__lastAppShellCaptureResult.dataUrl,
+ );
+ closeLegacyCameraModal();
+ return;
+ }
+ return originalCapturePhoto(...args);
+ };
+ wrappedCapturePhoto.__appShellCompatWrapped = true;
+ window.capturePhoto = wrappedCapturePhoto;
+ }
+
+ return true;
+ };
+
+ const originalCaptureImage = window.AppShell.captureImage;
+ window.AppShell.captureImage = async function(options = {}) {
+ const result = await originalCaptureImage(options);
+ window.__lastAppShellCaptureResult = result;
+ return result;
+ };
+
+ installLegacyCameraCompat();
+
+ let attempts = 0;
+ const compatTimer = setInterval(() => {
+ attempts += 1;
+ const installed = installLegacyCameraCompat();
+ if (installed || attempts >= 20) {
+ clearInterval(compatTimer);
+ }
+ }, 500);
+ }
+''';
diff --git a/packages/web_shell_core/lib/src/config/shell_environment.dart b/packages/web_shell_core/lib/src/config/shell_environment.dart
new file mode 100644
index 0000000..e3fbd81
--- /dev/null
+++ b/packages/web_shell_core/lib/src/config/shell_environment.dart
@@ -0,0 +1,37 @@
+part of '../../core_app.dart';
+
+/// 壳应用的品牌环境配置。
+/// 每个白标应用都在 `main.dart` 中传入自己的 [ShellEnvironment] 实例。
+class ShellEnvironment {
+ /// 创建 Android 平板壳应用的环境配置。
+ const ShellEnvironment({
+ required this.appName,
+ required this.appKey,
+ required this.accentColor,
+ required this.backgroundColor,
+ required this.textColor,
+ required this.mutedTextColor,
+ this.initialUrl,
+ });
+
+ /// 应用显示名称(如 "全学通"、"点智学")
+ final String appName;
+
+ /// 应用唯一标识符(用于后端区分品牌)
+ final String appKey;
+
+ /// 品牌主题强调色
+ final Color accentColor;
+
+ /// 品牌背景色
+ final Color backgroundColor;
+
+ /// 品牌正文文字色
+ final Color textColor;
+
+ /// 品牌次要文字色
+ final Color mutedTextColor;
+
+ /// 可选的初始地址;为空时使用默认地址。
+ final String? initialUrl;
+}
diff --git a/packages/web_shell_core/lib/src/config/url_resolver.dart b/packages/web_shell_core/lib/src/config/url_resolver.dart
new file mode 100644
index 0000000..df89f8c
--- /dev/null
+++ b/packages/web_shell_core/lib/src/config/url_resolver.dart
@@ -0,0 +1,66 @@
+part of '../../core_app.dart';
+
+const String _defaultInitialUrl = 'http://xszy.lzzneng.com/login.html';
+
+late Uri _initialUri;
+late String _initialUrl;
+
+void _initializeUrls() {
+ final candidate = (_env.initialUrl ?? '').trim();
+ if (candidate.isEmpty) {
+ _initialUri = Uri.parse(_defaultInitialUrl);
+ } else {
+ final directUri = Uri.tryParse(candidate);
+ if (directUri != null && directUri.hasScheme) {
+ _initialUri = directUri;
+ } else if (candidate.startsWith('//')) {
+ final protocolRelativeUri = Uri.tryParse('https:$candidate');
+ if (protocolRelativeUri != null && protocolRelativeUri.hasScheme) {
+ _initialUri = protocolRelativeUri;
+ }
+ } else {
+ final httpsUri = Uri.tryParse('https://$candidate');
+ if (httpsUri != null && httpsUri.hasScheme && httpsUri.host.isNotEmpty) {
+ _initialUri = httpsUri;
+ } else {
+ _initialUri = Uri.parse(_defaultInitialUrl);
+ }
+ }
+ }
+ _initialUrl = _initialUri.toString();
+}
+
+bool _isNetworkUrl(String? url) {
+ final uri = Uri.tryParse(url ?? '');
+ if (uri == null) {
+ return false;
+ }
+ return uri.scheme == 'http' || uri.scheme == 'https';
+}
+
+String _normalizeComparableUri(Uri uri) {
+ final scheme = uri.scheme.toLowerCase();
+ final host = uri.host.toLowerCase();
+ final port = uri.hasPort ? uri.port : _defaultPortForScheme(scheme);
+ final path = _normalizeComparablePath(uri.path);
+ final query = uri.query;
+ return '$scheme://$host:$port$path?$query';
+}
+
+int _defaultPortForScheme(String scheme) {
+ return switch (scheme) {
+ 'https' => 443,
+ 'http' => 80,
+ _ => -1,
+ };
+}
+
+String _normalizeComparablePath(String path) {
+ if (path.isEmpty) {
+ return '/';
+ }
+ if (path.length > 1 && path.endsWith('/')) {
+ return path.substring(0, path.length - 1);
+ }
+ return path;
+}
diff --git a/packages/web_shell_core/lib/src/engine/compatibility.dart b/packages/web_shell_core/lib/src/engine/compatibility.dart
new file mode 100644
index 0000000..4025866
--- /dev/null
+++ b/packages/web_shell_core/lib/src/engine/compatibility.dart
@@ -0,0 +1,226 @@
+part of '../../core_app.dart';
+
+/// Android WebView 的渲染模式。
+enum AndroidRenderMode {
+ /// 使用纹理层渲染模式。
+ texture,
+
+ /// 使用混合合成渲染模式。
+ hybrid
+ ;
+
+ /// 当前模式是否使用混合合成。
+ bool get usesHybridComposition => this == AndroidRenderMode.hybrid;
+
+ /// 返回当前渲染模式对应的稳定日志标识。
+ String get logName => switch (this) {
+ AndroidRenderMode.texture => 'texture-layer',
+ AndroidRenderMode.hybrid => 'hybrid-composition',
+ };
+
+ /// 返回当前渲染模式面向用户的显示名称。
+ String get displayName => switch (this) {
+ AndroidRenderMode.texture => '标准模式',
+ AndroidRenderMode.hybrid => '兼容模式',
+ };
+}
+
+/// Android 设备及系统 WebView 的运行信息。
+class AndroidWebViewInfo {
+ /// 创建 Android 设备与 WebView 元数据快照。
+ AndroidWebViewInfo({
+ required this.sdkInt,
+ required this.manufacturer,
+ required this.brand,
+ required this.model,
+ this.webViewDataDirectorySuffix,
+ this.webViewPackageName,
+ this.webViewVersionName,
+ this.webViewLongVersionCode,
+ }) : webViewMajorVersion = _parseWebViewMajorVersion(webViewVersionName);
+
+ /// 根据平台通道返回的原始数据创建实例。
+ factory AndroidWebViewInfo.fromMap(Map