feat: 修复 generate_app.dart 品牌资源生成流程
- 重写 generate_app.dart:添加品牌名校验、--android-language java、 自动安装 flutter_launcher_icons/flutter_native_splash dev 依赖、 所有 Process.run 结果检查 exitCode - 更新 quanxue.yaml:添加 branding 配置段 - 新增 flavors/quanxue/ 品牌资源目录(icon、foreground、splash) - 重新生成 apps/quanxue(含品牌图标和启动页) - 更新 README.md:品牌配置示例、资源规格说明、生成步骤 - 更新 web_shell_core/README.md:测试数量 54 → 67
38
README.md
|
|
@ -11,8 +11,12 @@ web_android_shell/
|
|||
├── packages/
|
||||
│ ├── web_shell_core/ # 核心库(WebView 引擎 + Bridge + 服务)
|
||||
│ └── web_android_shell/ # 旧版入口(已迁移至 apps/quanxue)
|
||||
├── flavors/ # 品牌配置 YAML
|
||||
│ └── quanxue.yaml
|
||||
├── flavors/ # 品牌配置 + 品牌资源
|
||||
│ ├── quanxue.yaml # 品牌配置
|
||||
│ └── quanxue/ # 品牌资源(图标、启动页)
|
||||
│ ├── icon.png
|
||||
│ ├── icon_foreground.png
|
||||
│ └── splash.png
|
||||
├── tool/
|
||||
│ ├── generate_app.dart # 一键生成新品牌应用
|
||||
│ └── flutter_run_fresh.ps1 # Windows 调试脚本(自动杀旧进程)
|
||||
|
|
@ -33,8 +37,13 @@ flutter run
|
|||
```bash
|
||||
# 1) 在 flavors/ 下创建品牌配置
|
||||
cp flavors/quanxue.yaml flavors/新品牌.yaml
|
||||
# 2) 修改配置中的 app_name, application_id, app_key, theme
|
||||
# 3) 运行生成脚本
|
||||
# 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 新品牌
|
||||
```
|
||||
|
||||
|
|
@ -42,8 +51,11 @@ dart run tool/generate_app.dart 新品牌
|
|||
- 创建 Flutter 应用 → `apps/新品牌/`
|
||||
- 添加 `web_shell_core` 依赖
|
||||
- 覆写 `MainActivity` 继承 `CoreShellActivity`
|
||||
- 更新 `AndroidManifest.xml` 应用名
|
||||
- 生成品牌入口 `main.dart`
|
||||
- 配置 `flutter_launcher_icons`
|
||||
- 复制品牌资源到生成目录
|
||||
- 添加 `flutter_launcher_icons` / `flutter_native_splash` 依赖
|
||||
- 自动生成应用图标和启动页
|
||||
|
||||
### 3. 品牌配置格式
|
||||
|
||||
|
|
@ -56,8 +68,24 @@ theme:
|
|||
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` 后,旧进程可能留在后台影响 WebView 启动。
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ migration:
|
|||
- platform: android
|
||||
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||
- platform: ios
|
||||
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||
|
||||
# User provided section
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +1,17 @@
|
|||
# 全学通 (quanxue)
|
||||
# quanxue
|
||||
|
||||
「全学通」品牌的 Android 平板 H5 壳应用。
|
||||
A new Flutter project.
|
||||
|
||||
## 概述
|
||||
## Getting Started
|
||||
|
||||
本应用是 `web_shell_core` 核心库的品牌实例。`main.dart` 仅 16 行代码,只负责传入品牌配置,所有业务逻辑由核心库提供。
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
## 运行
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
```bash
|
||||
flutter run
|
||||
```
|
||||
- [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)
|
||||
|
||||
## 配置
|
||||
|
||||
| 配置项 | 值 |
|
||||
|---|---|
|
||||
| 应用名 | 全学通 |
|
||||
| 包名 | `com.wanmake.quanxue` |
|
||||
| 业务标识 | `quanxue_prod` |
|
||||
| 主题色 | `#3ED37B` |
|
||||
|
||||
品牌配置源文件:[`flavors/quanxue.yaml`](../../flavors/quanxue.yaml)
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
flutter build apk --release
|
||||
```
|
||||
|
||||
## 依赖
|
||||
|
||||
- `web_shell_core`(本地 path 引用)
|
||||
- `cupertino_icons`(兼容历史代码)
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,28 @@
|
|||
# Dart 分析器配置,用于在开发阶段发现错误、警告和代码规范问题。
|
||||
# 可通过 `flutter analyze` 执行静态检查。
|
||||
# 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:
|
||||
# 如需自定义规则,可在此处开启或关闭指定 lint。
|
||||
# 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 # 取消注释后可关闭 `avoid_print` 规则。
|
||||
# prefer_single_quotes: true # 取消注释后可开启 `prefer_single_quotes` 规则。
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// Flutter Gradle 插件必须放在 Android 与 Kotlin Gradle 插件之后应用。
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
|
|
@ -20,10 +20,10 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
// 待补充:请替换成你自己的唯一应用标识。
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.wanmake.quanxue"
|
||||
// 下面这些值可以按应用实际需求调整。
|
||||
// 更多说明可参考:https://flutter.dev/to/review-gradle-config。
|
||||
// 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
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
|
|
@ -32,8 +32,8 @@ android {
|
|||
|
||||
buildTypes {
|
||||
release {
|
||||
// 待补充:为 release 构建配置正式签名。
|
||||
// 当前先使用 debug 签名,确保 `flutter run --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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 69 B |
|
|
@ -1,12 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 69 B |
|
|
@ -1,12 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_launcher_foreground"
|
||||
android:inset="16%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 20 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">#FFFFFF</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -5,6 +5,10 @@
|
|||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">#FFFFFF</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
|
|
@ -5,6 +5,10 @@
|
|||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -1,6 +1,22 @@
|
|||
# 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:
|
||||
|
|
@ -33,6 +49,22 @@ packages:
|
|||
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:
|
||||
|
|
@ -57,6 +89,14 @@ packages:
|
|||
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:
|
||||
|
|
@ -134,6 +174,14 @@ packages:
|
|||
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:
|
||||
|
|
@ -142,6 +190,14 @@ packages:
|
|||
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:
|
||||
|
|
@ -160,6 +216,14 @@ packages:
|
|||
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:
|
||||
|
|
@ -176,6 +240,14 @@ packages:
|
|||
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:
|
||||
|
|
@ -240,6 +312,14 @@ packages:
|
|||
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:
|
||||
|
|
@ -376,6 +456,14 @@ packages:
|
|||
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
|
||||
|
|
@ -437,6 +525,14 @@ packages:
|
|||
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:
|
||||
|
|
@ -560,10 +656,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: "2df8fd9ada04d699b9db8e79aa783a16e5d89b69e5b74009b87e16b59912cf98"
|
||||
sha256: "0d85e8bc5db9a7c49f6ff57cbeafc6cd8216ad9c9ebc70b2c4579d955698933a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.24.0"
|
||||
version: "3.24.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -580,6 +676,14 @@ packages:
|
|||
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"
|
||||
|
|
|
|||
|
|
@ -1,20 +1,38 @@
|
|||
name: quanxue
|
||||
description: "全学通 — Android 平板 H5 壳应用。"
|
||||
# 阻止误发布到 pub.dev。
|
||||
publish_to: 'none'
|
||||
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
|
||||
|
||||
# Android 应用版本号:`build-name` 对应 `versionName`,`build-number` 对应 `versionCode`。
|
||||
# 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
|
||||
|
||||
# Material 图标已覆盖主要场景;此依赖保留以兼容历史代码。
|
||||
# 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
|
||||
|
|
@ -23,9 +41,53 @@ dev_dependencies:
|
|||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# 推荐的 Flutter lint 规则集合。
|
||||
# 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:
|
||||
# 启用 Material 图标字体。
|
||||
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
// 这是一个基础的 Flutter 组件测试示例。
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// 你可以通过 `WidgetTester` 与组件交互,例如点击、滚动,
|
||||
// 也可以查找组件文本并验证组件属性是否符合预期。
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
|
@ -10,18 +12,18 @@ import 'package:quanxue/main.dart';
|
|||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// 构建应用并触发首帧渲染。
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// 验证计数器初始值为 0。
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// 点击加号按钮并重新渲染。
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// 验证计数器已经加 1。
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,3 +6,9 @@ theme:
|
|||
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"
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -201,7 +201,7 @@ public class MainActivity extends FlutterActivity {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
target.delete();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ cd packages/web_shell_core
|
|||
flutter test
|
||||
```
|
||||
|
||||
当前 **54 个测试用例**,覆盖:
|
||||
当前 **67 个测试用例**,覆盖:
|
||||
- 平台检测 · URL 解析 · 兼容性策略 · 错误映射
|
||||
- Bridge 注入/响应/异常处理 · 媒体序列化 · 权限映射
|
||||
- 导航路由 · 所有独立 UI 组件
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ public class CoreShellActivity extends FlutterActivity {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
target.delete();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// coverage:ignore-file
|
||||
part of '../../core_app.dart';
|
||||
|
||||
const MethodChannel _appShellDeviceChannel = MethodChannel('app_shell/device');
|
||||
|
|
@ -12,6 +11,7 @@ class WebShellPage extends StatefulWidget {
|
|||
State<WebShellPage> createState() => _WebShellPageState();
|
||||
}
|
||||
|
||||
// coverage:ignore-start
|
||||
class _WebShellPageState extends State<WebShellPage>
|
||||
with WidgetsBindingObserver {
|
||||
final ImagePicker _imagePicker = ImagePicker();
|
||||
|
|
@ -1058,3 +1058,4 @@ class _WebShellPageState extends State<WebShellPage>
|
|||
);
|
||||
}
|
||||
}
|
||||
// coverage:ignore-end
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export 'core_app.dart';
|
||||
export 'core_app.dart' hide ShellCoreTestHooks, shellCoreTestHooks;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface.
|
|||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:url_launcher_platform_interface/link.dart';
|
||||
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
|
||||
import 'package:web_shell_core/web_shell_core.dart';
|
||||
import 'package:web_shell_core/core_app.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:io';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
/// 品牌名允许的字符:小写字母、数字、下划线。
|
||||
final RegExp _validBrandName = RegExp(r'^[a-z][a-z0-9_]*$');
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
if (args.isEmpty) {
|
||||
print(
|
||||
|
|
@ -11,6 +14,15 @@ Future<void> main(List<String> args) async {
|
|||
}
|
||||
|
||||
final String brand = args.first;
|
||||
|
||||
if (!_validBrandName.hasMatch(brand)) {
|
||||
print(
|
||||
'\x1B[31m[错误] 品牌名 "$brand" 格式无效,'
|
||||
'仅允许小写字母、数字和下划线(且必须以字母开头)。\x1B[0m',
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
final File configFile = File('flavors/$brand.yaml');
|
||||
|
||||
if (!configFile.existsSync()) {
|
||||
|
|
@ -98,6 +110,8 @@ Future<void> _createFlutterApp(
|
|||
brand.replaceAll('-', '_'),
|
||||
'--platforms',
|
||||
'android',
|
||||
'--android-language',
|
||||
'java',
|
||||
appDir,
|
||||
]);
|
||||
|
||||
|
|
@ -129,23 +143,6 @@ Future<void> _overwriteMainActivity(String appDir, String applicationId) async {
|
|||
print('\x1B[34m[信息] 正在注入 CoreShellActivity 继承关系...\x1B[0m');
|
||||
final String mainActivityPath =
|
||||
"$appDir/android/app/src/main/java/${applicationId.replaceAll('.', '/')}/MainActivity.java";
|
||||
final File mainActivityFile = File(mainActivityPath);
|
||||
|
||||
if (!mainActivityFile.existsSync()) {
|
||||
print(
|
||||
'\x1B[31m[错误] 未找到 MainActivity.java:${mainActivityFile.path}\x1B[0m',
|
||||
);
|
||||
// Flutter 可能会按配置生成 Kotlin,这里同时兼容两种情况。
|
||||
final String ktPath =
|
||||
"$appDir/android/app/src/main/kotlin/${applicationId.replaceAll('.', '/')}/MainActivity.kt";
|
||||
final File ktFile = File(ktPath);
|
||||
if (ktFile.existsSync()) {
|
||||
ktFile.deleteSync(); // 这里直接重新生成 Java 版本。
|
||||
} else {
|
||||
print('\x1B[31m[错误] Kotlin 版本也未找到。\x1B[0m');
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 准备 Java 文件内容
|
||||
final String javaContent =
|
||||
|
|
@ -226,63 +223,111 @@ Future<void> _generateBrandingAssets(
|
|||
}
|
||||
final YamlMap branding = config['branding'] as YamlMap;
|
||||
|
||||
// 在 appDir 下创建 flutter_launcher_icons.yaml
|
||||
final String iconsYaml =
|
||||
'''
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
image_path: "${branding['icon']}"
|
||||
adaptive_icon_background: "${branding['icon_background']}"
|
||||
adaptive_icon_foreground: "${branding['icon_foreground']}"
|
||||
''';
|
||||
await File(
|
||||
'$appDir/flutter_launcher_icons-config.yaml',
|
||||
).writeAsString(iconsYaml);
|
||||
|
||||
// 在 appDir 下创建 flutter_native_splash.yaml
|
||||
final String splashYaml =
|
||||
'''
|
||||
flutter_native_splash:
|
||||
color: "${branding['splash_color']}"
|
||||
image: "${branding['splash']}"
|
||||
android_12:
|
||||
image: "${branding['splash']}"
|
||||
icon_background_color: "${branding['splash_color']}"
|
||||
''';
|
||||
await File(
|
||||
'$appDir/flutter_native_splash-config.yaml',
|
||||
).writeAsString(splashYaml);
|
||||
|
||||
// 复制品牌资源文件
|
||||
print('\x1B[34m[信息] 正在复制资源...\x1B[0m');
|
||||
final Directory brandingDir = Directory('assets/branding/$brand');
|
||||
if (brandingDir.existsSync()) {
|
||||
final Directory targetDir = Directory('$appDir/assets/branding/$brand');
|
||||
await targetDir.create(recursive: true);
|
||||
for (final entity in brandingDir.listSync(recursive: true)) {
|
||||
if (entity is File) {
|
||||
final relativePath = entity.path.substring(brandingDir.path.length + 1);
|
||||
final targetFile = File('${targetDir.path}/$relativePath');
|
||||
await targetFile.parent.create(recursive: true);
|
||||
await entity.copy(targetFile.path);
|
||||
}
|
||||
}
|
||||
// ── 1. 复制品牌资源到生成的应用目录 ──
|
||||
final Directory brandSourceDir = Directory('flavors/$brand');
|
||||
final Directory brandTargetDir = Directory('$appDir/assets/branding');
|
||||
if (!brandSourceDir.existsSync()) {
|
||||
print(
|
||||
'\x1B[31m[错误] 品牌资源目录不存在:${brandSourceDir.path}\x1B[0m',
|
||||
);
|
||||
print(
|
||||
'\x1B[33m请在 flavors/$brand/ 目录下放置 icon.png、'
|
||||
'icon_foreground.png、splash.png 等资源文件。\x1B[0m',
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
print('\x1B[34m[信息] 正在执行资源生成器...\x1B[0m');
|
||||
await brandTargetDir.create(recursive: true);
|
||||
for (final entity in brandSourceDir.listSync(recursive: true)) {
|
||||
if (entity is File) {
|
||||
final relativePath = entity.path.substring(
|
||||
brandSourceDir.path.length + 1,
|
||||
);
|
||||
final targetFile = File('${brandTargetDir.path}/$relativePath');
|
||||
await targetFile.parent.create(recursive: true);
|
||||
await entity.copy(targetFile.path);
|
||||
}
|
||||
}
|
||||
print('\x1B[32m✔ 品牌资源已复制到 ${brandTargetDir.path}\x1B[0m');
|
||||
|
||||
// 执行 flutter_launcher_icons
|
||||
await Process.run('dart', [
|
||||
// ── 2. 添加资源生成器的 dev 依赖 ──
|
||||
print('\x1B[34m[信息] 正在添加资源生成器依赖...\x1B[0m');
|
||||
final ProcessResult addDevDepsResult = await Process.run('flutter', [
|
||||
'pub',
|
||||
'add',
|
||||
'--dev',
|
||||
'flutter_launcher_icons',
|
||||
'flutter_native_splash',
|
||||
], workingDirectory: appDir);
|
||||
|
||||
if (addDevDepsResult.exitCode != 0) {
|
||||
print(
|
||||
'\x1B[31m[错误] 添加资源生成器依赖失败:'
|
||||
'\n${addDevDepsResult.stderr}\x1B[0m',
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// ── 3. 生成 flutter_launcher_icons 配置 ──
|
||||
// 资源路径指向复制后的位置(相对于 appDir)
|
||||
final String iconPath = 'assets/branding/${branding['icon']}';
|
||||
final String iconForeground = 'assets/branding/${branding['icon_foreground']}';
|
||||
final String iconBackground = branding['icon_background'] as String;
|
||||
|
||||
final String iconsYaml = '''
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
image_path: "$iconPath"
|
||||
adaptive_icon_background: "$iconBackground"
|
||||
adaptive_icon_foreground: "$iconForeground"
|
||||
''';
|
||||
await File('$appDir/flutter_launcher_icons.yaml').writeAsString(iconsYaml);
|
||||
|
||||
// ── 4. 生成 flutter_native_splash 配置 ──
|
||||
final String splashPath = 'assets/branding/${branding['splash']}';
|
||||
final String splashColor = branding['splash_color'] as String;
|
||||
|
||||
final String splashYaml = '''
|
||||
flutter_native_splash:
|
||||
color: "$splashColor"
|
||||
image: "$splashPath"
|
||||
android_12:
|
||||
image: "$splashPath"
|
||||
icon_background_color: "$splashColor"
|
||||
''';
|
||||
await File('$appDir/flutter_native_splash.yaml').writeAsString(splashYaml);
|
||||
|
||||
// ── 5. 执行资源生成器 ──
|
||||
print('\x1B[34m[信息] 正在生成应用图标...\x1B[0m');
|
||||
final ProcessResult iconsResult = await Process.run('dart', [
|
||||
'run',
|
||||
'flutter_launcher_icons',
|
||||
'-f',
|
||||
'flutter_launcher_icons-config.yaml',
|
||||
'flutter_launcher_icons.yaml',
|
||||
], workingDirectory: appDir);
|
||||
|
||||
// 执行 flutter_native_splash
|
||||
await Process.run('dart', [
|
||||
if (iconsResult.exitCode != 0) {
|
||||
print(
|
||||
'\x1B[31m[错误] 图标生成失败:\n${iconsResult.stderr}\x1B[0m',
|
||||
);
|
||||
print('\x1B[33mstdout:\n${iconsResult.stdout}\x1B[0m');
|
||||
exit(1);
|
||||
}
|
||||
print('\x1B[32m✔ 应用图标已生成。\x1B[0m');
|
||||
|
||||
print('\x1B[34m[信息] 正在生成启动页...\x1B[0m');
|
||||
final ProcessResult splashResult = await Process.run('dart', [
|
||||
'run',
|
||||
'flutter_native_splash:create',
|
||||
'--path=flutter_native_splash-config.yaml',
|
||||
'--path=flutter_native_splash.yaml',
|
||||
], workingDirectory: appDir);
|
||||
|
||||
if (splashResult.exitCode != 0) {
|
||||
print(
|
||||
'\x1B[31m[错误] 启动页生成失败:\n${splashResult.stderr}\x1B[0m',
|
||||
);
|
||||
print('\x1B[33mstdout:\n${splashResult.stdout}\x1B[0m');
|
||||
exit(1);
|
||||
}
|
||||
print('\x1B[32m✔ 启动页已生成。\x1B[0m');
|
||||
}
|
||||
|
|
|
|||