feat(brand): 应用图标、开屏资源与品牌字体切换为 ymb
No files matched your search
@@ -6,7 +6,7 @@
|
||||
<uses-permission
|
||||
android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<application
|
||||
android:label="FruitShorts"
|
||||
android:label="ymb"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
@@ -2,6 +2,18 @@
|
||||
<!-- Author: fruit.com.cn -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/splash_background" />
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:centerX="0"
|
||||
android:centerY="0"
|
||||
android:gradientRadius="350dp"
|
||||
android:startColor="#FF6B011A"
|
||||
android:centerColor="#616B011A"
|
||||
android:endColor="#006B011A" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
<!-- Author: fruit.com.cn -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/splash_background" />
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:centerX="0"
|
||||
android:centerY="0"
|
||||
android:gradientRadius="350dp"
|
||||
android:startColor="#FF6B011A"
|
||||
android:centerColor="#616B011A"
|
||||
android:endColor="#006B011A" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
|
||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB |
@@ -2,6 +2,18 @@
|
||||
<!-- Author: fruit.com.cn -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/splash_background" />
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:centerX="0"
|
||||
android:centerY="0"
|
||||
android:gradientRadius="350dp"
|
||||
android:startColor="#FF6B011A"
|
||||
android:centerColor="#616B011A"
|
||||
android:endColor="#006B011A" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
<!-- Author: fruit.com.cn -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/splash_background" />
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:centerX="0"
|
||||
android:centerY="0"
|
||||
android:gradientRadius="350dp"
|
||||
android:startColor="#FF6B011A"
|
||||
android:centerColor="#616B011A"
|
||||
android:endColor="#006B011A" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 89 KiB |
@@ -2,6 +2,18 @@
|
||||
<!-- Author: fruit.com.cn -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/splash_background" />
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:centerX="0"
|
||||
android:centerY="0"
|
||||
android:gradientRadius="350dp"
|
||||
android:startColor="#FF6B011A"
|
||||
android:centerColor="#616B011A"
|
||||
android:endColor="#006B011A" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 325 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 218 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 469 B |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -8,7 +8,7 @@
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#000000</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_brand</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
<item name="android:windowSplashScreenAnimationDuration">0</item>
|
||||
</style>
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#000000</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_brand</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
<item name="android:windowSplashScreenAnimationDuration">0</item>
|
||||
</style>
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
|
||||
<resources>
|
||||
<color name="splash_background">#000000</color>
|
||||
<color name="ic_launcher_background">#FF6B2C</color>
|
||||
</resources>
|
||||
<color name="ic_launcher_background">#E9142E</color>
|
||||
</resources>
|
||||
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 734 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 734 KiB |
|
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 822 B |
|
Before Width: | Height: | Size: 907 B After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 907 B After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 364 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 655 KiB |
@@ -1,115 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>ymb</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>ymb</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>FruitShorts</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>FruitShorts</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<key>127.0.0.1</key>
|
||||
<dict>
|
||||
<key>127.0.0.1</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>192.168.31.243</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>39.162.50.99</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>192.168.31.243</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>39.162.50.99</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>开发阶段需要访问同一局域网内的短剧内容服务。</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>开发阶段需要访问同一局域网内的短剧内容服务。</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -87,11 +87,30 @@ flutter:
|
||||
assets:
|
||||
- assets/icon/
|
||||
- assets/icon/splash_logo.png
|
||||
- assets/icon/splash_logo_android12.png
|
||||
- assets/icon/rail/
|
||||
# 只打包代码实际引用的那张登录背景(其余 3 张留在仓库但不进包)。
|
||||
- assets/auth/bg_3.jpg
|
||||
|
||||
fonts:
|
||||
- family: PlusJakartaSans
|
||||
fonts:
|
||||
- asset: assets/fonts/PlusJakartaSans-Regular.ttf
|
||||
weight: 400
|
||||
- asset: assets/fonts/PlusJakartaSans-Medium.ttf
|
||||
weight: 500
|
||||
- asset: assets/fonts/PlusJakartaSans-SemiBold.ttf
|
||||
weight: 600
|
||||
- asset: assets/fonts/PlusJakartaSans-Bold.ttf
|
||||
weight: 700
|
||||
- family: Outfit
|
||||
fonts:
|
||||
- asset: assets/fonts/Outfit-Regular.ttf
|
||||
weight: 400
|
||||
- asset: assets/fonts/Outfit-SemiBold.ttf
|
||||
weight: 600
|
||||
- asset: assets/fonts/Outfit-Bold.ttf
|
||||
weight: 700
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
@@ -126,13 +145,15 @@ flutter:
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
ios: true
|
||||
image_path: assets/icon/fruitshorts.png
|
||||
adaptive_icon_background: "#FF6B2C"
|
||||
adaptive_icon_foreground: assets/icon/fruitshorts.png
|
||||
image_path: assets/icon/ymb.png
|
||||
adaptive_icon_background: "#E9142E"
|
||||
adaptive_icon_foreground: assets/icon/ymb_foreground.png
|
||||
remove_alpha_ios: true
|
||||
|
||||
# 修改以下开屏配置后,必须重新执行:
|
||||
# dart run flutter_native_splash:create
|
||||
# 开屏页资源目前是手工维护的:Android 走 res/drawable*/launch_background.xml
|
||||
# (黑底暗红渐变 + 居中标识),iOS 走 LaunchImage.imageset 的完整参考图。
|
||||
# 不要再执行 `dart run flutter_native_splash:create`,它只会按 color 重新生成,
|
||||
# 会覆盖固定尺寸布局。使用 python3 tools/generate_brand_assets.py 生成资源。
|
||||
flutter_native_splash:
|
||||
color: "#000000"
|
||||
android: true
|
||||
@@ -140,3 +161,4 @@ flutter_native_splash:
|
||||
web: false
|
||||
android_12:
|
||||
color: "#000000"
|
||||
image: assets/icon/ymb_android12.png
|
||||
@@ -0,0 +1,136 @@
|
||||
// Author: fruit.com.cn
|
||||
// 验证品牌名称、原生启动布局与平台图标资源。
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
|
||||
const _iconAsset = 'assets/icon/ymb.png';
|
||||
const _iconSize = 1024;
|
||||
const _launchWidth = 390;
|
||||
const _launchHeight = 844;
|
||||
const _androidRoot = 'android/app/src/main/res';
|
||||
const _iosRoot = 'ios/Runner/Assets.xcassets';
|
||||
const _red = [245, 48, 59, 255];
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test('should_use_ymb_when_resolving_each_app_locale', () async {
|
||||
for (final locale in AppLocalizations.supportedLocales) {
|
||||
final l10n = await AppLocalizations.delegate.load(locale);
|
||||
expect(l10n.appName, 'ymb');
|
||||
}
|
||||
});
|
||||
|
||||
test('should_keep_reference_colors_when_decoding_master_icon', () async {
|
||||
final image = await _decode(_iconAsset);
|
||||
addTearDown(image.dispose);
|
||||
expect(image.width, _iconSize);
|
||||
expect(image.height, _iconSize);
|
||||
final bytes = await image.toByteData();
|
||||
final pixel = bytes!.buffer.asUint8List().sublist(0, _red.length);
|
||||
for (var channel = 0; channel < _red.length; channel++) {
|
||||
expect(pixel[channel], closeTo(_red[channel], 2));
|
||||
}
|
||||
});
|
||||
|
||||
test('should_match_declared_sizes_when_loading_ios_app_icons', () async {
|
||||
final manifest =
|
||||
jsonDecode(
|
||||
File(
|
||||
'$_iosRoot/AppIcon.appiconset/Contents.json',
|
||||
).readAsStringSync(),
|
||||
)
|
||||
as Map<String, dynamic>;
|
||||
for (final entry in manifest['images'] as List) {
|
||||
final size = double.parse((entry['size'] as String).split('x').first);
|
||||
final scale = int.parse((entry['scale'] as String).replaceAll('x', ''));
|
||||
final image = await _decode(
|
||||
'$_iosRoot/AppIcon.appiconset/${entry['filename']}',
|
||||
);
|
||||
expect(image.width, (size * scale).round());
|
||||
expect(image.height, image.width);
|
||||
image.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
test('should_fit_full_reference_when_loading_ios_launch_screen', () {
|
||||
final storyboard = File(
|
||||
'ios/Runner/Base.lproj/LaunchScreen.storyboard',
|
||||
).readAsStringSync();
|
||||
expect(storyboard, contains('contentMode="scaleAspectFit"'));
|
||||
expect(storyboard, contains('firstAttribute="leading"'));
|
||||
expect(storyboard, contains('firstAttribute="trailing"'));
|
||||
});
|
||||
|
||||
test('should_keep_reference_ratio_when_loading_ios_launch_images', () async {
|
||||
for (var scale = 1; scale <= 3; scale++) {
|
||||
final suffix = scale == 1 ? '' : '@${scale}x';
|
||||
final image = await _decode(
|
||||
'$_iosRoot/LaunchImage.imageset/LaunchImage$suffix.png',
|
||||
);
|
||||
expect(image.width, _launchWidth * scale);
|
||||
expect(image.height, _launchHeight * scale);
|
||||
image.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
test('should_keep_reference_tint_when_loading_android_launch_layers', () {
|
||||
for (final qualifier in ['', '-v21', '-v31', '-night', '-night-v21']) {
|
||||
final layers = File(
|
||||
'$_androidRoot/drawable$qualifier/launch_background.xml',
|
||||
).readAsStringSync();
|
||||
expect(layers, contains('<gradient'));
|
||||
expect(layers, contains('android:gravity="center"'));
|
||||
expect(layers, contains('@drawable/splash_screen'));
|
||||
}
|
||||
});
|
||||
|
||||
test('should_preserve_safe_area_when_loading_android12_mark', () async {
|
||||
const root = '$_androidRoot/drawable-xxxhdpi';
|
||||
final image = await _decode('$root/android12splash.png');
|
||||
addTearDown(image.dispose);
|
||||
const canvasSize = 1152;
|
||||
const markInset = 416;
|
||||
const center = canvasSize ~/ 2;
|
||||
expect(image.width, canvasSize);
|
||||
expect(image.height, canvasSize);
|
||||
final data = (await image.toByteData())!.buffer.asUint8List();
|
||||
int alphaAt(int x, int y) => data[(y * canvasSize + x) * 4 + 3];
|
||||
expect(alphaAt(0, 0), 0);
|
||||
expect(alphaAt(markInset - 1, center), 0);
|
||||
expect(alphaAt(markInset + 1, center), 255);
|
||||
expect(alphaAt(center, center), 255);
|
||||
for (final qualifier in ['', '-night']) {
|
||||
final styles = File(
|
||||
'$_androidRoot/values$qualifier-v31/styles.xml',
|
||||
).readAsStringSync();
|
||||
expect(styles, contains('@drawable/android12splash'));
|
||||
}
|
||||
});
|
||||
|
||||
test('should_use_ymb_when_resolving_platform_display_names', () {
|
||||
final manifest = File(
|
||||
'android/app/src/main/AndroidManifest.xml',
|
||||
).readAsStringSync();
|
||||
expect(manifest, contains('android:label="ymb"'));
|
||||
final plist = File('ios/Runner/Info.plist').readAsStringSync();
|
||||
expect(
|
||||
RegExp(
|
||||
r'<key>CFBundleDisplayName</key>\s*<string>ymb</string>',
|
||||
).hasMatch(plist),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<ui.Image> _decode(String path) async {
|
||||
final codec = await ui.instantiateImageCodec(File(path).readAsBytesSync());
|
||||
final frame = await codec.getNextFrame();
|
||||
codec.dispose();
|
||||
return frame.image;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'dart:math';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -107,4 +108,18 @@ void main() {
|
||||
expect(AppTheme.cardShadow.blurRadius, 0);
|
||||
expect(AppTheme.cardShadow.offset, Offset.zero);
|
||||
});
|
||||
|
||||
test('should_register_brand_font_assets_in_pubspec', () {
|
||||
final pubspec = File('pubspec.yaml').readAsStringSync();
|
||||
|
||||
expect(pubspec, contains('family: PlusJakartaSans'));
|
||||
expect(pubspec, contains('PlusJakartaSans-Regular.ttf'));
|
||||
expect(pubspec, contains('PlusJakartaSans-Medium.ttf'));
|
||||
expect(pubspec, contains('PlusJakartaSans-SemiBold.ttf'));
|
||||
expect(pubspec, contains('PlusJakartaSans-Bold.ttf'));
|
||||
expect(pubspec, contains('family: Outfit'));
|
||||
expect(pubspec, contains('Outfit-Regular.ttf'));
|
||||
expect(pubspec, contains('Outfit-SemiBold.ttf'));
|
||||
expect(pubspec, contains('Outfit-Bold.ttf'));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
# Author: fruit.com.cn
|
||||
"""使用确认的 ymb 原图生成图标、启动标识及原生平台尺寸。"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageChops, ImageDraw
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ASSETS = ROOT / "assets/icon"
|
||||
IOS = ROOT / "ios/Runner/Assets.xcassets"
|
||||
ANDROID = ROOT / "android/app/src/main/res"
|
||||
REFERENCE = ROOT / "assets/branding/ymb-reference.png"
|
||||
LAUNCH_REFERENCE = ROOT / "assets/branding/ymb-launch-reference.png"
|
||||
BRAND_RED = "#E9142E"
|
||||
WHITE = "#FFFFFF"
|
||||
CLEAR = (0, 0, 0, 0)
|
||||
MASTER_SIZE = 1024
|
||||
SPLASH_SIZE = 80
|
||||
SPLASH_RADIUS = 18
|
||||
ANDROID12_SIZE = 288
|
||||
FOREGROUND_SIZE = 108
|
||||
LEGACY_SIZE = 48
|
||||
PLACEHOLDER_SIZE = (600, 360)
|
||||
LAUNCH_SIZE = (390, 844)
|
||||
WHITE_THRESHOLD = 100
|
||||
WHITE_RANGE = 145
|
||||
DENSITIES = {"mdpi": 1, "hdpi": 1.5, "xhdpi": 2, "xxhdpi": 3, "xxxhdpi": 4}
|
||||
RESAMPLE = Image.Resampling.LANCZOS
|
||||
|
||||
|
||||
def wordmark(reference):
|
||||
"""从原图白色字标提取透明前景,不重新绘制或改变字形。"""
|
||||
channels = reference.convert("RGB").split()
|
||||
lightness = ImageChops.darker(ImageChops.darker(*channels[:2]), channels[2])
|
||||
alpha = lightness.point(
|
||||
lambda value: max(0, min(255, round(
|
||||
(value - WHITE_THRESHOLD) * 255 / WHITE_RANGE
|
||||
)))
|
||||
)
|
||||
image = Image.new("RGBA", reference.size, WHITE)
|
||||
image.putalpha(alpha)
|
||||
return image
|
||||
|
||||
|
||||
def centered(canvas, mark):
|
||||
"""将标识按可见边界居中,不包含字体基线额外留白。"""
|
||||
origin = ((canvas.width - mark.width) // 2, (canvas.height - mark.height) // 2)
|
||||
canvas.alpha_composite(mark, origin)
|
||||
return canvas
|
||||
|
||||
|
||||
def scaled_wordmark(mark, width):
|
||||
"""等比缩放字标,所有输出共用同一字形。"""
|
||||
height = round(mark.height * width / mark.width)
|
||||
return mark.resize((width, height), RESAMPLE)
|
||||
|
||||
|
||||
def splash_icon(master, size):
|
||||
"""仅启动图烘焙圆角;桌面图标保留系统负责的裁切。"""
|
||||
image = master.resize((size, size), RESAMPLE).convert("RGBA")
|
||||
mask = Image.new("L", (MASTER_SIZE, MASTER_SIZE))
|
||||
radius = MASTER_SIZE * SPLASH_RADIUS / SPLASH_SIZE
|
||||
ImageDraw.Draw(mask).rounded_rectangle(
|
||||
(0, 0, MASTER_SIZE - 1, MASTER_SIZE - 1), radius=radius, fill=255
|
||||
)
|
||||
image.putalpha(mask.resize((size, size), RESAMPLE))
|
||||
return image
|
||||
|
||||
|
||||
def save(image, target):
|
||||
"""写入明确路径的生成资源,不清理或删除目录。"""
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
image.save(target, optimize=True)
|
||||
|
||||
|
||||
def ios_assets(master):
|
||||
"""严格按资源目录声明导出图标;开屏保留确认的完整参考图。"""
|
||||
app_icons = IOS / "AppIcon.appiconset"
|
||||
manifest = json.loads((app_icons / "Contents.json").read_text())
|
||||
for entry in manifest["images"]:
|
||||
points = float(entry["size"].split("x")[0])
|
||||
scale = int(entry["scale"].removesuffix("x"))
|
||||
pixels = round(points * scale)
|
||||
save(master.resize((pixels, pixels), RESAMPLE), app_icons / entry["filename"])
|
||||
for scale in (1, 2, 3):
|
||||
suffix = "" if scale == 1 else f"@{scale}x"
|
||||
size = tuple(value * scale for value in LAUNCH_SIZE)
|
||||
image = Image.open(LAUNCH_REFERENCE).convert("RGB").resize(size, RESAMPLE)
|
||||
save(image, IOS / f"LaunchImage.imageset/LaunchImage{suffix}.png")
|
||||
|
||||
|
||||
def android_assets(master, foreground):
|
||||
"""覆盖现有密度资源,并为 Android 12 提供独立安全区标识。"""
|
||||
for density, scale in DENSITIES.items():
|
||||
pixels = round(LEGACY_SIZE * scale)
|
||||
save(
|
||||
master.resize((pixels, pixels), RESAMPLE),
|
||||
ANDROID / f"mipmap-{density}/ic_launcher.png",
|
||||
)
|
||||
pixels = round(FOREGROUND_SIZE * scale)
|
||||
save(
|
||||
foreground.resize((pixels, pixels), RESAMPLE),
|
||||
ANDROID / f"drawable-{density}/ic_launcher_foreground.png",
|
||||
)
|
||||
save(
|
||||
Image.new("RGB", (pixels, pixels), BRAND_RED),
|
||||
ANDROID / f"mipmap-{density}/ic_launcher_background.png",
|
||||
)
|
||||
mark = splash_icon(master, round(SPLASH_SIZE * scale))
|
||||
save(mark, ANDROID / f"drawable-{density}/splash_screen.png")
|
||||
size = round(ANDROID12_SIZE * scale)
|
||||
image = centered(Image.new("RGBA", (size, size), CLEAR), mark)
|
||||
save(image, ANDROID / f"drawable-{density}/android12splash.png")
|
||||
|
||||
|
||||
def main():
|
||||
"""直接复用确认的原始图标和开屏,输出平台所需尺寸。"""
|
||||
reference = Image.open(REFERENCE).convert("RGB")
|
||||
master = reference.resize((MASTER_SIZE, MASTER_SIZE), RESAMPLE)
|
||||
mark = wordmark(reference)
|
||||
foreground = mark.resize((MASTER_SIZE, MASTER_SIZE), RESAMPLE)
|
||||
save(master, ASSETS / "ymb.png")
|
||||
save(foreground, ASSETS / "ymb_foreground.png")
|
||||
placeholder = centered(
|
||||
Image.new("RGBA", PLACEHOLDER_SIZE, CLEAR),
|
||||
scaled_wordmark(mark.crop(mark.getbbox()), PLACEHOLDER_SIZE[0]),
|
||||
)
|
||||
save(placeholder, ASSETS / "splash_logo.png")
|
||||
icon = splash_icon(master, SPLASH_SIZE * 4)
|
||||
size = ANDROID12_SIZE * 4
|
||||
save(
|
||||
centered(Image.new("RGBA", (size, size), CLEAR), icon),
|
||||
ASSETS / "ymb_android12.png",
|
||||
)
|
||||
ios_assets(master)
|
||||
android_assets(master, foreground)
|
||||
print("Generated ymb assets: master 1024, iOS catalog, Android 5 densities.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||