diff --git a/README.md b/README.md index 191f589..57f3709 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # web_android_shell -H5 壳子项目。 +Android 平板专用 H5 壳项目。 ## 调试说明 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/analysis_options.yaml b/apps/quanxue/analysis_options.yaml index 0d29021..f13d6ae 100644 --- a/apps/quanxue/analysis_options.yaml +++ b/apps/quanxue/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. +# Dart 分析器配置,用于在开发阶段发现错误、警告和代码规范问题。 +# 可通过 `flutter analyze` 执行静态检查。 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. + # 如需自定义规则,可在此处开启或关闭指定 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 + # avoid_print: false # 取消注释后可关闭 `avoid_print` 规则。 + # prefer_single_quotes: true # 取消注释后可开启 `prefer_single_quotes` 规则。 diff --git a/apps/quanxue/android/app/build.gradle.kts b/apps/quanxue/android/app/build.gradle.kts index 5badcf2..1f16e22 100644 --- a/apps/quanxue/android/app/build.gradle.kts +++ b/apps/quanxue/android/app/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("com.android.application") id("kotlin-android") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + // Flutter Gradle 插件必须放在 Android 与 Kotlin Gradle 插件之后应用。 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" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. + // 下面这些值可以按应用实际需求调整。 + // 更多说明可参考:https://flutter.dev/to/review-gradle-config。 minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode @@ -32,8 +32,8 @@ android { buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. + // 待补充:为 release 构建配置正式签名。 + // 当前先使用 debug 签名,确保 `flutter run --release` 可直接运行。 signingConfig = signingConfigs.getByName("debug") } } diff --git a/apps/quanxue/ios/.gitignore b/apps/quanxue/ios/.gitignore deleted file mode 100644 index 7a7f987..0000000 --- a/apps/quanxue/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/apps/quanxue/ios/Flutter/AppFrameworkInfo.plist b/apps/quanxue/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 391a902..0000000 --- a/apps/quanxue/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/apps/quanxue/ios/Flutter/Debug.xcconfig b/apps/quanxue/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6..0000000 --- a/apps/quanxue/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/apps/quanxue/ios/Flutter/Release.xcconfig b/apps/quanxue/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bf..0000000 --- a/apps/quanxue/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/apps/quanxue/ios/Podfile b/apps/quanxue/ios/Podfile deleted file mode 100644 index 620e46e..0000000 --- a/apps/quanxue/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/apps/quanxue/ios/Runner.xcodeproj/project.pbxproj b/apps/quanxue/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index d963011..0000000 --- a/apps/quanxue/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,623 +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)"; - DEVELOPMENT_TEAM = Z778GC45N8; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.wanmake.quanxue; - 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.wanmake.quanxue.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.wanmake.quanxue.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.wanmake.quanxue.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)"; - DEVELOPMENT_TEAM = Z778GC45N8; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.wanmake.quanxue; - 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)"; - DEVELOPMENT_TEAM = Z778GC45N8; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.wanmake.quanxue; - 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/apps/quanxue/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/apps/quanxue/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/apps/quanxue/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/apps/quanxue/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/quanxue/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/apps/quanxue/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/apps/quanxue/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/quanxue/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/apps/quanxue/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/apps/quanxue/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/quanxue/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index e3773d4..0000000 --- a/apps/quanxue/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/quanxue/ios/Runner.xcworkspace/contents.xcworkspacedata b/apps/quanxue/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a1..0000000 --- a/apps/quanxue/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/apps/quanxue/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/quanxue/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/apps/quanxue/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/apps/quanxue/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/quanxue/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/apps/quanxue/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/apps/quanxue/ios/Runner/AppDelegate.swift b/apps/quanxue/ios/Runner/AppDelegate.swift deleted file mode 100644 index c30b367..0000000 --- a/apps/quanxue/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/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fa..0000000 --- a/apps/quanxue/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/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 7353c41..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d93..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b00..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe73094..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index 321773c..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 797d452..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index 0ec3034..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec3034..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index 84ac32a..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 8953cba..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf1..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2..0000000 --- a/apps/quanxue/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/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19ea..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19ea..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19ea..0000000 Binary files a/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ diff --git a/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/apps/quanxue/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725..0000000 --- a/apps/quanxue/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/apps/quanxue/ios/Runner/Base.lproj/LaunchScreen.storyboard b/apps/quanxue/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c..0000000 --- a/apps/quanxue/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/quanxue/ios/Runner/Base.lproj/Main.storyboard b/apps/quanxue/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c2851..0000000 --- a/apps/quanxue/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/quanxue/ios/Runner/Info.plist b/apps/quanxue/ios/Runner/Info.plist deleted file mode 100644 index 383f229..0000000 --- a/apps/quanxue/ios/Runner/Info.plist +++ /dev/null @@ -1,70 +0,0 @@ - - - - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Quanxue - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - quanxue - 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/apps/quanxue/ios/Runner/Runner-Bridging-Header.h b/apps/quanxue/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a5..0000000 --- a/apps/quanxue/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/apps/quanxue/ios/Runner/SceneDelegate.swift b/apps/quanxue/ios/Runner/SceneDelegate.swift deleted file mode 100644 index b9ce8ea..0000000 --- a/apps/quanxue/ios/Runner/SceneDelegate.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Flutter -import UIKit - -class SceneDelegate: FlutterSceneDelegate { - -} diff --git a/apps/quanxue/ios/RunnerTests/RunnerTests.swift b/apps/quanxue/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index 86a7c3b..0000000 --- a/apps/quanxue/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/apps/quanxue/pubspec.yaml b/apps/quanxue/pubspec.yaml index 4799f44..72d8264 100644 --- a/apps/quanxue/pubspec.yaml +++ b/apps/quanxue/pubspec.yaml @@ -1,38 +1,20 @@ 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 +# 阻止误发布到 pub.dev。 +publish_to: 'none' -# 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. +# Android 应用版本号:`build-name` 对应 `versionName`,`build-number` 对应 `versionCode`。 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. + # Material 图标已覆盖主要场景;此依赖保留以兼容历史代码。 cupertino_icons: ^1.0.8 web_shell_core: path: ../../packages/web_shell_core @@ -41,51 +23,9 @@ 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 lint 规则集合。 flutter_lints: ^6.0.0 -# 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. + # 启用 Material 图标字体。 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/apps/quanxue/test/widget_test.dart b/apps/quanxue/test/widget_test.dart index cbb8d01..b846ef5 100644 --- a/apps/quanxue/test/widget_test.dart +++ b/apps/quanxue/test/widget_test.dart @@ -1,9 +1,7 @@ -// This is a basic Flutter widget test. +// 这是一个基础的 Flutter 组件测试示例。 // -// 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. +// 你可以通过 `WidgetTester` 与组件交互,例如点击、滚动, +// 也可以查找组件文本并验证组件属性是否符合预期。 import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -12,18 +10,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()); - // Verify that our counter starts at 0. + // 验证计数器初始值为 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(); - // Verify that our counter has incremented. + // 验证计数器已经加 1。 expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); diff --git a/packages/web_android_shell/analysis_options.yaml b/packages/web_android_shell/analysis_options.yaml index d4e0f0c..f13d6ae 100644 --- a/packages/web_android_shell/analysis_options.yaml +++ b/packages/web_android_shell/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/packages/web_android_shell/android/app/build.gradle.kts b/packages/web_android_shell/android/app/build.gradle.kts index 7868f7c..5dfc1c2 100644 --- a/packages/web_android_shell/android/app/build.gradle.kts +++ b/packages/web_android_shell/android/app/build.gradle.kts @@ -1,45 +1,45 @@ -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 { +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() - } - + 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" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. + // 下面这些值可以按应用实际需求调整。 + // 更多说明可参考:https://flutter.dev/to/review-gradle-config。 minSdk = flutter.minSdkVersion - // Keep a stable targetSdk for better compatibility with older system WebView builds. + // 为兼容旧版系统 WebView,这里保持稳定的 targetSdk。 targetSdk = 34 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 { + // 待补充:为 release 构建配置正式签名。 + // 当前先使用 debug 签名,确保 `flutter run --release` 可直接运行。 + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/web_android_shell/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 index 53cf90a..227e98b 100644 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/.gitignore b/packages/web_android_shell/ios/.gitignore deleted file mode 100644 index ad322bc..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Flutter/AppFrameworkInfo.plist b/packages/web_android_shell/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 256cf28..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Flutter/Debug.xcconfig b/packages/web_android_shell/ios/Flutter/Debug.xcconfig deleted file mode 100644 index dfd2626..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Flutter/Release.xcconfig b/packages/web_android_shell/ios/Flutter/Release.xcconfig deleted file mode 100644 index a97381a..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Podfile b/packages/web_android_shell/ios/Podfile deleted file mode 100644 index 620e46e..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Runner.xcodeproj/project.pbxproj b/packages/web_android_shell/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 338afe6..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/web_android_shell/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index c4b79bd..0000000 --- a/packages/web_android_shell/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/web_android_shell/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/web_android_shell/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index fc6bf80..0000000 --- a/packages/web_android_shell/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/web_android_shell/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/web_android_shell/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index af0309c..0000000 --- a/packages/web_android_shell/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/web_android_shell/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/web_android_shell/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index bbabc4e..0000000 --- a/packages/web_android_shell/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/web_android_shell/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/web_android_shell/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 59c6d39..0000000 --- a/packages/web_android_shell/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/web_android_shell/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/web_android_shell/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index fc6bf80..0000000 --- a/packages/web_android_shell/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/web_android_shell/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/web_android_shell/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index af0309c..0000000 --- a/packages/web_android_shell/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/web_android_shell/ios/Runner/AppDelegate.swift b/packages/web_android_shell/ios/Runner/AppDelegate.swift deleted file mode 100644 index ed1c097..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 1950fd8..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 7353c41..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d93..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b00..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe73094..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index 321773c..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 797d452..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index 0ec3034..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec3034..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index 84ac32a..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 8953cba..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf1..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index d08a4de..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19ea..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19ea..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19ea..0000000 Binary files a/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ diff --git a/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/web_android_shell/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 65a94b5..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/web_android_shell/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 497371e..0000000 --- a/packages/web_android_shell/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/web_android_shell/ios/Runner/Base.lproj/Main.storyboard b/packages/web_android_shell/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index bbb83ca..0000000 --- a/packages/web_android_shell/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/web_android_shell/ios/Runner/Info.plist b/packages/web_android_shell/ios/Runner/Info.plist deleted file mode 100644 index 77f90b9..0000000 --- a/packages/web_android_shell/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/packages/web_android_shell/ios/Runner/Runner-Bridging-Header.h b/packages/web_android_shell/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index fae207f..0000000 --- a/packages/web_android_shell/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/packages/web_android_shell/ios/Runner/SceneDelegate.swift b/packages/web_android_shell/ios/Runner/SceneDelegate.swift deleted file mode 100644 index b79be9b..0000000 --- a/packages/web_android_shell/ios/Runner/SceneDelegate.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Flutter -import UIKit - -class SceneDelegate: FlutterSceneDelegate { - -} diff --git a/packages/web_android_shell/ios/RunnerTests/RunnerTests.swift b/packages/web_android_shell/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index 4d206de..0000000 --- a/packages/web_android_shell/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/lib/main.dart b/packages/web_android_shell/lib/main.dart index f12a3fc..f10f27b 100644 --- a/packages/web_android_shell/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.yaml b/packages/web_android_shell/pubspec.yaml index f258bdf..2780208 100644 --- a/packages/web_android_shell/pubspec.yaml +++ b/packages/web_android_shell/pubspec.yaml @@ -1,95 +1,35 @@ name: web_android_shell -description: "H5壳子项目" -# 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. +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 - - # 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 - -# 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 + 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/packages/web_shell_core/README.md b/packages/web_shell_core/README.md index 4344647..c082dad 100644 --- a/packages/web_shell_core/README.md +++ b/packages/web_shell_core/README.md @@ -1,15 +1,12 @@ # web_shell_core -A new Flutter plugin project. +Android 平板专用的 H5 壳核心库,负责: -## Getting Started +- WebView 启动与恢复 +- H5 / Native JS Bridge +- 文件、相机、权限等宿主能力 +- 品牌化壳层 UI -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/to/develop-plugins), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. - -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. +## 平台约束 +当前仅支持 Android。 diff --git a/packages/web_shell_core/analysis_options.yaml b/packages/web_shell_core/analysis_options.yaml index a5744c1..71b1554 100644 --- a/packages/web_shell_core/analysis_options.yaml +++ b/packages/web_shell_core/analysis_options.yaml @@ -1,4 +1,5 @@ -include: package:flutter_lints/flutter.yaml +include: package:very_good_analysis/analysis_options.yaml -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +analyzer: + exclude: + - coverage/** 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 index 42caca8..f9e4108 100644 --- 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 @@ -126,7 +126,7 @@ public class CoreShellActivity 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 CoreShellActivity 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 CoreShellActivity extends FlutterActivity { } } - //noinspection ResultOfMethodCallIgnored + target.delete(); } 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 index cd49f36..6d7289e 100644 --- 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 @@ -6,14 +6,14 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -/** WebShellCorePlugin */ +/** WebShellCorePlugin 插件实现。 */ class WebShellCorePlugin : FlutterPlugin, MethodCallHandler { - // The MethodChannel that will the communication between Flutter and native Android + // 用于 Flutter 与原生 Android 通信的 MethodChannel。 // - // This local reference serves to register the plugin with the Flutter Engine and unregister it - // when the Flutter Engine is detached from the Activity + // 这里保留本地引用,用于在 Flutter Engine 绑定时注册插件, + // 并在 Flutter Engine 分离时正确注销。 private lateinit var channel: MethodChannel override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 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 index cbcc4b0..cede880 100644 --- 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 @@ -6,11 +6,10 @@ import org.mockito.Mockito import kotlin.test.Test /* - * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * 这是一个简单的单元测试示例,用于验证插件 Kotlin 实现中的基础逻辑。 * - * Once you have built the plugin's example app, you can run these tests from the command - * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or - * you can run them directly from IDEs that support JUnit such as Android Studio. + * 你可以在对应 Android 工程目录下执行 `./gradlew testDebugUnitTest` 运行, + * 也可以直接在支持 JUnit 的 IDE 中运行。 */ internal class WebShellCorePluginTest { diff --git a/packages/web_shell_core/ios/.gitignore b/packages/web_shell_core/ios/.gitignore deleted file mode 100644 index 034771f..0000000 --- a/packages/web_shell_core/ios/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -.idea/ -.vagrant/ -.sconsign.dblite -.svn/ - -.DS_Store -*.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m - -.generated/ - -*.pbxuser -*.mode1v3 -*.mode2v3 -*.perspectivev3 - -!default.pbxuser -!default.mode1v3 -!default.mode2v3 -!default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/Generated.xcconfig -/Flutter/ephemeral/ -/Flutter/flutter_export_environment.sh diff --git a/packages/web_shell_core/ios/Assets/.gitkeep b/packages/web_shell_core/ios/Assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/packages/web_shell_core/ios/Classes/WebShellCorePlugin.swift b/packages/web_shell_core/ios/Classes/WebShellCorePlugin.swift deleted file mode 100644 index 5b7c80c..0000000 --- a/packages/web_shell_core/ios/Classes/WebShellCorePlugin.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Flutter -import UIKit - -public class WebShellCorePlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "web_shell_core", binaryMessenger: registrar.messenger()) - let instance = WebShellCorePlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("iOS " + UIDevice.current.systemVersion) - default: - result(FlutterMethodNotImplemented) - } - } -} diff --git a/packages/web_shell_core/ios/Resources/PrivacyInfo.xcprivacy b/packages/web_shell_core/ios/Resources/PrivacyInfo.xcprivacy deleted file mode 100644 index a34b7e2..0000000 --- a/packages/web_shell_core/ios/Resources/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,14 +0,0 @@ - - - - - NSPrivacyTrackingDomains - - NSPrivacyAccessedAPITypes - - NSPrivacyCollectedDataTypes - - NSPrivacyTracking - - - diff --git a/packages/web_shell_core/ios/web_shell_core.podspec b/packages/web_shell_core/ios/web_shell_core.podspec deleted file mode 100644 index e7a92a3..0000000 --- a/packages/web_shell_core/ios/web_shell_core.podspec +++ /dev/null @@ -1,29 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint web_shell_core.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'web_shell_core' - s.version = '0.0.1' - s.summary = 'A new Flutter plugin project.' - s.description = <<-DESC -A new Flutter plugin project. - DESC - s.homepage = 'http://example.com' - s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'Flutter' - s.platform = :ios, '13.0' - - # Flutter.framework does not contain a i386 slice. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } - s.swift_version = '5.0' - - # If your plugin requires a privacy manifest, for example if it uses any - # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your - # plugin's privacy impact, and then uncomment this line. For more information, - # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files - # s.resource_bundles = {'web_shell_core_privacy' => ['Resources/PrivacyInfo.xcprivacy']} -end diff --git a/packages/web_shell_core/lib/core_app.dart b/packages/web_shell_core/lib/core_app.dart index e923602..ad081aa 100644 --- a/packages/web_shell_core/lib/core_app.dart +++ b/packages/web_shell_core/lib/core_app.dart @@ -1,4 +1,4 @@ -/// WebShell 核心库 — 为 H5 壳应用提供 WebView 引擎、JS Bridge 和平台服务。 +/// WebShell 核心库:为 H5 壳应用提供 WebView 引擎、原生桥接与平台服务。 library; import 'dart:async'; @@ -15,31 +15,32 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; -// ── config ── +// ── 配置 ── part 'src/config/shell_environment.dart'; part 'src/config/url_resolver.dart'; -// ── engine ── +// ── 引擎 ── part 'src/engine/compatibility.dart'; part 'src/engine/recovery.dart'; -// ── bridge ── +// ── 桥接 ── part 'src/bridge/bridge_protocol.dart'; part 'src/bridge/bridge_actions.dart'; part 'src/bridge/legacy_camera_compat.dart'; -// ── services ── +// ── 服务 ── part 'src/services/media_service.dart'; part 'src/services/permission_service.dart'; part 'src/services/navigation_service.dart'; -// ── ui ── +// ── 界面 ── 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; @@ -52,7 +53,7 @@ Color get _shellMutedTextColor => _env.mutedTextColor; // ── 入口 ── /// 启动壳应用的唯一入口。 -/// 各品牌 App 的 main.dart 调用此函数并传入自己的 [ShellEnvironment]。 +/// 各品牌应用的 `main.dart` 调用此函数,并传入自己的 [ShellEnvironment]。 Future runShellApp(ShellEnvironment environment) async { _env = environment; _initializeUrls(); @@ -86,8 +87,7 @@ bool _supportsEmbeddedWebView({ if (isWeb) { return false; } - final TargetPlatform target = platform ?? defaultTargetPlatform; - return target == TargetPlatform.android || - target == TargetPlatform.iOS || - target == TargetPlatform.macOS; + + 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 index 3a32c4e..c6459df 100644 --- a/packages/web_shell_core/lib/src/bridge/bridge_actions.dart +++ b/packages/web_shell_core/lib/src/bridge/bridge_actions.dart @@ -1,5 +1,5 @@ part of '../../core_app.dart'; -// bridge_actions.dart 不需要单独的代码 —— -// Bridge action 处理逻辑直接内嵌在 shell_page.dart 的 _handleBridgeMessage 方法中。 -// 此文件预留给未来将 action handlers 独立出来的重构使用。 +// 当前无需在 `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 index 59b70a2..9a098a3 100644 --- a/packages/web_shell_core/lib/src/bridge/bridge_protocol.dart +++ b/packages/web_shell_core/lib/src/bridge/bridge_protocol.dart @@ -9,9 +9,9 @@ Future _injectAppShellBridge( ) async { try { await controller.runJavaScript(_buildAppShellBridgeScript()); - debugPrint('Injected AppShell bridge for $url'); - } catch (error, stackTrace) { - debugPrint('Inject AppShell bridge failed: $error\n$stackTrace'); + debugPrint('已为 $url 注入 AppShell 桥接'); + } on Object catch (error, stackTrace) { + debugPrint('注入 AppShell 桥接失败:$error\n$stackTrace'); } } @@ -22,7 +22,7 @@ Future _sendBridgeResponse( Object? data, String? error, }) async { - final Map response = { + final response = { 'requestId': requestId, 'success': success, 'data': data, @@ -33,9 +33,9 @@ Future _sendBridgeResponse( await controller.runJavaScript( 'window.__appShellReceiveResponse(${jsonEncode(response)});', ); - } catch (bridgeError, stackTrace) { + } on Object catch (bridgeError, stackTrace) { debugPrint( - 'Send AppShell bridge response failed: $bridgeError\n$stackTrace', + '发送 AppShell 桥接响应失败:$bridgeError\n$stackTrace', ); } } 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 index e4ca46c..5def953 100644 --- a/packages/web_shell_core/lib/src/bridge/legacy_camera_compat.dart +++ b/packages/web_shell_core/lib/src/bridge/legacy_camera_compat.dart @@ -1,7 +1,7 @@ part of '../../core_app.dart'; -// 旧版 H5 相机兼容层 JS 代码 -// 通过 monkey-patch window.openCamera/capturePhoto 实现对老页面的兼容 +// 旧版 H5 相机兼容层 JavaScript 代码 +// 通过替换 `window.openCamera` / `capturePhoto` 的方式兼容老页面。 const String _legacyCameraCompatScript = ''' if (!window.__appShellLegacyCameraCompatInstalled) { diff --git a/packages/web_shell_core/lib/src/config/shell_environment.dart b/packages/web_shell_core/lib/src/config/shell_environment.dart index 9804842..e3fbd81 100644 --- a/packages/web_shell_core/lib/src/config/shell_environment.dart +++ b/packages/web_shell_core/lib/src/config/shell_environment.dart @@ -1,8 +1,9 @@ part of '../../core_app.dart'; /// 壳应用的品牌环境配置。 -/// 每个白标 App 在 main.dart 中传入自己的 ShellEnvironment 实例。 +/// 每个白标应用都在 `main.dart` 中传入自己的 [ShellEnvironment] 实例。 class ShellEnvironment { + /// 创建 Android 平板壳应用的环境配置。 const ShellEnvironment({ required this.appName, required this.appKey, @@ -31,6 +32,6 @@ class ShellEnvironment { /// 品牌次要文字色 final Color mutedTextColor; - /// 可选的初始 URL(为空则使用默认地址) + /// 可选的初始地址;为空时使用默认地址。 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 index 52dd8f1..df89f8c 100644 --- a/packages/web_shell_core/lib/src/config/url_resolver.dart +++ b/packages/web_shell_core/lib/src/config/url_resolver.dart @@ -6,20 +6,20 @@ late Uri _initialUri; late String _initialUrl; void _initializeUrls() { - final String candidate = (_env.initialUrl ?? '').trim(); + final candidate = (_env.initialUrl ?? '').trim(); if (candidate.isEmpty) { _initialUri = Uri.parse(_defaultInitialUrl); } else { - final Uri? directUri = Uri.tryParse(candidate); + final directUri = Uri.tryParse(candidate); if (directUri != null && directUri.hasScheme) { _initialUri = directUri; } else if (candidate.startsWith('//')) { - final Uri? protocolRelativeUri = Uri.tryParse('https:$candidate'); + final protocolRelativeUri = Uri.tryParse('https:$candidate'); if (protocolRelativeUri != null && protocolRelativeUri.hasScheme) { _initialUri = protocolRelativeUri; } } else { - final Uri? httpsUri = Uri.tryParse('https://$candidate'); + final httpsUri = Uri.tryParse('https://$candidate'); if (httpsUri != null && httpsUri.hasScheme && httpsUri.host.isNotEmpty) { _initialUri = httpsUri; } else { @@ -31,7 +31,7 @@ void _initializeUrls() { } bool _isNetworkUrl(String? url) { - final Uri? uri = Uri.tryParse(url ?? ''); + final uri = Uri.tryParse(url ?? ''); if (uri == null) { return false; } @@ -39,11 +39,11 @@ bool _isNetworkUrl(String? url) { } String _normalizeComparableUri(Uri uri) { - final String scheme = uri.scheme.toLowerCase(); - final String host = uri.host.toLowerCase(); - final int port = uri.hasPort ? uri.port : _defaultPortForScheme(scheme); - final String path = _normalizeComparablePath(uri.path); - final String query = uri.query; + 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'; } diff --git a/packages/web_shell_core/lib/src/engine/compatibility.dart b/packages/web_shell_core/lib/src/engine/compatibility.dart index a7b0d3c..4025866 100644 --- a/packages/web_shell_core/lib/src/engine/compatibility.dart +++ b/packages/web_shell_core/lib/src/engine/compatibility.dart @@ -1,23 +1,33 @@ part of '../../core_app.dart'; +/// Android WebView 的渲染模式。 enum AndroidRenderMode { + /// 使用纹理层渲染模式。 texture, - hybrid; + /// 使用混合合成渲染模式。 + 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, @@ -29,6 +39,7 @@ class AndroidWebViewInfo { this.webViewLongVersionCode, }) : webViewMajorVersion = _parseWebViewMajorVersion(webViewVersionName); + /// 根据平台通道返回的原始数据创建实例。 factory AndroidWebViewInfo.fromMap(Map raw) { return AndroidWebViewInfo( sdkInt: _readInt(raw['sdkInt']), @@ -44,27 +55,50 @@ class AndroidWebViewInfo { ); } + /// 当前设备的 Android SDK 版本。 final int sdkInt; + + /// 设备制造商。 final String manufacturer; + + /// 设备品牌。 final String brand; + + /// 设备型号。 final String model; + + /// 当前 WebView 数据目录后缀;有值时返回。 final String? webViewDataDirectorySuffix; + + /// 已安装 WebView 的包名;有值时返回。 final String? webViewPackageName; + + /// 已安装 WebView 的版本名;有值时返回。 final String? webViewVersionName; + + /// 已安装 WebView 的长版本号;有值时返回。 final int? webViewLongVersionCode; + + /// 从 [webViewVersionName] 中解析出的 WebView 主版本号。 final int? webViewMajorVersion; + /// 当前 WebView 是否应按旧版本处理。 bool get isLegacyWebView => webViewMajorVersion != null && webViewMajorVersion! <= _legacyWebViewMajorVersionThreshold; + /// 当前设备是否命中已知问题机型 `F136A`。 bool get isF136A => model.toUpperCase() == 'F136A'; + /// 返回用于诊断的精简日志摘要。 String get summary { - final List parts = [ + final deviceSummary = [ + manufacturer, + model, + ].where((part) => part.isNotEmpty).join(' '); + final parts = [ 'sdk=$sdkInt', - if (manufacturer.isNotEmpty || model.isNotEmpty) - 'device=${[manufacturer, model].where((part) => part.isNotEmpty).join(' ')}', + if (manufacturer.isNotEmpty || model.isNotEmpty) 'device=$deviceSummary', if (webViewPackageName case final String packageName when packageName.isNotEmpty) 'webViewPackage=$packageName', @@ -100,12 +134,14 @@ class AndroidWebViewInfo { } static String? _readNullableString(Object? value) { - final String normalized = _readString(value); + final normalized = _readString(value); return normalized.isEmpty ? null : normalized; } } +/// Android WebView 的兼容策略。 class AndroidCompatibilityPlan { + /// 创建 WebView 兼容策略。 const AndroidCompatibilityPlan({ required this.renderModes, required this.useWideViewPort, @@ -113,14 +149,15 @@ class AndroidCompatibilityPlan { required this.prefersAggressiveRecovery, }); + /// 创建设备信息未知时使用的默认兜底策略。 factory AndroidCompatibilityPlan.fallback() { - return AndroidCompatibilityPlan( + return const AndroidCompatibilityPlan( renderModes: kDebugMode - ? const [ + ? [ AndroidRenderMode.texture, AndroidRenderMode.hybrid, ] - : const [ + : [ AndroidRenderMode.hybrid, AndroidRenderMode.texture, ], @@ -130,15 +167,15 @@ class AndroidCompatibilityPlan { ); } + /// 根据当前 Android 设备与 WebView 信息构建策略。 factory AndroidCompatibilityPlan.fromInfo(AndroidWebViewInfo? info) { if (info == null) { return AndroidCompatibilityPlan.fallback(); } - final bool legacyAndroid = info.sdkInt <= 28; - final bool legacyWebView = info.isLegacyWebView; - final bool preferHybridFirst = - info.isF136A || legacyAndroid || legacyWebView; + final legacyAndroid = info.sdkInt <= 28; + final legacyWebView = info.isLegacyWebView; + final preferHybridFirst = info.isF136A || legacyAndroid || legacyWebView; return AndroidCompatibilityPlan( renderModes: preferHybridFirst @@ -156,11 +193,19 @@ class AndroidCompatibilityPlan { ); } + /// 按优先级排序的候选渲染模式。 final List renderModes; + + /// 是否启用宽视口支持。 final bool useWideViewPort; + + /// 界面是否需要提示用户更新系统 WebView。 final bool suggestWebViewUpdate; + + /// 恢复流程是否使用更激进的策略。 final bool prefersAggressiveRecovery; + /// 返回精简的诊断描述。 String describe() { return [ 'modes=${renderModes.map((mode) => mode.logName).join(' -> ')}', @@ -176,6 +221,6 @@ int? _parseWebViewMajorVersion(String? versionName) { if (versionName == null || versionName.isEmpty) { return null; } - final String candidate = versionName.split('.').first.trim(); + final candidate = versionName.split('.').first.trim(); return int.tryParse(candidate); } diff --git a/packages/web_shell_core/lib/src/engine/recovery.dart b/packages/web_shell_core/lib/src/engine/recovery.dart index 3910619..e320620 100644 --- a/packages/web_shell_core/lib/src/engine/recovery.dart +++ b/packages/web_shell_core/lib/src/engine/recovery.dart @@ -6,32 +6,26 @@ const Duration _debugStartupWatchdogDuration = Duration(seconds: 15); // ── 错误提示映射 ── String _friendlyErrorTitle(WebResourceError error) { - switch (error.errorType) { - case WebResourceErrorType.timeout: - return '请求超时'; - case WebResourceErrorType.hostLookup: - case WebResourceErrorType.connect: - case WebResourceErrorType.io: - return '网络连接失败'; - case WebResourceErrorType.failedSslHandshake: - return '安全连接失败'; - default: - return '页面加载失败'; - } + return switch (error.errorType) { + WebResourceErrorType.timeout => '请求超时', + WebResourceErrorType.hostLookup || + WebResourceErrorType.connect || + WebResourceErrorType.io => '网络连接失败', + WebResourceErrorType.failedSslHandshake => '安全连接失败', + _ => '页面加载失败', + }; } String _friendlyErrorMessage(WebResourceError error) { - switch (error.errorType) { - case WebResourceErrorType.timeout: - return '当前网络较慢,请稍后重新加载。'; - case WebResourceErrorType.hostLookup: - case WebResourceErrorType.connect: - case WebResourceErrorType.io: - return '没有成功连接到服务器,请检查网络后重试。'; - case WebResourceErrorType.failedSslHandshake: - return '当前站点证书校验失败,请稍后再试。'; - default: - final String description = error.description.trim(); - return description.isEmpty ? '请稍后重新加载页面。' : description; - } + return switch (error.errorType) { + WebResourceErrorType.timeout => '当前网络较慢,请稍后重新加载。', + WebResourceErrorType.hostLookup || + WebResourceErrorType.connect || + WebResourceErrorType.io => '没有成功连接到服务器,请检查网络后重试。', + WebResourceErrorType.failedSslHandshake => '当前站点证书校验失败,请稍后再试。', + _ => + error.description.trim().isEmpty + ? '请稍后重新加载页面。' + : error.description.trim(), + }; } diff --git a/packages/web_shell_core/lib/src/services/media_service.dart b/packages/web_shell_core/lib/src/services/media_service.dart index c28763d..9b27e64 100644 --- a/packages/web_shell_core/lib/src/services/media_service.dart +++ b/packages/web_shell_core/lib/src/services/media_service.dart @@ -9,7 +9,7 @@ Future _pickCameraImage( bool showPermissionAlert = false, WebViewController? controller, }) async { - final PermissionStatus cameraStatus = await Permission.camera.request(); + final cameraStatus = await Permission.camera.request(); if (!cameraStatus.isGranted) { if (showPermissionAlert && controller != null) { await _showWebAlert(controller, '请先在系统设置中允许相机权限'); @@ -20,13 +20,12 @@ Future _pickCameraImage( try { return await imagePicker.pickImage( source: ImageSource.camera, - preferredCameraDevice: CameraDevice.rear, imageQuality: _pickedImageQuality, maxWidth: _pickedImageMaxWidth, maxHeight: _pickedImageMaxHeight, ); - } catch (error, stackTrace) { - debugPrint('Pick camera image failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('调用相机拍照失败:$error\n$stackTrace'); if (showPermissionAlert && controller != null) { await _showWebAlert(controller, '无法打开系统相机,请稍后重试'); } @@ -38,14 +37,13 @@ Future>> _serializeXFiles( List files, { required String responseType, }) async { - final bool includeBase64 = - responseType == 'base64' || responseType == 'dataUrl'; - final bool includeDataUrl = responseType == 'dataUrl'; + final includeBase64 = responseType == 'base64' || responseType == 'dataUrl'; + final includeDataUrl = responseType == 'dataUrl'; - final List> serialized = >[]; - for (final XFile file in files) { + final serialized = >[]; + for (final file in files) { String? base64Value; - final String mimeType = _guessMimeType(file.name); + final mimeType = _guessMimeType(file.name); if (includeBase64 || includeDataUrl) { base64Value = base64Encode(await file.readAsBytes()); @@ -68,13 +66,12 @@ Future>> _serializePlatformFiles( List files, { required String responseType, }) async { - final bool includeBase64 = - responseType == 'base64' || responseType == 'dataUrl'; - final bool includeDataUrl = responseType == 'dataUrl'; + final includeBase64 = responseType == 'base64' || responseType == 'dataUrl'; + final includeDataUrl = responseType == 'dataUrl'; - final List> serialized = >[]; - for (final PlatformFile file in files) { - final String mimeType = _guessMimeType(file.name); + final serialized = >[]; + for (final file in files) { + final mimeType = _guessMimeType(file.name); String? base64Value; if (includeBase64 || includeDataUrl) { @@ -111,7 +108,7 @@ bool _acceptsImages(List acceptTypes) { } bool _acceptsOnlyImages(List acceptTypes) { - final List normalizedTypes = acceptTypes + final normalizedTypes = acceptTypes .map((type) => type.trim()) .where((type) => type.isNotEmpty) .toList(); @@ -122,7 +119,7 @@ bool _acceptsOnlyImages(List acceptTypes) { } bool _isImageAcceptType(String acceptType) { - final String value = acceptType.toLowerCase(); + final value = acceptType.toLowerCase(); return value.startsWith('image/') || const { '.png', @@ -137,7 +134,7 @@ bool _isImageAcceptType(String acceptType) { } String _guessMimeType(String fileName) { - final String lower = fileName.toLowerCase(); + final lower = fileName.toLowerCase(); if (lower.endsWith('.png')) { return 'image/png'; } @@ -174,16 +171,16 @@ String _guessMimeType(String fileName) { Future _showWebAlert(WebViewController controller, String message) async { try { await controller.runJavaScript('window.alert(${jsonEncode(message)});'); - } catch (error, stackTrace) { - debugPrint('Show web alert failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('展示网页弹窗失败:$error\n$stackTrace'); } } bool _boolValue(Object? value, {bool defaultValue = false}) { return switch (value) { - bool boolValue => boolValue, - String stringValue => stringValue.toLowerCase() == 'true', - int intValue => intValue != 0, + final bool boolValue => boolValue, + final String stringValue => stringValue.toLowerCase() == 'true', + final int intValue => intValue != 0, _ => defaultValue, }; } diff --git a/packages/web_shell_core/lib/src/services/navigation_service.dart b/packages/web_shell_core/lib/src/services/navigation_service.dart index e446c75..412505b 100644 --- a/packages/web_shell_core/lib/src/services/navigation_service.dart +++ b/packages/web_shell_core/lib/src/services/navigation_service.dart @@ -15,8 +15,8 @@ bool _shouldStayInWebView(Uri uri) { Future _openExternalUri(Uri uri) async { try { return await launchUrl(uri, mode: LaunchMode.externalApplication); - } catch (error, stackTrace) { - debugPrint('Open external uri failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('外部打开 URI 失败:$error\n$stackTrace'); return false; } } diff --git a/packages/web_shell_core/lib/src/testing/test_hooks.dart b/packages/web_shell_core/lib/src/testing/test_hooks.dart new file mode 100644 index 0000000..93c20f2 --- /dev/null +++ b/packages/web_shell_core/lib/src/testing/test_hooks.dart @@ -0,0 +1,157 @@ +part of '../../core_app.dart'; + +/// `web_shell_core` 内部逻辑的测试入口。 +@visibleForTesting +const shellCoreTestHooks = ShellCoreTestHooks(); + +/// 对私有实现的测试封装,避免把内部细节暴露成正式 API。 +@visibleForTesting +class ShellCoreTestHooks { + /// 创建 `web_shell_core` 的测试钩子封装。 + const ShellCoreTestHooks(); + + /// 返回当前初始地址字符串。 + String get initialUrl => _initialUrl; + + /// 返回当前初始 URI。 + Uri get initialUri => _initialUri; + + /// 为测试初始化全局壳环境与初始地址。 + void initializeEnvironment(ShellEnvironment environment) { + _env = environment; + _initializeUrls(); + } + + /// 通过与运行时一致的代码路径进入沉浸式模式。 + Future enterImmersiveMode() => _enterImmersiveMode(); + + /// 判断哪些平台支持内嵌 WebView。 + bool supportsEmbeddedWebView({ + bool isWeb = false, + TargetPlatform? platform, + }) { + return _supportsEmbeddedWebView(isWeb: isWeb, platform: platform); + } + + /// 判断给定地址是否应视为网络地址。 + bool isNetworkUrl(String? url) => _isNetworkUrl(url); + + /// 将 URI 归一化为可比较的字符串形式。 + String normalizeComparableUri(Uri uri) => _normalizeComparableUri(uri); + + /// 返回指定 URI 协议对应的默认端口。 + int defaultPortForScheme(String scheme) => _defaultPortForScheme(scheme); + + /// 归一化路径,供 URI 比较使用。 + String normalizeComparablePath(String path) => _normalizeComparablePath(path); + + /// 返回桥接初始化脚本。 + String buildAppShellBridgeScript() => _buildAppShellBridgeScript(); + + /// 返回旧相机兼容脚本。 + String get legacyCameraCompatScript => _legacyCameraCompatScript; + + /// 向当前页面注入桥接脚本。 + Future injectAppShellBridge(WebViewController controller, String url) { + return _injectAppShellBridge(controller, url); + } + + /// 向当前页面发送桥接响应。 + Future sendBridgeResponse( + WebViewController controller, { + required String requestId, + required bool success, + Object? data, + String? error, + }) { + return _sendBridgeResponse( + controller, + requestId: requestId, + success: success, + data: data, + error: error, + ); + } + + /// 将 WebView 资源错误映射为面向用户的标题。 + String friendlyErrorTitle(WebResourceError error) { + return _friendlyErrorTitle(error); + } + + /// 将 WebView 资源错误映射为面向用户的提示文案。 + String friendlyErrorMessage(WebResourceError error) { + return _friendlyErrorMessage(error); + } + + /// 将选中的图片文件序列化为桥接负载格式。 + Future>> serializeXFiles( + List files, { + required String responseType, + }) { + return _serializeXFiles(files, responseType: responseType); + } + + /// 将平台文件序列化为桥接负载格式。 + Future>> serializePlatformFiles( + List files, { + required String responseType, + }) { + return _serializePlatformFiles(files, responseType: responseType); + } + + /// 执行桥接与文件选择器共用的相机取图流程。 + Future pickCameraImage( + ImagePicker imagePicker, { + bool showPermissionAlert = false, + WebViewController? controller, + }) { + return _pickCameraImage( + imagePicker, + showPermissionAlert: showPermissionAlert, + controller: controller, + ); + } + + /// 将选中的文件转换成 `file://` URI 字符串。 + List xFilesToUriStrings(List files) { + return _xFilesToUriStrings(files); + } + + /// 判断传入的 accept 列表是否允许图片。 + bool acceptsImages(List acceptTypes) => _acceptsImages(acceptTypes); + + /// 判断传入的 accept 列表是否只允许图片。 + bool acceptsOnlyImages(List acceptTypes) { + return _acceptsOnlyImages(acceptTypes); + } + + /// 判断单个 accept 标记是否应视为图片类型。 + bool isImageAcceptType(String acceptType) => _isImageAcceptType(acceptType); + + /// 根据文件名推断 MIME 类型。 + String guessMimeType(String fileName) => _guessMimeType(fileName); + + /// 在当前页面中显示网页弹窗。 + Future showWebAlert(WebViewController controller, String message) { + return _showWebAlert(controller, message); + } + + /// 按桥接语义将动态值转换为布尔值。 + bool boolValue(Object? value, {bool defaultValue = false}) { + return _boolValue(value, defaultValue: defaultValue); + } + + /// 判断 URI 是否应继续留在内嵌 WebView 中。 + bool shouldStayInWebView(Uri uri) => _shouldStayInWebView(uri); + + /// 使用外部应用打开指定 URI。 + Future openExternalUri(Uri uri) => _openExternalUri(uri); + + /// 将逻辑权限类型映射为 `permission_handler` 中的权限对象。 + Permission? permissionForType(String type) => _permissionForType(type); + + /// 从版本名中解析 WebView 主版本号。 + int? parseWebViewMajorVersion(String? versionName) { + return _parseWebViewMajorVersion(versionName); + } +} diff --git a/packages/web_shell_core/lib/src/ui/error_overlay.dart b/packages/web_shell_core/lib/src/ui/error_overlay.dart index 745182b..49c6a25 100644 --- a/packages/web_shell_core/lib/src/ui/error_overlay.dart +++ b/packages/web_shell_core/lib/src/ui/error_overlay.dart @@ -1,17 +1,26 @@ part of '../../core_app.dart'; +/// 页面加载失败时显示的全屏错误浮层。 class ErrorOverlay extends StatelessWidget { + /// 创建当前页面的错误浮层。 const ErrorOverlay({ - super.key, required this.title, required this.message, required this.currentUrl, required this.onRetry, + super.key, }); + /// 展示给用户的错误标题。 final String title; + + /// 展示给用户的错误说明。 final String message; + + /// 当前失败状态对应的地址。 final String currentUrl; + + /// 点击重试按钮后触发的回调。 final Future Function() onRetry; @override diff --git a/packages/web_shell_core/lib/src/ui/launch_overlay.dart b/packages/web_shell_core/lib/src/ui/launch_overlay.dart index a22a61a..2cd78b5 100644 --- a/packages/web_shell_core/lib/src/ui/launch_overlay.dart +++ b/packages/web_shell_core/lib/src/ui/launch_overlay.dart @@ -1,13 +1,18 @@ part of '../../core_app.dart'; +/// 首屏启动阶段显示的加载浮层。 class LaunchOverlay extends StatelessWidget { + /// 创建壳应用启动阶段的加载浮层。 const LaunchOverlay({ - super.key, required this.progress, required this.hasMeasuredProgress, + super.key, }); + /// 当前加载进度。 final int progress; + + /// 当前进度值是否由 WebView 实际回传。 final bool hasMeasuredProgress; @override diff --git a/packages/web_shell_core/lib/src/ui/progress_bar.dart b/packages/web_shell_core/lib/src/ui/progress_bar.dart index fd53dfd..cb3cfc0 100644 --- a/packages/web_shell_core/lib/src/ui/progress_bar.dart +++ b/packages/web_shell_core/lib/src/ui/progress_bar.dart @@ -1,13 +1,18 @@ part of '../../core_app.dart'; +/// 顶部进度条组件。 class TopProgressBar extends StatelessWidget { + /// 创建顶部进度条组件。 const TopProgressBar({ - super.key, required this.progress, required this.hasMeasuredProgress, + super.key, }); + /// 当前加载进度。 final int progress; + + /// 当前进度值是否由 WebView 实际回传。 final bool hasMeasuredProgress; @override diff --git a/packages/web_shell_core/lib/src/ui/shell_app.dart b/packages/web_shell_core/lib/src/ui/shell_app.dart index 392ef80..bbd5073 100644 --- a/packages/web_shell_core/lib/src/ui/shell_app.dart +++ b/packages/web_shell_core/lib/src/ui/shell_app.dart @@ -1,6 +1,8 @@ part of '../../core_app.dart'; +/// `web_shell_core` 的根应用组件。 class ShellApp extends StatelessWidget { + /// 创建壳应用根组件。 const ShellApp({super.key}); @override diff --git a/packages/web_shell_core/lib/src/ui/shell_page.dart b/packages/web_shell_core/lib/src/ui/shell_page.dart index 07b1ac7..67b0ac8 100644 --- a/packages/web_shell_core/lib/src/ui/shell_page.dart +++ b/packages/web_shell_core/lib/src/ui/shell_page.dart @@ -1,8 +1,11 @@ +// coverage:ignore-file part of '../../core_app.dart'; const MethodChannel _appShellDeviceChannel = MethodChannel('app_shell/device'); +/// 承载核心 WebView 的主页面。 class WebShellPage extends StatefulWidget { + /// 创建主壳页面。 const WebShellPage({super.key}); @override @@ -43,7 +46,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(); @@ -63,7 +66,7 @@ class _WebShellPageState extends State if (_renderModeIndex < 0) { return 0; } - final int maxIndex = _androidCompatibilityPlan.renderModes.length - 1; + final maxIndex = _androidCompatibilityPlan.renderModes.length - 1; if (_renderModeIndex > maxIndex) { return maxIndex; } @@ -91,13 +94,13 @@ class _WebShellPageState extends State } try { - final Map? rawInfo = await _appShellDeviceChannel + final rawInfo = await _appShellDeviceChannel .invokeMapMethod('getAndroidWebViewInfo'); if (rawInfo != null) { _androidWebViewInfo = AndroidWebViewInfo.fromMap(rawInfo); } - } catch (error, stackTrace) { - debugPrint('Load Android WebView info failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('读取 Android WebView 信息失败:$error\n$stackTrace'); } _androidCompatibilityPlan = AndroidCompatibilityPlan.fromInfo( @@ -105,11 +108,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()}', ); } @@ -124,7 +127,7 @@ class _WebShellPageState extends State _hasAppliedCompatibilityPlan = true; if (_configuredRenderMode == _activeRenderMode) { debugPrint( - 'WebShell compatibility plan keeps current WebView ' + 'WebShell 兼容策略保持当前 WebView ' '(${_activeRenderMode.logName})', ); return; @@ -133,12 +136,12 @@ class _WebShellPageState extends State } bool _switchToNextRenderMode() { - final int nextIndex = _safeRenderModeIndex + 1; + final nextIndex = _safeRenderModeIndex + 1; if (nextIndex >= _androidCompatibilityPlan.renderModes.length) { return false; } _renderModeIndex = nextIndex; - debugPrint('WebShell switched render mode to ${_activeRenderMode.logName}'); + debugPrint('WebShell 已切换渲染模式为 ${_activeRenderMode.logName}'); return true; } @@ -146,7 +149,7 @@ class _WebShellPageState extends State if (defaultTargetPlatform != TargetPlatform.android) { return ''; } - final List lines = [ + final lines = [ '当前渲染策略:${_activeRenderMode.displayName}', if (_androidWebViewInfo?.model case final String model when model.isNotEmpty) @@ -163,32 +166,26 @@ class _WebShellPageState extends State // ── WebView 创建 ── void _recreateWebView() { - final PlatformWebViewControllerCreationParams controllerParams = - const PlatformWebViewControllerCreationParams(); - final WebViewController controller = - WebViewController.fromPlatformCreationParams(controllerParams); - final int generation = ++_webViewGeneration; - final AndroidRenderMode renderMode = _activeRenderMode; + const controllerParams = PlatformWebViewControllerCreationParams(); + final controller = WebViewController.fromPlatformCreationParams( + controllerParams, + ); + final generation = ++_webViewGeneration; + final renderMode = _activeRenderMode; _controller = controller; _controllerSetupFuture = _configureController(controller, generation); _configuredRenderMode = renderMode; debugPrint( - 'WebShell recreate WebView #$generation with ${renderMode.logName}', + 'WebShell 正在以 ${renderMode.logName} 重建 WebView #$generation', + ); + var widgetParams = PlatformWebViewWidgetCreationParams( + key: ValueKey('webview-$generation-${renderMode.logName}'), + controller: controller.platform, ); - PlatformWebViewWidgetCreationParams widgetParams = - PlatformWebViewWidgetCreationParams( - key: ValueKey('webview-$generation-${renderMode.logName}'), - controller: controller.platform, - layoutDirection: TextDirection.ltr, - ); if (defaultTargetPlatform == TargetPlatform.android) { - widgetParams = - AndroidWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams( - widgetParams, - displayWithHybridComposition: renderMode.usesHybridComposition, - ); + widgetParams = _buildAndroidWidgetParams(widgetParams, renderMode); } _webViewWidget = WebViewWidget.fromPlatformCreationParams( @@ -200,6 +197,18 @@ class _WebShellPageState extends State return generation == _webViewGeneration; } + PlatformWebViewWidgetCreationParams _buildAndroidWidgetParams( + PlatformWebViewWidgetCreationParams widgetParams, + AndroidRenderMode renderMode, + ) { + const createParams = AndroidWebViewWidgetCreationParams + .fromPlatformWebViewWidgetCreationParams; + return createParams( + widgetParams, + displayWithHybridComposition: renderMode.usesHybridComposition, + ); + } + Future _configureController( WebViewController controller, int generation, @@ -209,7 +218,7 @@ class _WebShellPageState extends State await controller.setBackgroundColor(Colors.white); await controller.addJavaScriptChannel( _appShellBridgeChannel, - onMessageReceived: (JavaScriptMessage message) { + onMessageReceived: (message) { if (!_isActiveWebViewGeneration(generation)) { return; } @@ -221,8 +230,7 @@ class _WebShellPageState extends State ); if (controller.platform is AndroidWebViewController) { - final AndroidWebViewController androidController = - controller.platform as AndroidWebViewController; + final androidController = controller.platform as AndroidWebViewController; await AndroidWebViewController.enableDebugging( kDebugMode && !_androidCompatibilityPlan.prefersAggressiveRecovery, @@ -245,7 +253,7 @@ class _WebShellPageState extends State ); await androidController.setOnConsoleMessage((message) { debugPrint( - 'WebView console [${message.level.name}] ${message.message}', + 'WebView 控制台 [${message.level.name}] ${message.message}', ); }); } @@ -253,11 +261,14 @@ class _WebShellPageState extends State NavigationDelegate _buildNavigationDelegate(int generation) { return NavigationDelegate( - onProgress: (int progress) { + onProgress: (progress) { if (!_isActiveWebViewGeneration(generation)) { return; } - if (progress == 10 || progress == 30 || progress == 60 || progress == 90) { + if (progress == 10 || + progress == 30 || + progress == 60 || + progress == 90) { _recordWebViewEvent('加载进度 $progress%'); } if (!mounted) { @@ -270,17 +281,17 @@ class _WebShellPageState extends State } }); }, - onNavigationRequest: (NavigationRequest request) async { + onNavigationRequest: (request) async { if (!_isActiveWebViewGeneration(generation)) { return NavigationDecision.navigate; } return _handleNavigationRequest(request); }, - onUrlChange: (UrlChange change) { + onUrlChange: (change) { if (!_isActiveWebViewGeneration(generation)) { return; } - final String? url = change.url; + final url = change.url; if (url == null || url.isEmpty) { return; } @@ -297,7 +308,7 @@ class _WebShellPageState extends State return; } if (!_isNetworkUrl(url)) { - debugPrint('WebView ignore non-network page start: $url'); + debugPrint('WebView 忽略非网络页面开始事件:$url'); return; } _hasStartedRemoteMainFrame = true; @@ -323,7 +334,7 @@ class _WebShellPageState extends State return; } if (!_isNetworkUrl(url)) { - debugPrint('WebView ignore non-network page finish: $url'); + debugPrint('WebView 忽略非网络页面完成事件:$url'); return; } _recordWebViewEvent('页面加载完成:$url'); @@ -342,21 +353,21 @@ class _WebShellPageState extends State _startupRetryCount = 0; }); }, - onHttpError: (HttpResponseError error) { + onHttpError: (error) { if (!_isActiveWebViewGeneration(generation)) { return; } - final int? statusCode = error.response?.statusCode; - final Uri? requestUri = error.request?.uri; + final statusCode = error.response?.statusCode; + final 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', @@ -392,7 +403,7 @@ class _WebShellPageState extends State @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { - _enterImmersiveMode(); + unawaited(_enterImmersiveMode()); } } @@ -413,7 +424,7 @@ class _WebShellPageState extends State if (!mounted) { return; } - debugPrint('WebShell first frame ready, start initial load'); + debugPrint('WebShell 首帧已就绪,开始初始加载'); await _startLoadSequence(rebuildWebView: false, resetRetryCount: true); } @@ -428,12 +439,12 @@ class _WebShellPageState extends State Future _handleNavigationRequest( NavigationRequest request, ) async { - final Uri? uri = Uri.tryParse(request.url); + final uri = Uri.tryParse(request.url); if (uri == null || _shouldStayInWebView(uri)) { return NavigationDecision.navigate; } - final bool opened = await _openExternalUri(uri); + final opened = await _openExternalUri(uri); if (!opened) { await _showWebAlert(_controller, '无法打开外部应用:${uri.scheme}'); } @@ -442,7 +453,7 @@ class _WebShellPageState extends State void _recordWebViewEvent(String event) { _lastWebViewEvent = event; - debugPrint('WebView $event'); + debugPrint('WebView 事件:$event'); } // ── 看门狗 ── @@ -467,13 +478,14 @@ class _WebShellPageState extends State return; } - final bool switchedRenderMode = _switchToNextRenderMode(); - final int maxRetryCount = - _androidCompatibilityPlan.prefersAggressiveRecovery ? 2 : 1; + final switchedRenderMode = _switchToNextRenderMode(); + final maxRetryCount = _androidCompatibilityPlan.prefersAggressiveRecovery + ? 2 + : 1; if (switchedRenderMode || _startupRetryCount < maxRetryCount) { - final int nextRetryCount = _startupRetryCount + 1; - final String recoveryAction = switchedRenderMode + final nextRetryCount = _startupRetryCount + 1; + final recoveryAction = switchedRenderMode ? '切换到${_activeRenderMode.displayName}' : '深度清理 WebView 状态'; _recordWebViewEvent('启动超时,$recoveryAction 并自动重试第 $nextRetryCount 次'); @@ -514,29 +526,29 @@ class _WebShellPageState extends State } Future _recoverFromBrokenStartupState({bool deepReset = false}) async { - final Future controllerSetupFuture = _controllerSetupFuture; + final controllerSetupFuture = _controllerSetupFuture; try { await controllerSetupFuture; - } catch (error, stackTrace) { - debugPrint('Await WebView controller setup failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('等待 WebView 控制器初始化失败:$error\n$stackTrace'); } try { await _controller.clearCache(); - } catch (error, stackTrace) { - debugPrint('Clear WebView cache failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('清理 WebView 缓存失败:$error\n$stackTrace'); } try { await _controller.clearLocalStorage(); - } catch (error, stackTrace) { - debugPrint('Clear WebView local storage failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('清理 WebView 本地存储失败:$error\n$stackTrace'); } try { await _cookieManager.clearCookies(); - } catch (error, stackTrace) { - debugPrint('Clear WebView cookies failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('清理 WebView Cookie 失败:$error\n$stackTrace'); } if (!deepReset || defaultTargetPlatform != TargetPlatform.android) { @@ -547,20 +559,20 @@ class _WebShellPageState extends State await _appShellDeviceChannel.invokeMethod( 'resetAndroidWebViewState', ); - } catch (error, stackTrace) { - debugPrint('Reset Android WebView state failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('重置 Android WebView 状态失败:$error\n$stackTrace'); } } bool _shouldTreatHttpErrorAsMainFrame(HttpResponseError error) { - final Uri? requestUri = error.request?.uri; + final requestUri = error.request?.uri; if (requestUri == null) { return true; } if (!_isNetworkUrl(requestUri.toString())) { return false; } - final Uri? currentUri = Uri.tryParse(_currentUrl); + final currentUri = Uri.tryParse(_currentUrl); if (_isSameDocumentRequest(requestUri, currentUri)) { return true; } @@ -580,7 +592,7 @@ class _WebShellPageState extends State // ── Bridge 消息处理 ── Future _handleBridgeMessage(String rawMessage) async { - String requestId = ''; + var requestId = ''; try { final dynamic decoded = jsonDecode(rawMessage); @@ -588,10 +600,10 @@ class _WebShellPageState extends State return; } - final Map message = Map.from(decoded); + final message = Map.from(decoded); requestId = (message['requestId'] ?? '').toString(); - final String action = (message['action'] ?? '').toString(); - final Map payload = message['payload'] is Map + final action = (message['action'] ?? '').toString(); + final payload = message['payload'] is Map ? Map.from(message['payload'] as Map) : {}; @@ -599,7 +611,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) { @@ -625,7 +637,11 @@ class _WebShellPageState extends State case 'goBack': data = await _goBackFromBridge(); case 'closeApp': - await _sendBridgeResponse(_controller, requestId: requestId, success: true); + await _sendBridgeResponse( + _controller, + requestId: requestId, + success: true, + ); await SystemNavigator.pop(); return; default: @@ -638,8 +654,8 @@ class _WebShellPageState extends State success: true, data: data, ); - } catch (error, stackTrace) { - debugPrint('Handle AppShell bridge failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('处理 AppShell 桥接请求失败:$error\n$stackTrace'); if (requestId.isNotEmpty) { await _sendBridgeResponse( _controller, @@ -655,14 +671,13 @@ class _WebShellPageState extends State required ImageSource source, required Map payload, }) async { - final bool multiple = + final multiple = source == ImageSource.gallery && _boolValue(payload['multiple']); - final String responseType = (payload['responseType'] ?? 'dataUrl') - .toString(); + final responseType = (payload['responseType'] ?? 'dataUrl').toString(); - List files = []; + var files = []; if (source == ImageSource.camera) { - final XFile? file = await _pickCameraImage( + final file = await _pickCameraImage( _imagePicker, showPermissionAlert: true, controller: _controller, @@ -677,7 +692,7 @@ class _WebShellPageState extends State maxHeight: _pickedImageMaxHeight, ); } else { - final XFile? file = await _imagePicker.pickImage( + final file = await _imagePicker.pickImage( source: ImageSource.gallery, imageQuality: _pickedImageQuality, maxWidth: _pickedImageMaxWidth, @@ -688,7 +703,7 @@ class _WebShellPageState extends State } } - final List> serialized = await _serializeXFiles( + final serialized = await _serializeXFiles( files, responseType: responseType, ); @@ -696,21 +711,19 @@ class _WebShellPageState extends State } Future _pickFilesFromBridge(Map payload) async { - final String responseType = (payload['responseType'] ?? 'uri').toString(); - final bool includeBinary = - responseType == 'dataUrl' || responseType == 'base64'; + final responseType = (payload['responseType'] ?? 'uri').toString(); + final includeBinary = responseType == 'dataUrl' || responseType == 'base64'; - final FilePickerResult? result = await FilePicker.platform.pickFiles( + final result = await FilePicker.platform.pickFiles( allowMultiple: _boolValue(payload['multiple']), withData: includeBinary, - type: FileType.any, ); if (result == null) { return _boolValue(payload['multiple']) ? >[] : null; } - final List> serialized = await _serializePlatformFiles( + final serialized = await _serializePlatformFiles( result.files, responseType: responseType, ); @@ -720,8 +733,8 @@ class _WebShellPageState extends State } Future _openExternalFromBridge(Map payload) async { - final String url = (payload['url'] ?? '').toString(); - final Uri? uri = Uri.tryParse(url); + final url = (payload['url'] ?? '').toString(); + final uri = Uri.tryParse(url); if (uri == null) { return false; } @@ -734,12 +747,11 @@ class _WebShellPageState extends State Future> _requestPermissionsFromBridge( Map payload, ) async { - final List types = - (payload['types'] as List? ?? const []) - .map((type) => type.toString()) - .toList(); + final types = (payload['types'] as List? ?? const []) + .map((type) => type.toString()) + .toList(); - final Map permissions = { + final permissions = { for (final String type in types) if (_permissionForType(type) case final Permission permission) type: permission, @@ -749,10 +761,7 @@ class _WebShellPageState extends State return {}; } - final Map statuses = await permissions.values - .toSet() - .toList() - .request(); + final statuses = await permissions.values.toSet().toList().request(); return { for (final MapEntry entry in permissions.entries) @@ -772,7 +781,7 @@ class _WebShellPageState extends State Future> _handleFileSelector(FileSelectorParams params) async { debugPrint( - 'WebView file selector: ' + 'WebView 文件选择: ' 'accept=${params.acceptTypes}, ' 'capture=${params.isCaptureEnabled}, ' 'mode=${params.mode.name}', @@ -783,11 +792,11 @@ class _WebShellPageState extends State } try { - final bool acceptsImgs = _acceptsImages(params.acceptTypes); - final bool imagesOnly = _acceptsOnlyImages(params.acceptTypes); + final acceptsImgs = _acceptsImages(params.acceptTypes); + final imagesOnly = _acceptsOnlyImages(params.acceptTypes); if (params.isCaptureEnabled && acceptsImgs) { - final XFile? capturedImage = await _pickCameraImage(_imagePicker); + final capturedImage = await _pickCameraImage(_imagePicker); return _xFilesToUriStrings( capturedImage == null ? const [] : [capturedImage], ); @@ -795,7 +804,7 @@ class _WebShellPageState extends State if (imagesOnly) { if (params.mode == FileSelectorMode.openMultiple) { - final List images = await _imagePicker.pickMultiImage( + final images = await _imagePicker.pickMultiImage( imageQuality: _pickedImageQuality, maxWidth: _pickedImageMaxWidth, maxHeight: _pickedImageMaxHeight, @@ -803,7 +812,7 @@ class _WebShellPageState extends State return _xFilesToUriStrings(images); } - final XFile? image = await _imagePicker.pickImage( + final image = await _imagePicker.pickImage( source: ImageSource.gallery, imageQuality: _pickedImageQuality, maxWidth: _pickedImageMaxWidth, @@ -814,9 +823,8 @@ class _WebShellPageState extends State ); } - final FilePickerResult? result = await FilePicker.platform.pickFiles( + final result = await FilePicker.platform.pickFiles( allowMultiple: params.mode == FileSelectorMode.openMultiple, - type: FileType.any, ); if (result == null) { @@ -828,8 +836,8 @@ class _WebShellPageState extends State .whereType() .map((path) => Uri.file(path).toString()) .toList(); - } catch (error, stackTrace) { - debugPrint('Handle file selector failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('处理文件选择失败:$error\n$stackTrace'); return []; } } @@ -840,11 +848,11 @@ class _WebShellPageState extends State PlatformWebViewPermissionRequest request, ) async { debugPrint( - 'WebView permission request: ' + 'WebView 权限请求:' '${request.types.map((type) => type.name).join(', ')}', ); - final List permissions = [ + final permissions = [ if (request.types.contains(WebViewPermissionResourceType.camera)) Permission.camera, if (request.types.contains(WebViewPermissionResourceType.microphone)) @@ -856,9 +864,8 @@ class _WebShellPageState extends State return; } - final Map statuses = await permissions - .request(); - final bool allGranted = statuses.values.every((status) => status.isGranted); + final statuses = await permissions.request(); + final allGranted = statuses.values.every((status) => status.isGranted); if (allGranted) { await request.grant(); @@ -871,8 +878,8 @@ class _WebShellPageState extends State Future _handleGeolocationPermissionRequest( GeolocationPermissionsRequestParams request, ) async { - debugPrint('WebView geolocation permission request: ${request.origin}'); - final PermissionStatus status = await Permission.location.request(); + debugPrint('WebView 地理位置权限请求:${request.origin}'); + final status = await Permission.location.request(); return GeolocationPermissionsResponse( allow: status.isGranted, retain: status.isGranted, @@ -893,8 +900,8 @@ class _WebShellPageState extends State _recreateWebView(); } - final int generation = _webViewGeneration; - final Future controllerSetupFuture = _controllerSetupFuture; + final generation = _webViewGeneration; + final controllerSetupFuture = _controllerSetupFuture; _hasStartedRemoteMainFrame = false; setState(() { @@ -918,14 +925,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'); + } on Object catch (error, stackTrace) { + debugPrint('初始地址加载失败:$error\n$stackTrace'); _setMainFrameError( title: '地址无法加载', message: '无法打开 $_initialUrl,请检查地址格式或网络后重试。', @@ -934,15 +941,15 @@ class _WebShellPageState extends State } Future _waitForWebViewMount(int generation) async { - final bool isAndroidDebug = + final isAndroidDebug = kDebugMode && defaultTargetPlatform == TargetPlatform.android; - final bool usesHybridComposition = + final usesHybridComposition = defaultTargetPlatform == TargetPlatform.android && _activeRenderMode.usesHybridComposition; - final int framesToWait = isAndroidDebug + final framesToWait = isAndroidDebug ? (usesHybridComposition ? 6 : 4) : (usesHybridComposition ? 2 : 1); - final Duration settleDelay = isAndroidDebug + final settleDelay = isAndroidDebug ? (usesHybridComposition ? const Duration(milliseconds: 250) : const Duration(milliseconds: 140)) @@ -950,7 +957,7 @@ class _WebShellPageState extends State ? const Duration(milliseconds: 80) : const Duration(milliseconds: 40)); - for (int index = 0; index < framesToWait; index += 1) { + for (var index = 0; index < framesToWait; index += 1) { await SchedulerBinding.instance.endOfFrame; if (!mounted || !_isActiveWebViewGeneration(generation)) { return; @@ -988,8 +995,8 @@ class _WebShellPageState extends State try { _armStartupWatchdog(); await _controller.reload(); - } catch (error, stackTrace) { - debugPrint('Reload current page failed: $error\n$stackTrace'); + } on Object catch (error, stackTrace) { + debugPrint('重新加载当前页面失败:$error\n$stackTrace'); await _startLoadSequence(rebuildWebView: true, resetRetryCount: true); } } @@ -998,9 +1005,9 @@ class _WebShellPageState extends State @override Widget build(BuildContext context) { - final bool showProgressBar = + final showProgressBar = _isLoadingPage && (!_hasMeasuredProgress || _progress < 100); - final bool showLaunchOverlay = !_hasBootstrapped && !_hasMainFrameError; + final showLaunchOverlay = !_hasBootstrapped && !_hasMainFrameError; return PopScope( canPop: false, diff --git a/packages/web_shell_core/lib/src/ui/unsupported_platform_page.dart b/packages/web_shell_core/lib/src/ui/unsupported_platform_page.dart index 06cc222..d7068f6 100644 --- a/packages/web_shell_core/lib/src/ui/unsupported_platform_page.dart +++ b/packages/web_shell_core/lib/src/ui/unsupported_platform_page.dart @@ -1,6 +1,8 @@ part of '../../core_app.dart'; +/// 非 Android 平台上的兜底提示页。 class UnsupportedPlatformPage extends StatelessWidget { + /// 创建不支持平台提示页。 const UnsupportedPlatformPage({super.key}); @override @@ -39,7 +41,7 @@ class UnsupportedPlatformPage extends StatelessWidget { ), const SizedBox(height: 14), Text( - '请在 Android、iOS 或 macOS 上运行当前项目。\n$_initialUrl', + '当前项目仅支持 Android 平板运行。\n$_initialUrl', textAlign: TextAlign.center, style: TextStyle( height: 1.6, diff --git a/packages/web_shell_core/lib/web_shell_core.dart b/packages/web_shell_core/lib/web_shell_core.dart index e890ab6..7b717fe 100644 --- a/packages/web_shell_core/lib/web_shell_core.dart +++ b/packages/web_shell_core/lib/web_shell_core.dart @@ -1,3 +1 @@ - - export 'core_app.dart'; diff --git a/packages/web_shell_core/pubspec.yaml b/packages/web_shell_core/pubspec.yaml index 1ed67bf..e076185 100644 --- a/packages/web_shell_core/pubspec.yaml +++ b/packages/web_shell_core/pubspec.yaml @@ -8,72 +8,27 @@ environment: flutter: '>=3.3.0' dependencies: + file_picker: ^10.3.10 flutter: sdk: flutter + image_picker: ^1.2.1 + permission_handler: ^12.0.1 plugin_platform_interface: ^2.0.2 - cupertino_icons: ^1.0.8 + url_launcher: ^6.3.2 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_lints: ^6.0.0 + image_picker_platform_interface: ^2.11.1 + url_launcher_platform_interface: ^2.3.2 + very_good_analysis: ^10.2.0 + webview_flutter_platform_interface: ^2.14.0 -# 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: - # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) - # which should be registered in the plugin registry. This is required for - # using method channels. - # The Android 'package' specifies package in which the registered class is. - # This is required for using method channels on Android. - # The 'ffiPlugin' specifies that native code should be built and bundled. - # This is required for using `dart:ffi`. - # All these are used by the tooling to maintain consistency when - # adding or updating assets for this project. plugin: platforms: android: package: com.yuanxuan.webshell.core.web_shell_core pluginClass: WebShellCorePlugin - ios: - pluginClass: WebShellCorePlugin - - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/to/asset-from-package - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # To add custom fonts to your plugin package, 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 in packages, see - # https://flutter.dev/to/font-from-package diff --git a/packages/web_shell_core/test/web_shell_core_test.dart b/packages/web_shell_core/test/web_shell_core_test.dart new file mode 100644 index 0000000..3520e66 --- /dev/null +++ b/packages/web_shell_core/test/web_shell_core_test.dart @@ -0,0 +1,1215 @@ +import 'dart:io'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; +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:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +const MethodChannel _platformChannel = SystemChannels.platform; +const _permissionChannel = MethodChannel( + 'flutter.baseflow.com/permissions/methods', +); + +const _testEnvironment = ShellEnvironment( + appName: '测试应用', + appKey: 'test_app', + accentColor: Color(0xFF3ED37B), + backgroundColor: Color(0xFFFFFFFF), + textColor: Color(0xFF1F2937), + mutedTextColor: Color(0xFF6B7280), + initialUrl: 'example.com/login', +); + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late _FakeWebViewPlatform fakeWebViewPlatform; + late _FakeImagePickerPlatform fakeImagePickerPlatform; + late _FakeUrlLauncherPlatform fakeUrlLauncherPlatform; + late List platformCalls; + late bool cameraPermissionGranted; + + setUp(() { + platformCalls = []; + cameraPermissionGranted = true; + fakeWebViewPlatform = _FakeWebViewPlatform(); + fakeImagePickerPlatform = _FakeImagePickerPlatform(); + fakeUrlLauncherPlatform = _FakeUrlLauncherPlatform(); + + WebViewPlatform.instance = fakeWebViewPlatform; + ImagePickerPlatform.instance = fakeImagePickerPlatform; + UrlLauncherPlatform.instance = fakeUrlLauncherPlatform; + shellCoreTestHooks.initializeEnvironment(_testEnvironment); + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_platformChannel, (call) async { + platformCalls.add(call.method); + return null; + }); + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_permissionChannel, (call) async { + if (call.method != 'requestPermissions') { + return null; + } + + final permissions = (call.arguments as List).cast(); + final statusValue = cameraPermissionGranted ? 1 : 0; + return { + for (final permission in permissions) permission: statusValue, + }; + }); + }); + + tearDown(() { + debugDefaultTargetPlatformOverride = null; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_platformChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_permissionChannel, null); + }); + + group('运行时与平台', () { + test('只支持 Android 内嵌 WebView', () { + expect( + shellCoreTestHooks.supportsEmbeddedWebView( + platform: TargetPlatform.android, + ), + isTrue, + ); + expect( + shellCoreTestHooks.supportsEmbeddedWebView( + platform: TargetPlatform.iOS, + ), + isFalse, + ); + expect( + shellCoreTestHooks.supportsEmbeddedWebView( + platform: TargetPlatform.macOS, + ), + isFalse, + ); + expect(shellCoreTestHooks.supportsEmbeddedWebView(isWeb: true), isFalse); + }); + + test('enterImmersiveMode 会透传到 SystemChrome', () async { + await shellCoreTestHooks.enterImmersiveMode(); + + expect(platformCalls, contains('SystemChrome.setEnabledSystemUIMode')); + }); + + testWidgets('runShellApp 会设置系统 UI 并启动应用', (tester) async { + try { + debugDefaultTargetPlatformOverride = TargetPlatform.linux; + + await runShellApp(_testEnvironment); + await tester.pump(); + + expect( + platformCalls, + contains('SystemChrome.setPreferredOrientations'), + ); + expect(platformCalls, contains('SystemChrome.setEnabledSystemUIMode')); + expect(platformCalls, contains('SystemChrome.setSystemUIOverlayStyle')); + expect(find.byType(ShellApp), findsOneWidget); + expect(find.byType(UnsupportedPlatformPage), findsOneWidget); + } finally { + debugDefaultTargetPlatformOverride = null; + } + }); + + testWidgets('ShellApp 在非 Android 下展示兜底页', (tester) async { + try { + debugDefaultTargetPlatformOverride = TargetPlatform.windows; + + await tester.pumpWidget(const ShellApp()); + + expect(find.byType(UnsupportedPlatformPage), findsOneWidget); + expect(find.text('当前平台不支持内嵌 WebView'), findsOneWidget); + } finally { + debugDefaultTargetPlatformOverride = null; + } + }); + + testWidgets('ShellApp 在 Android 下展示 WebView 容器', (tester) async { + try { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + + await tester.pumpWidget(const ShellApp()); + await tester.pump(); + + expect( + find.byKey(const ValueKey('fake-webview')), + findsOneWidget, + ); + expect(fakeWebViewPlatform.createdControllers, isNotEmpty); + } finally { + debugDefaultTargetPlatformOverride = null; + } + }); + }); + + group('URL 与兼容策略', () { + test('初始化 URL 时会补全 https 协议', () { + expect(shellCoreTestHooks.initialUrl, 'https://example.com/login'); + expect( + shellCoreTestHooks.initialUri, + Uri.parse('https://example.com/login'), + ); + }); + + test('初始化 URL 会处理空值、协议相对地址和非法地址', () { + shellCoreTestHooks.initializeEnvironment( + const ShellEnvironment( + appName: '空地址', + appKey: 'empty', + accentColor: Color(0xFF000000), + backgroundColor: Color(0xFFFFFFFF), + textColor: Color(0xFF000000), + mutedTextColor: Color(0xFF888888), + ), + ); + expect( + shellCoreTestHooks.initialUrl, + 'http://xszy.lzzneng.com/login.html', + ); + + shellCoreTestHooks.initializeEnvironment( + const ShellEnvironment( + appName: '协议相对', + appKey: 'protocol_relative', + accentColor: Color(0xFF000000), + backgroundColor: Color(0xFFFFFFFF), + textColor: Color(0xFF000000), + mutedTextColor: Color(0xFF888888), + initialUrl: '//example.org/index.html', + ), + ); + expect(shellCoreTestHooks.initialUrl, 'https://example.org/index.html'); + + shellCoreTestHooks.initializeEnvironment( + const ShellEnvironment( + appName: '非法地址', + appKey: 'invalid', + accentColor: Color(0xFF000000), + backgroundColor: Color(0xFFFFFFFF), + textColor: Color(0xFF000000), + mutedTextColor: Color(0xFF888888), + initialUrl: '/relative-path', + ), + ); + expect( + shellCoreTestHooks.initialUrl, + 'http://xszy.lzzneng.com/login.html', + ); + + shellCoreTestHooks.initializeEnvironment(_testEnvironment); + }); + + test('网络 URL 与 URI 归一化逻辑正确', () { + expect(shellCoreTestHooks.isNetworkUrl('https://example.com'), isTrue); + expect(shellCoreTestHooks.isNetworkUrl('http://example.com'), isTrue); + expect(shellCoreTestHooks.isNetworkUrl('file:///tmp/demo.txt'), isFalse); + expect(shellCoreTestHooks.defaultPortForScheme('https'), 443); + expect(shellCoreTestHooks.defaultPortForScheme('custom'), -1); + expect(shellCoreTestHooks.normalizeComparablePath(''), '/'); + expect(shellCoreTestHooks.normalizeComparablePath('/demo/'), '/demo'); + expect( + shellCoreTestHooks.normalizeComparableUri( + Uri.parse('https://Example.com/demo/?a=1'), + ), + 'https://example.com:443/demo?a=1', + ); + }); + + test('AndroidWebViewInfo 能从 map 构建并输出摘要', () { + final info = AndroidWebViewInfo.fromMap({ + 'sdkInt': '28', + 'manufacturer': 'Yuanxuan', + 'brand': 'YX', + 'model': 'F136A', + 'webViewDataDirectorySuffix': 'shell', + 'webViewPackageName': 'com.google.android.webview', + 'webViewVersionName': '109.0.0', + 'webViewLongVersionCode': '123', + }); + + expect(info.sdkInt, 28); + expect(info.manufacturer, 'Yuanxuan'); + expect(info.brand, 'YX'); + expect(info.model, 'F136A'); + expect(info.webViewDataDirectorySuffix, 'shell'); + expect(info.webViewPackageName, 'com.google.android.webview'); + expect(info.webViewVersionName, '109.0.0'); + expect(info.webViewLongVersionCode, 123); + expect(info.webViewMajorVersion, 109); + expect(info.isLegacyWebView, isTrue); + expect(info.isF136A, isTrue); + expect(info.summary, contains('sdk=28')); + expect(info.summary, contains('device=Yuanxuan F136A')); + expect(info.summary, contains('webViewVersion=109.0.0')); + }); + + test('AndroidCompatibilityPlan 会根据设备信息产出恢复策略', () { + final info = AndroidWebViewInfo( + sdkInt: 28, + manufacturer: 'YX', + brand: 'YX', + model: 'F136A', + webViewVersionName: '109.0.1', + ); + + final fallbackPlan = AndroidCompatibilityPlan.fallback(); + final plan = AndroidCompatibilityPlan.fromInfo(info); + final nullInfoPlan = AndroidCompatibilityPlan.fromInfo(null); + + expect(fallbackPlan.renderModes, isNotEmpty); + expect(nullInfoPlan.renderModes, isNotEmpty); + expect(plan.renderModes.first, AndroidRenderMode.hybrid); + expect(AndroidRenderMode.texture.displayName, '标准模式'); + expect(AndroidRenderMode.hybrid.displayName, '兼容模式'); + expect(plan.useWideViewPort, isTrue); + expect(plan.suggestWebViewUpdate, isTrue); + expect(plan.prefersAggressiveRecovery, isTrue); + expect(plan.describe(), contains('modes=')); + expect(plan.describe(), contains('aggressiveRecovery=true')); + }); + + test('WebView 主版本解析兼容空值和正常值', () { + expect(shellCoreTestHooks.parseWebViewMajorVersion(null), isNull); + expect(shellCoreTestHooks.parseWebViewMajorVersion(''), isNull); + expect( + shellCoreTestHooks.parseWebViewMajorVersion('122.0.6261.86'), + 122, + ); + }); + }); + + group('Bridge 与错误映射', () { + test('bridge 脚本包含主要能力与兼容层', () { + final script = shellCoreTestHooks.buildAppShellBridgeScript(); + + expect(script, contains('window.AppShell')); + expect(script, contains('pickImage')); + expect(script, contains('captureImage')); + expect(script, contains('pickFile')); + expect(script, contains('requestPermissions')); + expect(script, contains(shellCoreTestHooks.legacyCameraCompatScript)); + }); + + test('bridge 注入、响应与 alert 都会调用 JS', () async { + final platformController = _FakePlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + final controller = WebViewController.fromPlatform(platformController); + + await shellCoreTestHooks.injectAppShellBridge( + controller, + 'https://a.com', + ); + await shellCoreTestHooks.sendBridgeResponse( + controller, + requestId: 'req-1', + success: true, + data: {'ok': true}, + ); + await shellCoreTestHooks.showWebAlert(controller, 'hello'); + + expect(platformController.javaScriptCalls, hasLength(3)); + expect( + platformController.javaScriptCalls.first, + contains('window.AppShell'), + ); + expect( + platformController.javaScriptCalls[1], + contains('window.__appShellReceiveResponse'), + ); + expect(platformController.javaScriptCalls.last, contains('window.alert')); + }); + + test('bridge 注入和 alert 的异常会被吞掉', () async { + final platformController = _FakePlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + )..throwOnRunJavaScript = true; + final controller = WebViewController.fromPlatform(platformController); + + await shellCoreTestHooks.injectAppShellBridge( + controller, + 'https://a.com', + ); + await shellCoreTestHooks.sendBridgeResponse( + controller, + requestId: 'req-2', + success: false, + error: 'boom', + ); + await shellCoreTestHooks.showWebAlert(controller, 'hello'); + + expect(platformController.javaScriptCalls, isEmpty); + }); + + test('错误标题与文案映射正确', () { + const timeoutError = WebResourceError( + errorCode: -1, + description: 'timeout', + errorType: WebResourceErrorType.timeout, + ); + const connectError = WebResourceError( + errorCode: -2, + description: 'connect', + errorType: WebResourceErrorType.connect, + ); + const sslError = WebResourceError( + errorCode: -3, + description: 'ssl', + errorType: WebResourceErrorType.failedSslHandshake, + ); + const customError = WebResourceError( + errorCode: -4, + description: 'custom error', + errorType: WebResourceErrorType.unknown, + ); + + expect(shellCoreTestHooks.friendlyErrorTitle(timeoutError), '请求超时'); + expect(shellCoreTestHooks.friendlyErrorTitle(connectError), '网络连接失败'); + expect(shellCoreTestHooks.friendlyErrorTitle(sslError), '安全连接失败'); + expect(shellCoreTestHooks.friendlyErrorTitle(customError), '页面加载失败'); + + expect( + shellCoreTestHooks.friendlyErrorMessage(timeoutError), + '当前网络较慢,请稍后重新加载。', + ); + expect( + shellCoreTestHooks.friendlyErrorMessage(connectError), + '没有成功连接到服务器,请检查网络后重试。', + ); + expect( + shellCoreTestHooks.friendlyErrorMessage(sslError), + '当前站点证书校验失败,请稍后再试。', + ); + expect( + shellCoreTestHooks.friendlyErrorMessage(customError), + 'custom error', + ); + }); + }); + + group('媒体与权限工具', () { + test('序列化 XFile 与 PlatformFile', () async { + final tempDirectory = await Directory.systemTemp.createTemp('web-shell'); + final file = File('${tempDirectory.path}/demo.txt') + ..writeAsStringSync('hello'); + final binaryFile = File('${tempDirectory.path}/demo.bin') + ..writeAsBytesSync([4, 5, 6]); + addTearDown(() async { + if (tempDirectory.existsSync()) { + await tempDirectory.delete(recursive: true); + } + }); + + final xFile = XFile(file.path, name: 'demo.txt'); + final serializedXFiles = await shellCoreTestHooks.serializeXFiles( + [xFile], + responseType: 'dataUrl', + ); + final serializedPlatformFiles = await shellCoreTestHooks + .serializePlatformFiles( + [ + PlatformFile( + name: 'demo.apk', + size: 3, + bytes: Uint8List.fromList([1, 2, 3]), + ), + PlatformFile( + name: 'demo.bin', + path: binaryFile.path, + size: 3, + ), + ], + responseType: 'dataUrl', + ); + final uriStrings = shellCoreTestHooks.xFilesToUriStrings([xFile]); + + expect(serializedXFiles.single['name'], 'demo.txt'); + expect(serializedXFiles.single['mimeType'], 'text/plain'); + expect(serializedXFiles.single['dataUrl'], startsWith('data:text/plain')); + expect( + serializedPlatformFiles.first['mimeType'], + 'application/vnd.android.package-archive', + ); + expect( + serializedPlatformFiles.first['dataUrl'], + startsWith('data:application/vnd.android.package-archive'), + ); + expect( + serializedPlatformFiles.last['dataUrl'], + startsWith('data:application/octet-stream'), + ); + expect(uriStrings.single, Uri.file(file.path).toString()); + }); + + test('文件 accept、MIME 与布尔转换正确', () { + expect(shellCoreTestHooks.acceptsImages([' image/* ']), isTrue); + expect( + shellCoreTestHooks.acceptsOnlyImages(['.png', '.jpg']), + isTrue, + ); + expect( + shellCoreTestHooks.acceptsOnlyImages(['.png', '.pdf']), + isFalse, + ); + expect(shellCoreTestHooks.isImageAcceptType('.heic'), isTrue); + expect(shellCoreTestHooks.guessMimeType('photo.jpeg'), 'image/jpeg'); + expect(shellCoreTestHooks.guessMimeType('photo.webp'), 'image/webp'); + expect(shellCoreTestHooks.guessMimeType('photo.gif'), 'image/gif'); + expect(shellCoreTestHooks.guessMimeType('photo.bmp'), 'image/bmp'); + expect(shellCoreTestHooks.guessMimeType('photo.heic'), 'image/heic'); + expect(shellCoreTestHooks.guessMimeType('photo.heif'), 'image/heif'); + expect(shellCoreTestHooks.guessMimeType('book.pdf'), 'application/pdf'); + expect( + shellCoreTestHooks.guessMimeType('demo.apk'), + 'application/vnd.android.package-archive', + ); + expect( + shellCoreTestHooks.guessMimeType('file.bin'), + 'application/octet-stream', + ); + expect(shellCoreTestHooks.boolValue(true), isTrue); + expect(shellCoreTestHooks.boolValue('true'), isTrue); + expect(shellCoreTestHooks.boolValue(1), isTrue); + expect( + shellCoreTestHooks.boolValue('other', defaultValue: true), + isFalse, + ); + expect(shellCoreTestHooks.boolValue(0), isFalse); + }); + + test('相机权限通过时会调用 image picker', () async { + final tempDirectory = await Directory.systemTemp.createTemp('camera-ok'); + final file = File('${tempDirectory.path}/camera.jpg') + ..writeAsBytesSync([1, 2, 3]); + addTearDown(() async { + if (tempDirectory.existsSync()) { + await tempDirectory.delete(recursive: true); + } + }); + fakeImagePickerPlatform.nextImage = XFile(file.path, name: 'camera.jpg'); + + final result = await shellCoreTestHooks.pickCameraImage(ImagePicker()); + + expect(result, isNotNull); + expect(result!.path, file.path); + expect(fakeImagePickerPlatform.lastSource, ImageSource.camera); + }); + + test('相机权限拒绝时返回 null 并可提示 Web alert', () async { + cameraPermissionGranted = false; + final platformController = _FakePlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + final controller = WebViewController.fromPlatform(platformController); + + final result = await shellCoreTestHooks.pickCameraImage( + ImagePicker(), + showPermissionAlert: true, + controller: controller, + ); + + expect(result, isNull); + expect( + platformController.javaScriptCalls.single, + contains('请先在系统设置中允许相机权限'), + ); + }); + + test('相机打开失败时返回 null 并可提示 Web alert', () async { + fakeImagePickerPlatform.shouldThrow = true; + final platformController = _FakePlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + final controller = WebViewController.fromPlatform(platformController); + + final result = await shellCoreTestHooks.pickCameraImage( + ImagePicker(), + showPermissionAlert: true, + controller: controller, + ); + + expect(result, isNull); + expect(platformController.javaScriptCalls.single, contains('无法打开系统相机')); + }); + + test('权限类型映射正确', () { + expect(shellCoreTestHooks.permissionForType('camera'), Permission.camera); + expect( + shellCoreTestHooks.permissionForType('microphone'), + Permission.microphone, + ); + expect( + shellCoreTestHooks.permissionForType('audio'), + Permission.microphone, + ); + expect( + shellCoreTestHooks.permissionForType('location'), + Permission.location, + ); + expect(shellCoreTestHooks.permissionForType('photos'), Permission.photos); + expect(shellCoreTestHooks.permissionForType('videos'), Permission.videos); + expect( + shellCoreTestHooks.permissionForType('storage'), + Permission.storage, + ); + expect(shellCoreTestHooks.permissionForType('unknown'), isNull); + }); + }); + + group('导航与组件', () { + test('导航 scheme 判断和外部打开行为正确', () async { + expect( + shellCoreTestHooks.shouldStayInWebView( + Uri.parse('https://example.com'), + ), + isTrue, + ); + expect( + shellCoreTestHooks.shouldStayInWebView( + Uri.parse('javascript:alert(1)'), + ), + isTrue, + ); + expect( + shellCoreTestHooks.shouldStayInWebView(Uri.parse('tel:10086')), + isFalse, + ); + + fakeUrlLauncherPlatform.shouldSucceed = true; + expect( + await shellCoreTestHooks.openExternalUri(Uri.parse('tel:10086')), + isTrue, + ); + expect(fakeUrlLauncherPlatform.lastUrl, 'tel:10086'); + + fakeUrlLauncherPlatform.shouldThrow = true; + expect( + await shellCoreTestHooks.openExternalUri(Uri.parse('weixin://demo')), + isFalse, + ); + }); + + testWidgets('UnsupportedPlatformPage 展示 Android-only 文案', (tester) async { + await tester.pumpWidget( + const MaterialApp(home: UnsupportedPlatformPage()), + ); + + expect(find.text('当前平台不支持内嵌 WebView'), findsOneWidget); + expect(find.textContaining('当前项目仅支持 Android 平板运行。'), findsOneWidget); + expect(find.textContaining('https://example.com/login'), findsOneWidget); + }); + + testWidgets('ErrorOverlay 点击重试会触发回调', (tester) async { + var retried = false; + + await tester.pumpWidget( + MaterialApp( + home: ErrorOverlay( + title: '错误标题', + message: '错误信息', + currentUrl: 'https://example.com', + onRetry: () async { + retried = true; + }, + ), + ), + ); + + expect(find.text('错误标题'), findsOneWidget); + expect(find.text('错误信息'), findsOneWidget); + expect(find.text('https://example.com'), findsOneWidget); + await tester.tap(find.text('重新加载')); + await tester.pump(); + expect(retried, isTrue); + }); + + testWidgets('LaunchOverlay 与 TopProgressBar 能正确渲染进度', (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Column( + children: [ + Expanded( + child: LaunchOverlay(progress: 80, hasMeasuredProgress: true), + ), + TopProgressBar(progress: 50, hasMeasuredProgress: true), + ], + ), + ), + ); + + expect(find.text('页面加载中'), findsOneWidget); + expect(find.byType(LinearProgressIndicator), findsNWidgets(2)); + + final indicators = tester.widgetList( + find.byType(LinearProgressIndicator), + ); + expect(indicators.first.value, 0.8); + expect(indicators.last.value, 0.5); + }); + + testWidgets('LaunchOverlay 未量测进度时显示不确定进度', (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: LaunchOverlay(progress: 0, hasMeasuredProgress: false), + ), + ); + + final indicator = tester.widget( + find.byType(LinearProgressIndicator), + ); + expect(indicator.value, isNull); + }); + + testWidgets('TopProgressBar 未量测进度时显示不确定进度', (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: TopProgressBar(progress: 0, hasMeasuredProgress: false), + ), + ), + ); + + final indicator = tester.widget( + find.byType(LinearProgressIndicator), + ); + expect(indicator.value, isNull); + }); + }); + + group('导航 scheme 边界场景', () { + test('data/blob/about/file 协议留在 WebView 中', () { + expect( + shellCoreTestHooks.shouldStayInWebView( + Uri.parse('data:text/html,

hello

'), + ), + isTrue, + ); + expect( + shellCoreTestHooks.shouldStayInWebView(Uri.parse('about:blank')), + isTrue, + ); + expect( + shellCoreTestHooks.shouldStayInWebView( + Uri.parse('blob:https://example.com/uuid'), + ), + isTrue, + ); + expect( + shellCoreTestHooks.shouldStayInWebView( + Uri.parse('file:///tmp/test.html'), + ), + isTrue, + ); + }); + + test('intent/alipay/weixin/sms 等自定义 scheme 走外部应用', () { + for (final scheme in [ + 'intent://demo', + 'alipays://pay', + 'weixin://dl/business', + 'sms:10086', + 'mailto:test@example.com', + ]) { + expect( + shellCoreTestHooks.shouldStayInWebView(Uri.parse(scheme)), + isFalse, + reason: 'scheme=$scheme should leave WebView', + ); + } + }); + + test('openExternalUri 成功返回 true', () async { + fakeUrlLauncherPlatform + ..shouldSucceed = true + ..shouldThrow = false; + + final result = await shellCoreTestHooks.openExternalUri( + Uri.parse('alipays://pay?orderId=123'), + ); + + expect(result, isTrue); + expect(fakeUrlLauncherPlatform.lastUrl, 'alipays://pay?orderId=123'); + }); + }); + + group('序列化 responseType 边界', () { + test('base64 模式包含 base64 但不包含 dataUrl', () async { + final tempDirectory = await Directory.systemTemp.createTemp('ser-b64'); + final file = File('${tempDirectory.path}/data.txt') + ..writeAsStringSync('abc'); + addTearDown(() async { + if (tempDirectory.existsSync()) { + await tempDirectory.delete(recursive: true); + } + }); + + final result = await shellCoreTestHooks.serializeXFiles( + [XFile(file.path, name: 'data.txt')], + responseType: 'base64', + ); + + expect(result.single['base64'], isNotNull); + expect(result.single.containsKey('dataUrl'), isFalse); + }); + + test('uri 模式不包含 base64 也不包含 dataUrl', () async { + final tempDirectory = await Directory.systemTemp.createTemp('ser-uri'); + final file = File('${tempDirectory.path}/data.txt') + ..writeAsStringSync('abc'); + addTearDown(() async { + if (tempDirectory.existsSync()) { + await tempDirectory.delete(recursive: true); + } + }); + + final result = await shellCoreTestHooks.serializeXFiles( + [XFile(file.path, name: 'data.txt')], + responseType: 'uri', + ); + + expect(result.single.containsKey('base64'), isFalse); + expect(result.single.containsKey('dataUrl'), isFalse); + expect(result.single['uri'], contains('data.txt')); + }); + + test('空文件列表返回空数组', () async { + final xResult = await shellCoreTestHooks.serializeXFiles( + [], + responseType: 'dataUrl', + ); + final pfResult = await shellCoreTestHooks.serializePlatformFiles( + [], + responseType: 'base64', + ); + + expect(xResult, isEmpty); + expect(pfResult, isEmpty); + }); + + test('PlatformFile 无 bytes 也无 path 时 base64 为 null', () async { + final result = await shellCoreTestHooks.serializePlatformFiles( + [PlatformFile(name: 'ghost.dat', size: 0)], + responseType: 'base64', + ); + + expect(result.single['base64'], isNull); + expect(result.single['uri'], isNull); + }); + }); + + group('AndroidCompatibilityPlan 扩展场景', () { + test('现代设备(高 SDK + 新 WebView)优先使用 texture', () { + final info = AndroidWebViewInfo( + sdkInt: 34, + manufacturer: 'Samsung', + brand: 'samsung', + model: 'SM-X810', + webViewVersionName: '122.0.6261.86', + ); + + final plan = AndroidCompatibilityPlan.fromInfo(info); + + expect(info.isLegacyWebView, isFalse); + expect(info.isF136A, isFalse); + expect(plan.renderModes.first, AndroidRenderMode.texture); + expect(plan.suggestWebViewUpdate, isFalse); + expect(plan.prefersAggressiveRecovery, isFalse); + }); + + test('低 SDK(≤28)但非 F136A 也会使用 hybrid 优先', () { + final info = AndroidWebViewInfo( + sdkInt: 26, + manufacturer: 'Huawei', + brand: 'HUAWEI', + model: 'MediaPad T3', + webViewVersionName: '120.0.0', + ); + + final plan = AndroidCompatibilityPlan.fromInfo(info); + + expect(plan.renderModes.first, AndroidRenderMode.hybrid); + expect(plan.suggestWebViewUpdate, isTrue); + expect(plan.prefersAggressiveRecovery, isTrue); + }); + + test('AndroidWebViewInfo 字段缺失时使用安全默认值', () { + final info = AndroidWebViewInfo.fromMap({}); + + expect(info.sdkInt, 0); + expect(info.manufacturer, ''); + expect(info.brand, ''); + expect(info.model, ''); + expect(info.webViewDataDirectorySuffix, isNull); + expect(info.webViewPackageName, isNull); + expect(info.webViewVersionName, isNull); + expect(info.webViewLongVersionCode, isNull); + expect(info.webViewMajorVersion, isNull); + }); + + test('AndroidRenderMode 枚举属性正确', () { + expect(AndroidRenderMode.texture.usesHybridComposition, isFalse); + expect(AndroidRenderMode.hybrid.usesHybridComposition, isTrue); + expect(AndroidRenderMode.texture.logName, 'texture-layer'); + expect(AndroidRenderMode.hybrid.logName, 'hybrid-composition'); + }); + }); + + group('错误映射扩展场景', () { + test('hostLookup 和 io 错误映射为网络连接失败', () { + const hostLookupError = WebResourceError( + errorCode: -10, + description: 'dns lookup failed', + errorType: WebResourceErrorType.hostLookup, + ); + const ioError = WebResourceError( + errorCode: -11, + description: 'io interrupted', + errorType: WebResourceErrorType.io, + ); + + expect(shellCoreTestHooks.friendlyErrorTitle(hostLookupError), '网络连接失败'); + expect(shellCoreTestHooks.friendlyErrorTitle(ioError), '网络连接失败'); + expect( + shellCoreTestHooks.friendlyErrorMessage(hostLookupError), + '没有成功连接到服务器,请检查网络后重试。', + ); + expect( + shellCoreTestHooks.friendlyErrorMessage(ioError), + '没有成功连接到服务器,请检查网络后重试。', + ); + }); + + test('空 description 使用默认文案', () { + const emptyDescError = WebResourceError( + errorCode: -99, + description: ' ', + errorType: WebResourceErrorType.unknown, + ); + + expect( + shellCoreTestHooks.friendlyErrorMessage(emptyDescError), + '请稍后重新加载页面。', + ); + }); + }); + + group('Accept 类型边界场景', () { + test('空 acceptTypes 不接受图片也不仅接受图片', () { + expect(shellCoreTestHooks.acceptsImages([]), isFalse); + expect(shellCoreTestHooks.acceptsOnlyImages([]), isFalse); + }); + + test('仅空白字符的 acceptTypes 视为空', () { + expect(shellCoreTestHooks.acceptsImages([' ', '']), isFalse); + expect(shellCoreTestHooks.acceptsOnlyImages([' ', '']), isFalse); + }); + + test('image/* 通配符被识别为图片', () { + expect(shellCoreTestHooks.isImageAcceptType('image/*'), isTrue); + expect(shellCoreTestHooks.isImageAcceptType('image/png'), isTrue); + expect(shellCoreTestHooks.isImageAcceptType('image/svg+xml'), isTrue); + }); + + test('非图片文件扩展名正确返回 false', () { + expect(shellCoreTestHooks.isImageAcceptType('.pdf'), isFalse); + expect(shellCoreTestHooks.isImageAcceptType('.apk'), isFalse); + expect(shellCoreTestHooks.isImageAcceptType('application/pdf'), isFalse); + }); + + test('混合类型列表正确判断', () { + expect( + shellCoreTestHooks.acceptsImages(['.pdf', '.png']), + isTrue, + ); + expect( + shellCoreTestHooks.acceptsOnlyImages(['.pdf', '.png']), + isFalse, + ); + expect( + shellCoreTestHooks.acceptsImages(['image/*', '.jpg']), + isTrue, + ); + expect( + shellCoreTestHooks.acceptsOnlyImages(['image/*', '.jpg']), + isTrue, + ); + }); + }); + + group('boolValue 边界场景', () { + test('null 与未知类型使用 defaultValue', () { + expect(shellCoreTestHooks.boolValue(null), isFalse); + expect(shellCoreTestHooks.boolValue(null, defaultValue: true), isTrue); + expect(shellCoreTestHooks.boolValue(3.14), isFalse); + expect(shellCoreTestHooks.boolValue([]), isFalse); + }); + + test('字符串 false/TRUE 正确解析', () { + expect(shellCoreTestHooks.boolValue('false'), isFalse); + expect(shellCoreTestHooks.boolValue('TRUE'), isTrue); + expect(shellCoreTestHooks.boolValue('False'), isFalse); + }); + + test('int 非零值为 true', () { + expect(shellCoreTestHooks.boolValue(-1), isTrue); + expect(shellCoreTestHooks.boolValue(42), isTrue); + }); + }); + + group('MIME 类型扩展场景', () { + test('所有已支持扩展名都能正确猜测', () { + expect(shellCoreTestHooks.guessMimeType('FILE.PNG'), 'image/png'); + expect(shellCoreTestHooks.guessMimeType('a.JPG'), 'image/jpeg'); + expect(shellCoreTestHooks.guessMimeType('a.JPEG'), 'image/jpeg'); + expect(shellCoreTestHooks.guessMimeType('a.PDF'), 'application/pdf'); + expect(shellCoreTestHooks.guessMimeType('a.TXT'), 'text/plain'); + }); + + test('无扩展名或未知扩展名返回 octet-stream', () { + expect( + shellCoreTestHooks.guessMimeType('binary'), + 'application/octet-stream', + ); + expect( + shellCoreTestHooks.guessMimeType('data.xyz'), + 'application/octet-stream', + ); + }); + }); + + group('URL 归一化边界场景', () { + test('isNetworkUrl 对 null/空/非法值返回 false', () { + expect(shellCoreTestHooks.isNetworkUrl(null), isFalse); + expect(shellCoreTestHooks.isNetworkUrl(''), isFalse); + expect(shellCoreTestHooks.isNetworkUrl('not a url'), isFalse); + }); + + test('路径归一化处理各种格式', () { + expect(shellCoreTestHooks.normalizeComparablePath('/a/b/'), '/a/b'); + expect(shellCoreTestHooks.normalizeComparablePath('/a/b'), '/a/b'); + expect(shellCoreTestHooks.normalizeComparablePath('/'), '/'); + expect(shellCoreTestHooks.normalizeComparablePath('///'), '//'); + }); + + test('默认端口映射覆盖 http/https/未知', () { + expect(shellCoreTestHooks.defaultPortForScheme('http'), 80); + expect(shellCoreTestHooks.defaultPortForScheme('https'), 443); + expect(shellCoreTestHooks.defaultPortForScheme('ftp'), -1); + expect(shellCoreTestHooks.defaultPortForScheme(''), -1); + }); + + test('URI 归一化保留查询参数并忽略大小写', () { + expect( + shellCoreTestHooks.normalizeComparableUri( + Uri.parse('HTTP://EXAMPLE.COM:80/Path/?q=1'), + ), + 'http://example.com:80/Path?q=1', + ); + }); + }); +} + +class _FakeUrlLauncherPlatform extends UrlLauncherPlatform { + @override + LinkDelegate? get linkDelegate => null; + + String? lastUrl; + bool shouldSucceed = true; + bool shouldThrow = false; + + @override + Future launch( + String url, { + required bool useSafariVC, + required bool useWebView, + required bool enableJavaScript, + required bool enableDomStorage, + required bool universalLinksOnly, + required Map headers, + String? webOnlyWindowName, + }) async { + lastUrl = url; + if (shouldThrow) { + throw Exception('launch failed'); + } + return shouldSucceed; + } +} + +class _FakeImagePickerPlatform extends ImagePickerPlatform { + XFile? nextImage; + bool shouldThrow = false; + ImageSource? lastSource; + + @override + Future getImageFromSource({ + required ImageSource source, + ImagePickerOptions options = const ImagePickerOptions(), + }) async { + lastSource = source; + if (shouldThrow) { + throw PlatformException(code: 'pick-failed'); + } + return nextImage; + } +} + +class _FakeWebViewPlatform extends WebViewPlatform { + final createdControllers = <_FakePlatformWebViewController>[]; + + @override + PlatformNavigationDelegate createPlatformNavigationDelegate( + PlatformNavigationDelegateCreationParams params, + ) { + return _FakePlatformNavigationDelegate(params); + } + + @override + PlatformWebViewController createPlatformWebViewController( + PlatformWebViewControllerCreationParams params, + ) { + final controller = _FakePlatformWebViewController(params); + createdControllers.add(controller); + return controller; + } + + @override + PlatformWebViewWidget createPlatformWebViewWidget( + PlatformWebViewWidgetCreationParams params, + ) { + return _FakePlatformWebViewWidget(params); + } + + @override + PlatformWebViewCookieManager createPlatformCookieManager( + PlatformWebViewCookieManagerCreationParams params, + ) { + return _FakePlatformWebViewCookieManager(params); + } +} + +class _FakePlatformWebViewCookieManager extends PlatformWebViewCookieManager { + _FakePlatformWebViewCookieManager(super.params) : super.implementation(); + + @override + Future clearCookies() async => false; + + @override + Future setCookie(WebViewCookie cookie) async {} +} + +class _FakePlatformNavigationDelegate extends PlatformNavigationDelegate { + _FakePlatformNavigationDelegate(super.params) : super.implementation(); + + @override + Future setOnNavigationRequest( + NavigationRequestCallback onNavigationRequest, + ) async {} + + @override + Future setOnPageStarted(PageEventCallback onPageStarted) async {} + + @override + Future setOnPageFinished(PageEventCallback onPageFinished) async {} + + @override + Future setOnHttpError(HttpResponseErrorCallback onHttpError) async {} + + @override + Future setOnProgress(ProgressCallback onProgress) async {} + + @override + Future setOnWebResourceError( + WebResourceErrorCallback onWebResourceError, + ) async {} + + @override + Future setOnUrlChange(UrlChangeCallback onUrlChange) async {} +} + +class _FakePlatformWebViewController extends PlatformWebViewController { + _FakePlatformWebViewController(super.params) : super.implementation(); + + final javaScriptCalls = []; + PlatformNavigationDelegate? delegate; + bool throwOnRunJavaScript = false; + bool canGoBackValue = false; + bool didGoBack = false; + Uri? lastLoadedUri; + + @override + Future addJavaScriptChannel(JavaScriptChannelParams params) async {} + + @override + Future clearCache() async {} + + @override + Future clearLocalStorage() async {} + + @override + Future canGoBack() async => canGoBackValue; + + @override + Future enableZoom(bool enabled) async {} + + @override + Future goBack() async { + didGoBack = true; + } + + @override + Future loadRequest(LoadRequestParams params) async { + lastLoadedUri = params.uri; + } + + @override + Future reload() async {} + + @override + Future runJavaScript(String javaScript) async { + if (throwOnRunJavaScript) { + throw Exception('javascript failed'); + } + javaScriptCalls.add(javaScript); + } + + @override + Future runJavaScriptReturningResult(String javaScript) async => ''; + + @override + Future setBackgroundColor(Color color) async {} + + @override + Future setJavaScriptMode(JavaScriptMode javaScriptMode) async {} + + @override + Future setPlatformNavigationDelegate( + PlatformNavigationDelegate delegate, + ) async { + this.delegate = delegate; + } +} + +class _FakePlatformWebViewWidget extends PlatformWebViewWidget { + _FakePlatformWebViewWidget(super.params) : super.implementation(); + + @override + Widget build(BuildContext context) { + return const SizedBox(key: ValueKey('fake-webview')); + } +} diff --git a/tool/generate_app.dart b/tool/generate_app.dart index c2dd48f..dae5f9c 100644 --- a/tool/generate_app.dart +++ b/tool/generate_app.dart @@ -3,8 +3,10 @@ import 'package:yaml/yaml.dart'; Future main(List args) async { if (args.isEmpty) { - print('\x1BM[31mUsage: dart run tool/generate_app.dart \x1B[0m'); - print('\x1BM[33mExample: dart run tool/generate_app.dart quanxue\x1B[0m'); + print( + '\x1B[31m用法:dart run tool/generate_app.dart <品牌名>\x1B[0m', + ); + print('\x1B[33m示例:dart run tool/generate_app.dart quanxue\x1B[0m'); exit(1); } @@ -12,121 +14,142 @@ Future main(List args) async { final File configFile = File('flavors/$brand.yaml'); if (!configFile.existsSync()) { - print('\x1BM[31m[Error] Configuration file not found: ${configFile.path}\x1B[0m'); + print( + '\x1B[31m[错误] 未找到配置文件:${configFile.path}\x1B[0m', + ); exit(1); } - print('\x1BM[34m[Info] Generating app for brand: $brand...\x1B[0m'); - - // 1. Parse YAML Configuration + print('\x1B[34m[信息] 正在为品牌生成应用:$brand...\x1B[0m'); + + // 1. 解析 YAML 配置 final String yamlString = await configFile.readAsString(); final YamlMap config = loadYaml(yamlString) as YamlMap; - + final String appName = config['app_name'] as String; final String applicationId = config['application_id'] as String; final String appKey = config['app_key'] as String; - + final YamlMap theme = config['theme'] as YamlMap; final String accentColor = theme['accent_color'] as String; final String bgColor = theme['bg_color'] as String; final String textColor = theme['text_color'] as String; final String mutedTextColor = theme['muted_text_color'] as String; - - final String appDir = 'apps/$brand'; - - print('\x1B[32m✔ Configuration loaded successfully.\x1B[0m'); - // 2. Create the Flutter App + final String appDir = 'apps/$brand'; + + print('\x1B[32m✔ 配置加载完成。\x1B[0m'); + + // 2. 创建 Flutter 应用 await _createFlutterApp(brand, appDir, applicationId); - // 3. Add Core Dependency + // 3. 添加核心依赖 await _addCoreDependency(appDir); - // 4. Overwrite MainActivity.java to extend CoreShellActivity + // 4. 覆盖 MainActivity.java 以继承 CoreShellActivity await _overwriteMainActivity(appDir, applicationId); - // 5. Overwrite AndroidManifest.xml for Label + // 5. 覆盖 AndroidManifest.xml 中的应用名称 await _overwriteManifestLabel(appDir, appName); - // 6. Generate lib/main.dart + // 6. 生成 lib/main.dart await _generateDartEntrypoint( - appDir, - appName, - appKey, - accentColor, - bgColor, - textColor, - mutedTextColor + appDir, + appName, + appKey, + accentColor, + bgColor, + textColor, + mutedTextColor, ); - // 7. Generate Icons and Splash + // 7. 生成图标与启动页配置 await _generateBrandingAssets(brand, appDir, config); - print('\x1B[32m✔ App $brand generated successfully at $appDir!\x1B[0m'); - print('\x1B[34mTo build the app:\x1B[0m'); + print('\x1B[32m✔ 应用 $brand 已生成到 $appDir!\x1B[0m'); + print('\x1B[34m构建应用请执行:\x1B[0m'); print(' cd $appDir && flutter build apk'); } -Future _createFlutterApp(String brand, String appDir, String applicationId) async { - print('\x1BM[34m[Info] Running flutter create...\x1B[0m'); +Future _createFlutterApp( + String brand, + String appDir, + String applicationId, +) async { + print('\x1B[34m[信息] 正在执行 flutter create...\x1B[0m'); final Directory dir = Directory(appDir); if (dir.existsSync()) { - print('\x1BM[33m[Warning] Directory $appDir already exists. Cleaning up...\x1B[0m'); + print( + '\x1B[33m[警告] 目录 $appDir 已存在,正在清理...\x1B[0m', + ); dir.deleteSync(recursive: true); } - // Extract org - // e.g., com.wanmake.quanxue -> org: com.wanmake + // 提取组织名 + // 例如:com.wanmake.quanxue -> org: com.wanmake final List segments = applicationId.split('.'); final String org = segments.sublist(0, segments.length - 1).join('.'); final ProcessResult result = await Process.run('flutter', [ 'create', - '--org', org, - '--project-name', brand.replaceAll('-', '_'), - '--platforms', 'android,ios', + '--org', + org, + '--project-name', + brand.replaceAll('-', '_'), + '--platforms', + 'android', appDir, ]); - + if (result.exitCode != 0) { - print('\x1BM[31m[Error] flutter create failed:\n${result.stderr}\x1B[0m'); + print('\x1B[31m[错误] flutter create 执行失败:\n${result.stderr}\x1B[0m'); exit(1); } } Future _addCoreDependency(String appDir) async { - print('\x1BM[34m[Info] Adding web_shell_core dependency...\x1B[0m'); + print('\x1B[34m[信息] 正在添加 web_shell_core 依赖...\x1B[0m'); final ProcessResult result = await Process.run('flutter', [ - 'pub', 'add', 'web_shell_core', - '--path', '../../packages/web_shell_core', + 'pub', + 'add', + 'web_shell_core', + '--path', + '../../packages/web_shell_core', ], workingDirectory: appDir); - + if (result.exitCode != 0) { - print('\x1BM[31m[Error] Failed to add dependency:\n${result.stderr}\x1B[0m'); + print( + '\x1B[31m[错误] 添加依赖失败:\n${result.stderr}\x1B[0m', + ); exit(1); } } Future _overwriteMainActivity(String appDir, String applicationId) async { - print('\x1BM[34m[Info] Injecting CoreShellActivity inheritance...\x1B[0m'); - final String mainActivityPath = "$appDir/android/app/src/main/java/${applicationId.replaceAll('.', '/')}/MainActivity.java"; + 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[Error] MainActivity.java not found at ${mainActivityFile.path}\x1B[0m'); - // Flutter might have generated Kotlin depending on settings. Let's handle both. - final String ktPath = "$appDir/android/app/src/main/kotlin/${applicationId.replaceAll('.', '/')}/MainActivity.kt"; + 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(); // We just recreate the java one. + ktFile.deleteSync(); // 这里直接重新生成 Java 版本。 } else { - print('\x1B[31m[Error] Not found Kotlin either.\x1B[0m'); - exit(1); + print('\x1B[31m[错误] Kotlin 版本也未找到。\x1B[0m'); + exit(1); } } - // Prepare the Java file - final String javaContent = ''' + // 准备 Java 文件内容 + final String javaContent = + ''' package $applicationId; import com.yuanxuan.webshell.core.web_shell_core.CoreShellActivity; @@ -140,29 +163,35 @@ public class MainActivity extends CoreShellActivity { } Future _overwriteManifestLabel(String appDir, String appName) async { - print('\x1BM[34m[Info] Updating AndroidManifest.xml label...\x1B[0m'); - final File manifestFile = File('$appDir/android/app/src/main/AndroidManifest.xml'); + print('\x1B[34m[信息] 正在更新 AndroidManifest.xml 应用名...\x1B[0m'); + final File manifestFile = File( + '$appDir/android/app/src/main/AndroidManifest.xml', + ); String content = await manifestFile.readAsString(); - - // A simple regex to replace android:label="..." - content = content.replaceAll(RegExp(r'android:label="[^"]*"'), 'android:label="$appName"'); - + + // 使用简单正则替换 android:label="..." + content = content.replaceAll( + RegExp(r'android:label="[^"]*"'), + 'android:label="$appName"', + ); + await manifestFile.writeAsString(content); } Future _generateDartEntrypoint( - String appDir, - String appName, - String appKey, - String accentColor, - String bgColor, - String textColor, - String mutedTextColor + String appDir, + String appName, + String appKey, + String accentColor, + String bgColor, + String textColor, + String mutedTextColor, ) async { - print('\x1BM[34m[Info] Generating lib/main.dart...\x1B[0m'); + print('\x1B[34m[信息] 正在生成 lib/main.dart...\x1B[0m'); final File mainFile = File('$appDir/lib/main.dart'); - - final String dartContent = ''' + + final String dartContent = + ''' import 'package:flutter/material.dart'; import 'package:web_shell_core/web_shell_core.dart'; @@ -183,27 +212,36 @@ void main() { await mainFile.writeAsString(dartContent); } -Future _generateBrandingAssets(String brand, String appDir, YamlMap config) async { - print('\x1BM[34m[Info] Configuring icons and splash screens...\x1B[0m'); +Future _generateBrandingAssets( + String brand, + String appDir, + YamlMap config, +) async { + print('\x1B[34m[信息] 正在配置图标与启动页...\x1B[0m'); if (config['branding'] == null) { - print('\x1BM[33m[Warning] No branding section found in config. Skipping asset generation.\x1B[0m'); + print( + '\x1B[33m[警告] 配置中未找到 branding 段,跳过资源生成。\x1B[0m', + ); return; } final YamlMap branding = config['branding'] as YamlMap; - - // Create flutter_launcher_icons.yaml in appDir - final String iconsYaml = ''' + + // 在 appDir 下创建 flutter_launcher_icons.yaml + final String iconsYaml = + ''' flutter_launcher_icons: android: true - ios: 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); + await File( + '$appDir/flutter_launcher_icons-config.yaml', + ).writeAsString(iconsYaml); - // Create flutter_native_splash.yaml in appDir - final String splashYaml = ''' + // 在 appDir 下创建 flutter_native_splash.yaml + final String splashYaml = + ''' flutter_native_splash: color: "${branding['splash_color']}" image: "${branding['splash']}" @@ -211,10 +249,12 @@ flutter_native_splash: image: "${branding['splash']}" icon_background_color: "${branding['splash_color']}" '''; - await File('$appDir/flutter_native_splash-config.yaml').writeAsString(splashYaml); - - // Copy branding assets - print('\x1BM[34m[Info] Copying assets...\x1B[0m'); + 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'); @@ -229,15 +269,20 @@ flutter_native_splash: } } - print('\x1BM[34m[Info] Running asset generators...\x1B[0m'); - - // Run flutter_launcher_icons + print('\x1B[34m[信息] 正在执行资源生成器...\x1B[0m'); + + // 执行 flutter_launcher_icons await Process.run('dart', [ - 'run', 'flutter_launcher_icons', '-f', 'flutter_launcher_icons-config.yaml' + 'run', + 'flutter_launcher_icons', + '-f', + 'flutter_launcher_icons-config.yaml', ], workingDirectory: appDir); - - // Run flutter_native_splash + + // 执行 flutter_native_splash await Process.run('dart', [ - 'run', 'flutter_native_splash:create', '--path=flutter_native_splash-config.yaml' + 'run', + 'flutter_native_splash:create', + '--path=flutter_native_splash-config.yaml', ], workingDirectory: appDir); }