app code 4/27

This commit is contained in:
袁智鸿
2026-04-27 12:16:53 +08:00
parent f0fa33cd67
commit 7413e758ce
257 changed files with 24691 additions and 8371 deletions
+34
View File
@@ -0,0 +1,34 @@
**/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
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>
+2
View File
@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
+2
View File
@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
+60
View File
@@ -0,0 +1,60 @@
source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'
# Uncomment this line to define a global platform for your project
platform :ios, '16.6'
$iOSVersion = '16.6'
# 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.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=*]"] = "armv7"
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
end
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
if Gem::Version.new($iOSVersion) > Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'])
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
end
end
target.build_configurations.each do |config|
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
end
end
end
+867
View File
@@ -0,0 +1,867 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
0E2BFCBD2F2C51FE00E21BCA /* StackChanRobot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2BFCBC2F2C51F600E21BCA /* StackChanRobot.swift */; };
0E2BFCBF2F2C525E00E21BCA /* ViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2BFCBE2F2C525400E21BCA /* ViewFactory.swift */; };
0E2BFCC32F2C566600E21BCA /* DanceList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2BFCC22F2C566600E21BCA /* DanceList.swift */; };
0E2BFCC52F2C566B00E21BCA /* ExpressionData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2BFCC42F2C566B00E21BCA /* ExpressionData.swift */; };
0E2BFCC82F2C59AD00E21BCA /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2BFCC72F2C59AD00E21BCA /* Extension.swift */; };
0E2BFD962F2C908200E21BCA /* StackChanRotaryRobot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2BFD952F2C907100E21BCA /* StackChanRotaryRobot.swift */; };
0E319DC52F504DD600B8C136 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E319DC42F504DD600B8C136 /* SceneDelegate.swift */; };
0E49C11D2F349BB300419137 /* StackChanArView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49C11C2F349B9B00419137 /* StackChanArView.swift */; };
0EF7F6292F2B40D7008CDEB0 /* NativeBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF7F6282F2B40C6008CDEB0 /* NativeBridge.swift */; };
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 */; };
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 */; };
ABF1068D156BACD9687833B5 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61533E6EB1B159D304737D3A /* Pods_RunnerTests.framework */; };
FEAAEB50E782E6F211E46BE8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69CC735B711452B07B9BC3CD /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
014B92591A4905CFCFF58C45 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
0E2BFCBC2F2C51F600E21BCA /* StackChanRobot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackChanRobot.swift; sourceTree = "<group>"; };
0E2BFCBE2F2C525400E21BCA /* ViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFactory.swift; sourceTree = "<group>"; };
0E2BFCC22F2C566600E21BCA /* DanceList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DanceList.swift; sourceTree = "<group>"; };
0E2BFCC42F2C566B00E21BCA /* ExpressionData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpressionData.swift; sourceTree = "<group>"; };
0E2BFCC72F2C59AD00E21BCA /* Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = "<group>"; };
0E2BFD952F2C907100E21BCA /* StackChanRotaryRobot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackChanRotaryRobot.swift; sourceTree = "<group>"; };
0E319DC42F504DD600B8C136 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
0E49C11C2F349B9B00419137 /* StackChanArView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackChanArView.swift; sourceTree = "<group>"; };
0EF7F6282F2B40C6008CDEB0 /* NativeBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeBridge.swift; sourceTree = "<group>"; };
0EF7F7242F2B4498008CDEB0 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
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 = "<group>"; };
61533E6EB1B159D304737D3A /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
69CC735B711452B07B9BC3CD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
83DB239E5DAC7976B8DC7B7A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
86F9532C8B82DB90BB2B967F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
8D86E24B4CB62314C14A6BF4 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; explicitFileType = text.xml; path = Info.plist; sourceTree = "<group>"; };
99E681E7B6E33A054304AA9A /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
D9756F4DAC76C779F6DADE83 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
0E2BFD5D2F2C6AD200E21BCA /* Exceptions for "3DModel" folder in "Runner" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
StackChanModel.scn,
);
target = 97C146ED1CF9000F007C117D /* Runner */;
};
0E2BFD5E2F2C6AD200E21BCA /* Exceptions for "3DModel" folder in "RunnerTests" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
StackChanModel.scn,
);
target = 331C8080294A63A400263BE5 /* RunnerTests */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
0E2BFCC12F2C538400E21BCA /* 3DModel */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
0E2BFD5D2F2C6AD200E21BCA /* Exceptions for "3DModel" folder in "Runner" target */,
0E2BFD5E2F2C6AD200E21BCA /* Exceptions for "3DModel" folder in "RunnerTests" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = 3DModel;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
15389107E87A87969889C15C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
ABF1068D156BACD9687833B5 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
FEAAEB50E782E6F211E46BE8 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0E2BFCBB2F2C51EB00E21BCA /* View */ = {
isa = PBXGroup;
children = (
0E49C11C2F349B9B00419137 /* StackChanArView.swift */,
0E2BFD952F2C907100E21BCA /* StackChanRotaryRobot.swift */,
0E2BFCBE2F2C525400E21BCA /* ViewFactory.swift */,
0E2BFCBC2F2C51F600E21BCA /* StackChanRobot.swift */,
);
path = View;
sourceTree = "<group>";
};
0E2BFCC62F2C599E00E21BCA /* Utils */ = {
isa = PBXGroup;
children = (
0E2BFCC72F2C59AD00E21BCA /* Extension.swift */,
0EF7F6282F2B40C6008CDEB0 /* NativeBridge.swift */,
);
path = Utils;
sourceTree = "<group>";
};
0EF7F6242F2B3D35008CDEB0 /* Model */ = {
isa = PBXGroup;
children = (
0E2BFCC42F2C566B00E21BCA /* ExpressionData.swift */,
0E2BFCC22F2C566600E21BCA /* DanceList.swift */,
);
path = Model;
sourceTree = "<group>";
};
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
6A84775745706C15EDCCC2AC /* Frameworks */ = {
isa = PBXGroup;
children = (
69CC735B711452B07B9BC3CD /* Pods_Runner.framework */,
61533E6EB1B159D304737D3A /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
F9C6E422CF624A714D6A795D /* Pods */,
6A84775745706C15EDCCC2AC /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
0E319DC42F504DD600B8C136 /* SceneDelegate.swift */,
0E2BFCC62F2C599E00E21BCA /* Utils */,
0E2BFCC12F2C538400E21BCA /* 3DModel */,
0E2BFCBB2F2C51EB00E21BCA /* View */,
0EF7F7242F2B4498008CDEB0 /* Runner.entitlements */,
0EF7F6242F2B3D35008CDEB0 /* Model */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
F9C6E422CF624A714D6A795D /* Pods */ = {
isa = PBXGroup;
children = (
86F9532C8B82DB90BB2B967F /* Pods-Runner.debug.xcconfig */,
014B92591A4905CFCFF58C45 /* Pods-Runner.release.xcconfig */,
83DB239E5DAC7976B8DC7B7A /* Pods-Runner.profile.xcconfig */,
D9756F4DAC76C779F6DADE83 /* Pods-RunnerTests.debug.xcconfig */,
99E681E7B6E33A054304AA9A /* Pods-RunnerTests.release.xcconfig */,
8D86E24B4CB62314C14A6BF4 /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
06679B67EF6ED2EE02C6AE1F /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
15389107E87A87969889C15C /* Frameworks */,
);
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 = (
6B1F6EB6486B9A29A8A7AE57 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
BCD9052275896B9385CAEC4A /* [CP] Embed Pods Frameworks */,
62354403C2A37F66E2DA6570 /* [CP] Copy Pods Resources */,
);
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 */
06679B67EF6ED2EE02C6AE1F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
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\n";
};
62354403C2A37F66E2DA6570 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
6B1F6EB6486B9A29A8A7AE57 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export PATH=\"/opt/homebrew/bin:$PATH\"\nexport CMAKE=\"/opt/homebrew/bin/cmake\"\nexport FLUTTER_BUILD_NATIVE_ASSETS=\"false\"\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
};
BCD9052275896B9385CAEC4A /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* 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 = (
0E2BFCC82F2C59AD00E21BCA /* Extension.swift in Sources */,
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
0E2BFCC52F2C566B00E21BCA /* ExpressionData.swift in Sources */,
0E2BFCBF2F2C525E00E21BCA /* ViewFactory.swift in Sources */,
0E2BFD962F2C908200E21BCA /* StackChanRotaryRobot.swift in Sources */,
0E49C11D2F349BB300419137 /* StackChanArView.swift in Sources */,
0EF7F6292F2B40D7008CDEB0 /* NativeBridge.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
0E2BFCC32F2C566600E21BCA /* DanceList.swift in Sources */,
0E2BFCBD2F2C51FE00E21BCA /* StackChanRobot.swift in Sources */,
0E319DC52F504DD600B8C136 /* 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 = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* 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;
EXCLUDED_ARCHS = "";
"EXCLUDED_ARCHS[sdk=*]" = armv7;
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;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
DEVELOPMENT_TEAM = NG678HLKHZ;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
"$(PROJECT_DIR)/Runner",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.m5stack.StackChan;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D9756F4DAC76C779F6DADE83 /* Pods-RunnerTests.debug.xcconfig */;
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.m5stack.stackchan.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;
baseConfigurationReference = 99E681E7B6E33A054304AA9A /* Pods-RunnerTests.release.xcconfig */;
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.m5stack.stackchan.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;
baseConfigurationReference = 8D86E24B4CB62314C14A6BF4 /* Pods-RunnerTests.profile.xcconfig */;
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.m5stack.stackchan.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;
EXCLUDED_ARCHS = "";
"EXCLUDED_ARCHS[sdk=*]" = armv7;
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;
EXCLUDED_ARCHS = "";
"EXCLUDED_ARCHS[sdk=*]" = armv7;
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;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = NG678HLKHZ;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
"$(PROJECT_DIR)/Runner",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.m5stack.StackChan;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
DEVELOPMENT_TEAM = NG678HLKHZ;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
"$(PROJECT_DIR)/Runner",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.m5stack.StackChan;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
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 */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
import Flutter
import CoreLocation
@main
@objc class AppDelegate: FlutterAppDelegate,FlutterImplicitEngineDelegate {
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
@@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "app_logo.jpg",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

@@ -0,0 +1,23 @@
{
"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"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

@@ -0,0 +1,5 @@
# 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.
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-255" y="7"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,19 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end
NS_ASSUME_NONNULL_END
#endif /* GeneratedPluginRegistrant_h */
+154
View File
@@ -0,0 +1,154 @@
//
// Generated file. Do not edit.
//
// clang-format off
#import "GeneratedPluginRegistrant.h"
#if __has_include(<audio_session/AudioSessionPlugin.h>)
#import <audio_session/AudioSessionPlugin.h>
#else
@import audio_session;
#endif
#if __has_include(<camera_avfoundation/CameraPlugin.h>)
#import <camera_avfoundation/CameraPlugin.h>
#else
@import camera_avfoundation;
#endif
#if __has_include(<ffmpeg_kit_flutter_new/FFmpegKitFlutterPlugin.h>)
#import <ffmpeg_kit_flutter_new/FFmpegKitFlutterPlugin.h>
#else
@import ffmpeg_kit_flutter_new;
#endif
#if __has_include(<file_picker/FilePickerPlugin.h>)
#import <file_picker/FilePickerPlugin.h>
#else
@import file_picker;
#endif
#if __has_include(<flutter_angle/FlutterAnglePlugin.h>)
#import <flutter_angle/FlutterAnglePlugin.h>
#else
@import flutter_angle;
#endif
#if __has_include(<flutter_blue_plus_darwin/FlutterBluePlusPlugin.h>)
#import <flutter_blue_plus_darwin/FlutterBluePlusPlugin.h>
#else
@import flutter_blue_plus_darwin;
#endif
#if __has_include(<geolocator_apple/GeolocatorPlugin.h>)
#import <geolocator_apple/GeolocatorPlugin.h>
#else
@import geolocator_apple;
#endif
#if __has_include(<google_mlkit_commons/GoogleMlKitCommonsPlugin.h>)
#import <google_mlkit_commons/GoogleMlKitCommonsPlugin.h>
#else
@import google_mlkit_commons;
#endif
#if __has_include(<google_mlkit_face_detection/GoogleMlKitFaceDetectionPlugin.h>)
#import <google_mlkit_face_detection/GoogleMlKitFaceDetectionPlugin.h>
#else
@import google_mlkit_face_detection;
#endif
#if __has_include(<just_audio/JustAudioPlugin.h>)
#import <just_audio/JustAudioPlugin.h>
#else
@import just_audio;
#endif
#if __has_include(<mobile_scanner/MobileScannerPlugin.h>)
#import <mobile_scanner/MobileScannerPlugin.h>
#else
@import mobile_scanner;
#endif
#if __has_include(<music_feature_analyzer/MusicFeatureAnalyzerPlugin.h>)
#import <music_feature_analyzer/MusicFeatureAnalyzerPlugin.h>
#else
@import music_feature_analyzer;
#endif
#if __has_include(<network_info_plus/FPPNetworkInfoPlusPlugin.h>)
#import <network_info_plus/FPPNetworkInfoPlusPlugin.h>
#else
@import network_info_plus;
#endif
#if __has_include(<opus_codec_ios/OpusFlutterIosPlugin.h>)
#import <opus_codec_ios/OpusFlutterIosPlugin.h>
#else
@import opus_codec_ios;
#endif
#if __has_include(<package_info_plus/FPPPackageInfoPlusPlugin.h>)
#import <package_info_plus/FPPPackageInfoPlusPlugin.h>
#else
@import package_info_plus;
#endif
#if __has_include(<permission_handler_apple/PermissionHandlerPlugin.h>)
#import <permission_handler_apple/PermissionHandlerPlugin.h>
#else
@import permission_handler_apple;
#endif
#if __has_include(<shared_preferences_foundation/SharedPreferencesPlugin.h>)
#import <shared_preferences_foundation/SharedPreferencesPlugin.h>
#else
@import shared_preferences_foundation;
#endif
#if __has_include(<three_js_sensors/TJSSensorsPlugin.h>)
#import <three_js_sensors/TJSSensorsPlugin.h>
#else
@import three_js_sensors;
#endif
#if __has_include(<video_player_avfoundation/FVPVideoPlayerPlugin.h>)
#import <video_player_avfoundation/FVPVideoPlayerPlugin.h>
#else
@import video_player_avfoundation;
#endif
#if __has_include(<webview_flutter_wkwebview/WebViewFlutterPlugin.h>)
#import <webview_flutter_wkwebview/WebViewFlutterPlugin.h>
#else
@import webview_flutter_wkwebview;
#endif
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[AudioSessionPlugin registerWithRegistrar:[registry registrarForPlugin:@"AudioSessionPlugin"]];
[CameraPlugin registerWithRegistrar:[registry registrarForPlugin:@"CameraPlugin"]];
[FFmpegKitFlutterPlugin registerWithRegistrar:[registry registrarForPlugin:@"FFmpegKitFlutterPlugin"]];
[FilePickerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FilePickerPlugin"]];
[FlutterAnglePlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterAnglePlugin"]];
[FlutterBluePlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterBluePlusPlugin"]];
[GeolocatorPlugin registerWithRegistrar:[registry registrarForPlugin:@"GeolocatorPlugin"]];
[GoogleMlKitCommonsPlugin registerWithRegistrar:[registry registrarForPlugin:@"GoogleMlKitCommonsPlugin"]];
[GoogleMlKitFaceDetectionPlugin registerWithRegistrar:[registry registrarForPlugin:@"GoogleMlKitFaceDetectionPlugin"]];
[JustAudioPlugin registerWithRegistrar:[registry registrarForPlugin:@"JustAudioPlugin"]];
[MobileScannerPlugin registerWithRegistrar:[registry registrarForPlugin:@"MobileScannerPlugin"]];
[MusicFeatureAnalyzerPlugin registerWithRegistrar:[registry registrarForPlugin:@"MusicFeatureAnalyzerPlugin"]];
[FPPNetworkInfoPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FPPNetworkInfoPlusPlugin"]];
[OpusFlutterIosPlugin registerWithRegistrar:[registry registrarForPlugin:@"OpusFlutterIosPlugin"]];
[FPPPackageInfoPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FPPPackageInfoPlusPlugin"]];
[PermissionHandlerPlugin registerWithRegistrar:[registry registrarForPlugin:@"PermissionHandlerPlugin"]];
[SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]];
[TJSSensorsPlugin registerWithRegistrar:[registry registrarForPlugin:@"TJSSensorsPlugin"]];
[FVPVideoPlayerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FVPVideoPlayerPlugin"]];
[WebViewFlutterPlugin registerWithRegistrar:[registry registrarForPlugin:@"WebViewFlutterPlugin"]];
}
@end
+108
View File
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>StackChan World</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.m5stack.StackChan</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>stackchan</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.1.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>11</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>The Bluetooth permission is required for searching and connecting to the StackChan device.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>The Bluetooth permission is required for searching and connecting to the StackChan device.</string>
<key>NSBonjourServices</key>
<array>
<string>_stackchan-mpc._tcp</string>
</array>
<key>NSCameraUsageDescription</key>
<string>We need access to the camera to take photos and record videos.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>This app requires access to the local network to communicate with devices.</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need access to the microphone to capture audio data.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save the photo to the album</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to the photo library to save your photos and videos.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
<string>bluetooth-le</string>
<string>arkit</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>com.apple.developer.networking.wifi-info</key>
<true/>
<key>NSLocationWhenInUseUsageDescription</key>
<string>You need to grant location permission to view the name of the WiFi you are currently connected to.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>You need to grant location permission to view the name of the WiFi you are currently connected to.</string>
</dict>
</plist>
+108
View File
@@ -0,0 +1,108 @@
//
// Dance.swift
// StackChan
//
// Created by 鸿 on 2026/1/16.
//
import Foundation
struct DanceList: Codable, Identifiable {
var danceData: [DanceData]?
var danceIndex: Int?
var danceName: String?
var id: String = UUID().uuidString
enum CodingKeys: String, CodingKey {
case danceData
case danceIndex
case danceName
}
}
struct DanceData : Codable,Identifiable {
var leftEye: ExpressionItem // Left eye, default weight = 100
var rightEye: ExpressionItem // Right eye, default weight = 100
var mouth: ExpressionItem // Mouth, default weight = 0
var yawServo: MotionDataItem // Yaw rotation, angle range (-1280 ~ 1280), default 0
var pitchServo: MotionDataItem // Pitch movement, angle range (0 ~ 900), default 0
var leftRgbColor: String = "#00000000"
var rightRgbColor: String = "#00000000"
var durationMs: Int // Duration in milliseconds, default 1000
var id: String = UUID().uuidString
enum CodingKeys: String, CodingKey {
case leftEye, rightEye, mouth, yawServo, pitchServo, leftRgbColor, rightRgbColor, durationMs
}
static func from(jsonString: String) -> DanceData? {
guard !jsonString.isEmpty else {
print("JSON string is empty")
return nil
}
guard let jsonData = jsonString.data(using: .utf8) else {
print("Failed to convert string to UTF-8 data")
return nil
}
do {
let decoder = JSONDecoder()
let danceData = try decoder.decode(DanceData.self, from: jsonData)
return danceData
} catch {
return nil
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
leftEye = try container.decode(ExpressionItem.self, forKey: .leftEye)
rightEye = try container.decode(ExpressionItem.self, forKey: .rightEye)
mouth = try container.decode(ExpressionItem.self, forKey: .mouth)
yawServo = try container.decode(MotionDataItem.self, forKey: .yawServo)
pitchServo = try container.decode(MotionDataItem.self, forKey: .pitchServo)
leftRgbColor = try container.decodeIfPresent(String.self, forKey: .leftRgbColor) ?? "#00000000"
rightRgbColor = try container.decodeIfPresent(String.self, forKey: .rightRgbColor) ?? "#00000000"
durationMs = try container.decode(Int.self, forKey: .durationMs)
id = UUID().uuidString
}
init(
leftEye: ExpressionItem,
rightEye: ExpressionItem,
mouth: ExpressionItem,
yawServo: MotionDataItem,
pitchServo: MotionDataItem,
leftRgbColor: String = "#00000000",
rightRgbColor: String = "#00000000",
durationMs: Int,
id: String = UUID().uuidString
) {
self.leftEye = leftEye
self.rightEye = rightEye
self.mouth = mouth
self.yawServo = yawServo
self.pitchServo = pitchServo
self.leftRgbColor = leftRgbColor
self.rightRgbColor = rightRgbColor
self.durationMs = durationMs
self.id = id
}
func copy() -> DanceData {
DanceData(
leftEye: self.leftEye.copy(),
rightEye: self.rightEye.copy(),
mouth: self.mouth.copy(),
yawServo: self.yawServo.copy(),
pitchServo: self.pitchServo.copy(),
leftRgbColor: self.leftRgbColor,
rightRgbColor: self.rightRgbColor,
durationMs: self.durationMs,
id: UUID().uuidString
)
}
}
+100
View File
@@ -0,0 +1,100 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
import Foundation
struct ExpressionData : Codable {
var type: String = "bleAvatar"
var leftEye: ExpressionItem
var rightEye: ExpressionItem
var mouth: ExpressionItem
}
struct ExpressionItem : Codable {
var x: Int = 0
var y: Int = 0
var rotation: Int = 0
var weight: Int = 0
var size: Int = 0
func copy() -> ExpressionItem {
ExpressionItem(
x: self.x,
y: self.y,
rotation: self.rotation,
weight: self.weight,
size: self.size
)
}
}
struct MotionData : Codable {
var type: String = "bleMotion"
var pitchServo: MotionDataItem
var yawServo: MotionDataItem
func toJsonString() -> String {
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(self),
let jsonString = String(data: jsonData, encoding: .utf8) {
return jsonString
}
return "{}"
}
}
struct MotionDataItem: Codable {
var angle: Int = 0
var speed: Int = 500
var rotate: Int = 0
init() {
self.angle = 0
self.speed = 500
self.rotate = 0
}
init(angle: Int, speed: Int = 500) {
self.angle = angle
self.speed = speed
self.rotate = 0
}
enum CodingKeys: String, CodingKey {
case angle
case speed
case rotate
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if angle != 0 {
try container.encode(angle, forKey: .angle)
try container.encode(speed, forKey: .speed)
} else if rotate != 0 {
try container.encode(rotate, forKey: .rotate)
try container.encode(speed, forKey: .speed)
} else {
try container.encode(angle, forKey: .angle)
try container.encode(speed, forKey: .speed)
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.angle = try container.decodeIfPresent(Int.self, forKey: .angle) ?? 0
self.speed = try container.decodeIfPresent(Int.self, forKey: .speed) ?? 500
self.rotate = try container.decodeIfPresent(Int.self, forKey: .rotate) ?? 0
}
func copy() -> MotionDataItem {
MotionDataItem(
angle: self.angle,
speed: self.speed,
)
}
}
+1
View File
@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.wifi-info</key>
<true/>
</dict>
</plist>
+79
View File
@@ -0,0 +1,79 @@
import Flutter
import NetworkExtension
import CoreLocation
import UIKit
class SceneDelegate: FlutterSceneDelegate,CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
override func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let controller = window?.rootViewController as? FlutterViewController {
NativeBridge.shared.setup(with: controller)
NativeBridge.shared.setMethodCallHandler { [weak self] call, result in
self?.handleMethodCall(call: call, result: result)
}
registerNativeViews(with: controller)
}
locationManager.delegate = self
}
private func registerNativeViews(with controller: FlutterViewController) {
guard let registrar = controller.registrar(forPlugin: "stack_chan") else {
return
}
let robotFactory =
StackChanRobotViewFactory(messenger: controller.binaryMessenger)
registrar.register(robotFactory, withId: "stackchan_robot_view")
let rotaryFactory =
StackChanRotaryRobotViewFactory(messenger: controller.binaryMessenger)
registrar.register(rotaryFactory, withId: "stackchan_rotary_robot_view")
let arViewFactory = StackChanArViewFactory(messenger: controller.binaryMessenger)
registrar.register(arViewFactory, withId: "stackchan_ar_view")
}
private func handleMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) {
let method = Method.fromString(call.method)
switch method {
case .wifiName:
getWifiName()
case .stopPlayPCM:
NativeBridge.shared.stopPlayPCM()
default:
break
}
}
private func getWifiName() {
switch locationManager.authorizationStatus {
case .authorizedWhenInUse, .authorizedAlways:
fetchWifiInfo()
break
case .denied, .restricted:
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
break
default:
break
}
}
private func fetchWifiInfo() {
NEHotspotNetwork.fetchCurrent { network in
if let wifiName = network?.ssid {
NativeBridge.shared.sendMessage(method: .wifiName, wifiName)
}
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
print(manager.authorizationStatus)
if manager.authorizationStatus == .authorizedWhenInUse ||
manager.authorizationStatus == .authorizedAlways {
fetchWifiInfo()
}
}
}
+399
View File
@@ -0,0 +1,399 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
import SwiftUI
import simd
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder),
to: nil, from: nil, for: nil)
}
}
extension Color {
init?(hex: String) {
var hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if hexString.hasPrefix("#") {
hexString.removeFirst()
}
let r, g, b, a: Double
switch hexString.count {
case 3: // RGB
let chars = Array(hexString)
r = Double(strtoul(String([chars[0], chars[0]]), nil, 16)) / 255
g = Double(strtoul(String([chars[1], chars[1]]), nil, 16)) / 255
b = Double(strtoul(String([chars[2], chars[2]]), nil, 16)) / 255
a = 1.0
case 4: // RGBA
let chars = Array(hexString)
r = Double(strtoul(String([chars[0], chars[0]]), nil, 16)) / 255
g = Double(strtoul(String([chars[1], chars[1]]), nil, 16)) / 255
b = Double(strtoul(String([chars[2], chars[2]]), nil, 16)) / 255
a = Double(strtoul(String([chars[3], chars[3]]), nil, 16)) / 255
case 6: // RRGGBB
var value: UInt64 = 0
Scanner(string: hexString).scanHexInt64(&value)
r = Double((value & 0xFF0000) >> 16) / 255
g = Double((value & 0x00FF00) >> 8) / 255
b = Double(value & 0x0000FF) / 255
a = 1.0
case 8: // AARRGGBB
var value: UInt64 = 0
Scanner(string: hexString).scanHexInt64(&value)
a = Double((value & 0xFF000000) >> 24) / 255
r = Double((value & 0x00FF0000) >> 16) / 255
g = Double((value & 0x0000FF00) >> 8) / 255
b = Double(value & 0x000000FF) / 255
default:
return nil
}
self.init(red: r, green: g, blue: b, opacity: a)
}
func toHex() -> String {
let uiColor = UIColor(self)
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
guard uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) else { return "#00000000" }
if a < 1.0 {
return String(format: "#%02X%02X%02X%02X",
Int(a * 255),
Int(r * 255),
Int(g * 255),
Int(b * 255))
} else {
return String(format: "#%02X%02X%02X",
Int(r * 255),
Int(g * 255),
Int(b * 255))
}
}
}
extension UIImage {
func scaledToFill(_ targetSize: CGSize) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
self.draw(in: CGRect(origin: .zero, size: targetSize))
}
}
/// Optional image compression method
/// - Parameters:
/// - resolutionSize: Target resolution (optional). If nil, no cropping or scaling is applied
/// - memorySize: Target memory size in MB (optional). If nil, no memory compression is applied
/// - cropCenter: Whether to crop from center. Default false = aspect-fit scaling
func compress(to resolutionSize: CGSize? = nil, memorySize: Float? = nil, cropCenter: Bool = false) -> Data? {
var scaledImage = self
// 1. Crop or scale based on target resolution
if let resolutionSize = resolutionSize {
if cropCenter {
// 1. Calculate scale to ensure the image fully covers the target resolution
let scale = max(resolutionSize.width / size.width, resolutionSize.height / size.height)
let scaledSize = CGSize(width: size.width * scale, height: size.height * scale)
// 2. Calculate offset to ensure center cropping
let originX = (scaledSize.width - resolutionSize.width) / 2
let originY = (scaledSize.height - resolutionSize.height) / 2
// 3. Start drawing
UIGraphicsBeginImageContextWithOptions(resolutionSize, false, 1.0)
self.draw(in: CGRect(x: -originX, y: -originY, width: scaledSize.width, height: scaledSize.height))
scaledImage = UIGraphicsGetImageFromCurrentImageContext() ?? self
UIGraphicsEndImageContext()
} else {
// Aspect-fit scaling
let scale = min(resolutionSize.width / size.width, resolutionSize.height / size.height)
let newSize = CGSize(width: size.width * scale, height: size.height * scale)
UIGraphicsBeginImageContextWithOptions(resolutionSize, false, 1.0)
self.draw(in: CGRect(origin: .zero, size: newSize))
scaledImage = UIGraphicsGetImageFromCurrentImageContext() ?? self
UIGraphicsEndImageContext()
}
}
// 2. Compress based on target memory size
guard let memorySize = memorySize else {
return scaledImage.jpegData(compressionQuality: 1)
}
let maxBytes = Int(memorySize * 1024 * 1024) // MB -> Bytes
var compression: CGFloat = 1.0
var imageData = scaledImage.jpegData(compressionQuality: compression)
// Keep compressing until size requirement is met or compression limit is reached
while let data = imageData, data.count > maxBytes, compression > 0.01 {
compression *= 0.7
imageData = scaledImage.jpegData(compressionQuality: compression)
}
return imageData
}
/// Compress image only based on target memory size, keeping resolution and aspect ratio unchanged
/// - Parameter memorySize: Target memory size in MB
/// - Returns: Compressed JPEG data
func compress(toMemorySize memorySize: Float) -> Data? {
let maxBytes = Int(memorySize * 1024 * 1024)
var compression: CGFloat = 1.0
var imageData = self.jpegData(compressionQuality: compression)
while let data = imageData, data.count > maxBytes, compression > 0.01 {
compression *= 0.7
imageData = self.jpegData(compressionQuality: compression)
}
return imageData
}
}
extension String {
func jsonPrint() {
guard let data = self.data(using: .utf8) else {
print(self)
return
}
do {
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
let prettyData = try JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted, .sortedKeys])
if let prettyString = String(data: prettyData, encoding: .utf8) {
print("✅ JSON formatted output:\n\(prettyString)")
} else {
print(self)
}
} catch {
print("❌ Invalid JSON format: \(error.localizedDescription)")
print(self)
}
}
func leftPadding(toLength: Int, withPad character: Character) -> String {
if count < toLength {
return String(repeatElement(character, count: toLength - count)) + self
} else {
return self
}
}
/// Convert a MAC address string into 6-byte Data
func macToData() -> Data? {
// Remove separators such as ":" or "-"
let cleaned = self.replacingOccurrences(of: "[:\\-]", with: "", options: .regularExpression)
// Must be exactly 12 hexadecimal characters
guard cleaned.count == 12 else { return nil }
var data = Data()
var index = cleaned.startIndex
for _ in 0..<6 {
let nextIndex = cleaned.index(index, offsetBy: 2)
let byteString = String(cleaned[index..<nextIndex])
guard let byte = UInt8(byteString, radix: 16) else { return nil }
data.append(byte)
index = nextIndex
}
return data
}
func toData() -> Data? {
return self.data(using: .utf8)
}
func toColor() -> Color {
let hexString = self.trimmingCharacters(in: .whitespacesAndNewlines)
if let color = Color(hex: hexString) {
return color
}
return .clear
}
}
extension Encodable {
func toDictionary() -> [String: Any]? {
guard let data = try? JSONEncoder().encode(self),
let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
return nil
}
return dict
}
func toListDictionary() -> [[String: Any]]? {
if let singleDict = self.toDictionary() {
return [singleDict]
}
if let arrayData = try? JSONEncoder().encode(self),
let jsonArray = try? JSONSerialization.jsonObject(with: arrayData) as? [[String: Any]] {
return jsonArray
}
return nil
}
func toJsonString(prettyPrinted: Bool = false) -> String {
let encoder = JSONEncoder()
if prettyPrinted {
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
}
do {
let data = try encoder.encode(self)
return String(data: data, encoding: .utf8) ?? "{}"
} catch {
print("❌ JSON serialization failed: \(error)")
return "{}"
}
}
func toData() -> Data? {
let encoder = JSONEncoder()
do {
return try encoder.encode(self)
} catch {
print("❌ Failed to convert JSON to Data: \(error.localizedDescription)")
return nil
}
}
}
extension UIColor {
convenience init?(hex: String) {
var hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if hexString.hasPrefix("#") {
hexString.removeFirst()
}
let r, g, b, a: CGFloat
switch hexString.count {
case 3: // RGB
let chars = Array(hexString)
r = CGFloat(strtoul(String([chars[0], chars[0]]), nil, 16)) / 255
g = CGFloat(strtoul(String([chars[1], chars[1]]), nil, 16)) / 255
b = CGFloat(strtoul(String([chars[2], chars[2]]), nil, 16)) / 255
a = 1.0
case 4: // RGBA
let chars = Array(hexString)
r = CGFloat(strtoul(String([chars[0], chars[0]]), nil, 16)) / 255
g = CGFloat(strtoul(String([chars[1], chars[1]]), nil, 16)) / 255
b = CGFloat(strtoul(String([chars[2], chars[2]]), nil, 16)) / 255
a = CGFloat(strtoul(String([chars[3], chars[3]]), nil, 16)) / 255
case 6: // RRGGBB
var value: UInt64 = 0
Scanner(string: hexString).scanHexInt64(&value)
r = CGFloat((value & 0xFF0000) >> 16) / 255
g = CGFloat((value & 0x00FF00) >> 8) / 255
b = CGFloat(value & 0x0000FF) / 255
a = 1.0
case 8: // AARRGGBB
var value: UInt64 = 0
Scanner(string: hexString).scanHexInt64(&value)
a = CGFloat((value & 0xFF000000) >> 24) / 255
r = CGFloat((value & 0x00FF0000) >> 16) / 255
g = CGFloat((value & 0x0000FF00) >> 8) / 255
b = CGFloat(value & 0x000000FF) / 255
default:
return nil
}
self.init(red: r, green: g, blue: b, alpha: a)
}
}
extension CGRect {
var minDimension: CGFloat {
min(width, height)
}
}
extension View {
@ViewBuilder
func rippleDiffusion() -> some View {
TimelineView(.animation) { timeline in
// Calculate time interval
let time = timeline.date.timeIntervalSinceReferenceDate
ZStack {
// Multiple ripple cycles
ForEach(0..<3) { i in
let progress = (time + Double(i) * 0.5).truncatingRemainder(dividingBy: 1.5) / 1.5
Circle()
.stroke(Color.blue.opacity(1 - progress), lineWidth: 2)
.scaleEffect(0.5 + progress * 2)
}
}
.drawingGroup()
}
}
@ViewBuilder
func glassEffectCircle() -> some View {
if #available(iOS 26.0, *) {
self.glassEffect()
} else {
self.background(
Circle()
.fill(.ultraThinMaterial)
)
}
}
@ViewBuilder
func glassEffectRegular(cornerRadius : CGFloat) -> some View {
if #available(iOS 26.0, *) {
self.glassEffect(.regular,in: RoundedRectangle(cornerRadius: cornerRadius))
} else {
self
.background(
RoundedRectangle(cornerRadius: cornerRadius)
.fill(.ultraThinMaterial)
)
}
}
@ViewBuilder
func presentationBackgroundClear() -> some View {
if #available(iOS 26.0, *) {
self.presentationBackground(.clear)
} else {
self.presentationBackground(.ultraThinMaterial)
}
}
@ViewBuilder
func glassButtonStyle() -> some View {
if #available(iOS 26.0, *) {
self.buttonStyle(.glass)
} else {
self
}
}
@ViewBuilder
func glassProminentButtonStyle() -> some View {
if #available(iOS 26.0, *) {
self.buttonStyle(.glassProminent)
} else {
self.buttonStyle(.borderedProminent)
}
}
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
+191
View File
@@ -0,0 +1,191 @@
import Foundation
import Flutter
import AVFoundation
class NativeBridge {
static let shared = NativeBridge()
private var channel: FlutterMethodChannel?
private var audioPlayChannel: FlutterBasicMessageChannel?
private weak var flutterViewController: FlutterViewController?
private let channelName = "com.m5stack.stackchan/native"
private let audioPlayChannelName = "com.m5stack.stackchan/audio_play"
private var audioEngine: AVAudioEngine?
private var audioPlayerNode: AVAudioPlayerNode?
private let sampleRate: Double = 16000.0
private let channels: AVAudioChannelCount = 1
private var isAudioInitialized = false
private let audioQueue = DispatchQueue(label: "com.stackchan.audio", qos: .userInitiated)
private let audioFormat: AVAudioFormat? = AVAudioFormat(
commonFormat: .pcmFormatFloat32,
sampleRate: 16_000,
channels: 1,
interleaved: true
)
private init() {}
func setup(with viewController: FlutterViewController) {
self.flutterViewController = viewController
let binaryMessenger = viewController.binaryMessenger
channel = FlutterMethodChannel(name: channelName, binaryMessenger: binaryMessenger)
audioPlayChannel = FlutterBasicMessageChannel(
name: audioPlayChannelName,
binaryMessenger: binaryMessenger,
codec: FlutterBinaryCodec()
)
audioPlayChannel?.setMessageHandler { [weak self] message, reply in
guard let self = self, let data = message as? Data else {
reply(nil)
return
}
self.audioQueue.async { [weak self] in
self?.playAudio(pcmData: data)
}
reply(nil)
}
}
private func playAudio(pcmData: Data) {
guard let audioFormat = audioFormat else {
print("❌ 音频格式初始化失败")
return
}
if !isAudioInitialized {
guard setupAudioSession() else {
print("❌ 会话初始化失败")
return
}
guard setupAudioEngine() else {
print("❌ 引擎初始化失败")
return
}
isAudioInitialized = true
print("✅ 音频初始化完成")
}
guard let engine = audioEngine, let playerNode = audioPlayerNode else {
resetAudio()
return
}
if !engine.isRunning {
do {
try engine.start()
} catch {
print("❌ 引擎启动失败: \(error)")
resetAudio()
return
}
}
var floatBuffer = pcmData.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [Float] in
let int16Buffer = bytes.bindMemory(to: Int16.self)
var floats = [Float](repeating: 0, count: int16Buffer.count)
//3
for i in 0..<int16Buffer.count {
floats[i] = min(max(Float(int16Buffer[i]) / Float(Int16.max) * 3.0, -1.0), 1.0)
}
return floats
}
guard let buffer = AVAudioPCMBuffer(pcmFormat: audioFormat,
frameCapacity: AVAudioFrameCount(floatBuffer.count)) else { return }
buffer.frameLength = buffer.frameCapacity
memcpy(buffer.floatChannelData![0], &floatBuffer, floatBuffer.count * MemoryLayout<Float>.size)
playerNode.scheduleBuffer(buffer)
if !playerNode.isPlaying {
playerNode.play()
}
}
// MARK: - -50
private func setupAudioSession() -> Bool {
do {
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playback, mode: .default)
try session.setActive(true)
return true
} catch {
let nsError = error as NSError
print("❌ 音频会话错误:\(nsError.code) - \(nsError.localizedDescription)")
return false
}
}
// MARK: - -10868
private func setupAudioEngine() -> Bool {
guard let audioFormat = audioFormat else {
print("❌ 音频格式为空")
return false
}
let engine = AVAudioEngine()
let playerNode = AVAudioPlayerNode()
engine.attach(playerNode)
//
engine.connect(playerNode, to: engine.mainMixerNode, format: audioFormat)
do {
try engine.start()
} catch {
print("❌ 引擎启动失败: \(error)")
return false
}
self.audioEngine = engine
self.audioPlayerNode = playerNode
return true
}
private func resetAudio() {
audioPlayerNode?.stop()
audioEngine?.stop()
audioEngine = nil
audioPlayerNode = nil
isAudioInitialized = false
}
func stopPlayPCM() {
audioQueue.async { [weak self] in
self?.resetAudio()
}
}
func sendMessage(method: Method,_ arguments: Any? = nil,_ completion: ((Any?) -> Void)? = nil) {
guard method != .unknown else {
print("⚠️ 未知方法")
completion?(nil)
return
}
channel?.invokeMethod(method.rawValue, arguments: arguments) { result in
if let error = result as? FlutterError {
print("❌ 发送失败:\(error)")
}
completion?(result)
}
}
func setMethodCallHandler(handler: @escaping FlutterMethodCallHandler) {
channel?.setMethodCallHandler(handler)
}
}
enum Method: String, CaseIterable {
case wifiName
case unknown
case stopPlayPCM
static func fromString(_ name: String) -> Method {
return Method(rawValue: name) ?? .unknown
}
}
+612
View File
@@ -0,0 +1,612 @@
//
// StackChanArView.swift
// Runner
//
// Created by 鸿 on 2026/2/5.
//
import RealityKit
import ARKit
class StackChanArView : NSObject, FlutterPlatformView, ARSessionDelegate, ARSCNViewDelegate {
private var arView: ARSCNView
private var channel: FlutterMethodChannel?
private var expressionChannel: FlutterEventChannel?
private var frameChannel: FlutterEventChannel?
private var expressionStreamHandler: ExpressionStreamHandler?
private var frameStreamHandler: FrameStreamHandler?
private var decorate: Int = 0
private var captureScreen: Bool = false
private let methodChannelName = "com.stackchan.ar.view"
private var lastCaptureTime: TimeInterval = 0
private let stackChanTargetSize = CGSize(width: 320, height: 240)
private var faceAnchorNode: SCNNode?
private var currentDecorationNode: SCNNode?
private var expressionLayer = ExpressionLayer(data: ExpressionData(leftEye: ExpressionItem(), rightEye: ExpressionItem(), mouth: ExpressionItem()),reverse: true)
private let emotionThresholds = EmotionThresholds()
private var lastSendTime: Date = Date(timeIntervalSince1970: 0)
func view() -> UIView {
return arView
}
init(
frame: CGRect,
viewId: Int64,
messenger: FlutterBinaryMessenger,
args: Any?
) {
arView = ARSCNView(frame: frame)
arView.contentMode = .scaleAspectFit
arView.autoresizingMask = [.flexibleWidth,.flexibleHeight]
super.init()
initializeChannels(viewId: viewId, messenger: messenger)
setupARSession()
}
private func initializeChannels(viewId: Int64, messenger: FlutterBinaryMessenger) {
let methodChannelName = "\(methodChannelName)_\(viewId)"
channel = FlutterMethodChannel(name: methodChannelName, binaryMessenger: messenger)
channel?.setMethodCallHandler { [weak self] call, result in
self?.handleMethodCall(call, result: result)
}
//
let expressionChannelName = "\(methodChannelName)_expression"
expressionStreamHandler = ExpressionStreamHandler()
expressionChannel = FlutterEventChannel(name: expressionChannelName, binaryMessenger: messenger)
expressionChannel?.setStreamHandler(expressionStreamHandler)
//
let frameChannelName = "\(methodChannelName)_frame"
frameStreamHandler = FrameStreamHandler()
frameChannel = FlutterEventChannel(name: frameChannelName, binaryMessenger: messenger)
frameChannel?.setStreamHandler(frameStreamHandler)
}
private func setupARSession() {
guard ARFaceTrackingConfiguration.isSupported else {
print("设备不支持面部追踪")
return
}
let configuration = ARFaceTrackingConfiguration()
configuration.isLightEstimationEnabled = true
if let format = ARFaceTrackingConfiguration.supportedVideoFormats.last {
configuration.videoFormat = format
}
configuration.videoHDRAllowed = true
arView.automaticallyUpdatesLighting = true
arView.session.delegate = self
arView.delegate = self
arView.session.run(configuration, options: [.resetTracking,.removeExistingAnchors])
}
private func handleMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "dispose":
dispose()
result(nil)
case "setDecorate":
if let decorateValue = call.arguments as? Int {
decorate = decorateValue
if let faceNode = self.faceAnchorNode {
self.updateDecorationOnNode(node: faceNode, decorate: decorate)
}
}
result(nil)
case "setCaptureScreen":
if let captureValue = call.arguments as? Bool {
captureScreen = captureValue
}
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
///
func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
DispatchQueue.main.async {
self.emotionDetection(session: session, anchors: anchors)
}
}
private func emotionDetection(session: ARSession, anchors: [ARAnchor]) {
if let anchor = anchors.first {
guard let faceAnchor = anchor as? ARFaceAnchor else { return }
let faceData = buildExpressionData(faceAnchor: faceAnchor)
let headData = detectHeadData(session:session,faceAnchor: faceAnchor)
self.updateDecoration(expressionData: faceData)
let now = Date()
if now.timeIntervalSince(self.lastSendTime) >= 0.5 {
let danceData = DanceData(leftEye: faceData.leftEye, rightEye: faceData.rightEye, mouth: faceData.mouth, yawServo: headData.yawServo, pitchServo: headData.pitchServo, durationMs: 1000)
let jsonString = danceData.toJsonString()
self.expressionStreamHandler?.sendExpressionData(jsonString)
lastSendTime = now
}
}
}
private func detectHeadData(session: ARSession,faceAnchor: ARFaceAnchor) -> MotionData {
let faceTransform = faceAnchor.transform
guard let cameraTransform = session.currentFrame?.camera.transform else {
return MotionData(pitchServo: MotionDataItem(angle: 0, speed: 500),
yawServo: MotionDataItem(angle: 0, speed: 500))
}
let relativeTransform = simd_mul(simd_inverse(cameraTransform), faceTransform)
let relativeMatrix = SCNMatrix4(relativeTransform)
let pitch = atan2(relativeMatrix.m31, relativeMatrix.m33) // Vertical rotation (pitch)
let yaw = asin(-relativeMatrix.m32) // Horizontal rotation (yaw)
let pitchDeg = pitch * 180.0 / .pi
let yawDeg = yaw * 180.0 / .pi
let yawServoAngle = max(-1280, min(1280, Int(-yawDeg * 20)))
let pitchServoAngle = max(0, min(900, Int(-pitchDeg * 10)))
let pitchItem = MotionDataItem(angle: pitchServoAngle, speed: 500)
let yawItem = MotionDataItem(angle: yawServoAngle, speed: 500)
return MotionData(pitchServo: pitchItem, yawServo: yawItem)
}
private func isAmazed(blendShapes: [ARFaceAnchor.BlendShapeLocation: NSNumber]) -> Bool {
let jawOpen = blendShapes[.jawOpen]?.floatValue ?? 0
let eyeWideLeft = blendShapes[.eyeWideLeft]?.floatValue ?? 0
let eyeWideRight = blendShapes[.eyeWideRight]?.floatValue ?? 0
let browInnerUp = blendShapes[.browInnerUp]?.floatValue ?? 0
let mouthFunnel = blendShapes[.mouthFunnel]?.floatValue ?? 0
// Amazed traits: wide eyes + raised brows + (open mouth or O-shape)
let isEyesWide = (eyeWideLeft + eyeWideRight) / 2 > emotionThresholds.amazed.eyeWide
let isBrowRaised = browInnerUp > emotionThresholds.amazed.browInnerUp
let isMouthAction = jawOpen > emotionThresholds.amazed.jawOpen ||
mouthFunnel > emotionThresholds.amazed.mouthFunnel
return isEyesWide && isBrowRaised && isMouthAction
}
private func buildExpressionData(faceAnchor: ARFaceAnchor) -> ExpressionData {
let blendShapes = faceAnchor.blendShapes
let eyeBlinkLeft = blendShapes[.eyeBlinkLeft]?.floatValue ?? 0
let leftEyeWeight = max(0, min(100, Int((1.0 - eyeBlinkLeft) * 100)))
let eyeBlinkRight = blendShapes[.eyeBlinkRight]?.floatValue ?? 0
let rightEyeWeight = max(0, min(100, Int((1.0 - eyeBlinkRight) * 100)))
let leftEye = ExpressionItem(
x: max(-100, min(100, Int(faceAnchor.lookAtPoint.x * 800))),
y: max(-100, min(100, Int(-faceAnchor.lookAtPoint.y * 500))),
rotation: 0,
weight: leftEyeWeight
)
let rightEye = ExpressionItem(
x: max(-100, min(100, Int(faceAnchor.lookAtPoint.x * 800))),
y: max(-100, min(100, Int(-faceAnchor.lookAtPoint.y * 500))),
rotation: 0,
weight: rightEyeWeight
)
let jawOpen = blendShapes[.jawOpen]?.floatValue ?? 0
let mouthSmileLeft = blendShapes[.mouthSmileLeft]?.floatValue ?? 0
let mouthSmileRight = blendShapes[.mouthSmileRight]?.floatValue ?? 0
let mouthX = max(-100, min(100, Int((mouthSmileRight - mouthSmileLeft) * 100)))
let mouthWeight = max(0, min(100, Int(jawOpen * 100)))
let mouth = ExpressionItem(
x: mouthX,
y: 0,
rotation: 0,
weight: mouthWeight
)
var expressionData = ExpressionData(leftEye: leftEye,
rightEye: rightEye,
mouth: mouth)
if isHappy(blendShapes: blendShapes) {
expressionData.leftEye.weight -= 35
expressionData.leftEye.rotation = -2150
expressionData.rightEye.weight -= 35
expressionData.rightEye.rotation = 2150
}
if isAnger(blendShapes: blendShapes) {
expressionData.leftEye.rotation = 450
expressionData.rightEye.rotation = -450
}
return expressionData
}
private func isAnger(blendShapes: [ARFaceAnchor.BlendShapeLocation: NSNumber]) -> Bool {
// Brow features
let browDownLeft = blendShapes[.browDownLeft]?.floatValue ?? 0
let browDownRight = blendShapes[.browDownRight]?.floatValue ?? 0
// Eye features
let eyeSquintLeft = blendShapes[.eyeSquintLeft]?.floatValue ?? 0
let eyeSquintRight = blendShapes[.eyeSquintRight]?.floatValue ?? 0
// Mouth features
let mouthFrownLeft = blendShapes[.mouthFrownLeft]?.floatValue ?? 0
let mouthFrownRight = blendShapes[.mouthFrownRight]?.floatValue ?? 0
let mouthPressLeft = blendShapes[.mouthPressLeft]?.floatValue ?? 0
let mouthPressRight = blendShapes[.mouthPressRight]?.floatValue ?? 0
// Nose features
let noseSneerLeft = blendShapes[.noseSneerLeft]?.floatValue ?? 0
let noseSneerRight = blendShapes[.noseSneerRight]?.floatValue ?? 0
// Calculate averages
let avgBrowDown = (browDownLeft + browDownRight) / 2
let avgEyeSquint = (eyeSquintLeft + eyeSquintRight) / 2
let avgMouthFrown = (mouthFrownLeft + mouthFrownRight) / 2
let avgMouthPress = (mouthPressLeft + mouthPressRight) / 2
let avgNoseSneer = (noseSneerLeft + noseSneerRight) / 2
// Anger scoring system
var angerScore = 0
if avgBrowDown > emotionThresholds.anger.browDown { angerScore += 3 }
if avgEyeSquint > emotionThresholds.anger.eyeSquint { angerScore += 2 }
if avgMouthFrown > emotionThresholds.anger.mouthFrown { angerScore += 2 }
if avgMouthPress > emotionThresholds.anger.mouthPress { angerScore += 1 }
if avgNoseSneer > emotionThresholds.anger.noseSneer { angerScore += 1 }
// Must reach threshold and include brow-down feature
return angerScore >= emotionThresholds.anger.minScore &&
avgBrowDown > emotionThresholds.anger.browDown
}
private func isTired(blendShapes: [ARFaceAnchor.BlendShapeLocation: NSNumber]) -> Bool {
let eyeBlinkLeft = blendShapes[.eyeBlinkLeft]?.floatValue ?? 0
let eyeBlinkRight = blendShapes[.eyeBlinkRight]?.floatValue ?? 0
let eyeSquintLeft = blendShapes[.eyeSquintLeft]?.floatValue ?? 0
let eyeSquintRight = blendShapes[.eyeSquintRight]?.floatValue ?? 0
// Tired traits: eyes closed or squinting
let eyesClosed = (eyeBlinkLeft > emotionThresholds.tired.eyeClose &&
eyeBlinkRight > emotionThresholds.tired.eyeClose) ||
(eyeSquintLeft > emotionThresholds.tired.eyeSquint &&
eyeSquintRight > emotionThresholds.tired.eyeSquint)
return eyesClosed
}
private func isHappy(blendShapes: [ARFaceAnchor.BlendShapeLocation: NSNumber]) -> Bool {
let smileLeft = blendShapes[.mouthSmileLeft]?.floatValue ?? 0
let smileRight = blendShapes[.mouthSmileRight]?.floatValue ?? 0
let eyeSquintLeft = blendShapes[.eyeSquintLeft]?.floatValue ?? 0
let eyeSquintRight = blendShapes[.eyeSquintRight]?.floatValue ?? 0
let cheekSquintLeft = blendShapes[.cheekSquintLeft]?.floatValue ?? 0
let cheekSquintRight = blendShapes[.cheekSquintRight]?.floatValue ?? 0
// Calculate overall smile intensity
let smileIntensity = (smileLeft + smileRight) / 2
let eyeSquintIntensity = (eyeSquintLeft + eyeSquintRight) / 2
let cheekSquintIntensity = (cheekSquintLeft + cheekSquintRight) / 2
// Happy expression requires a clear smile with eye muscle involvement
return smileIntensity > emotionThresholds.happy.smile &&
(eyeSquintIntensity > emotionThresholds.happy.eyeSquint ||
cheekSquintIntensity > emotionThresholds.happy.cheekSquint)
}
private func isShy(faceAnchor: ARFaceAnchor, blendShapes: [ARFaceAnchor.BlendShapeLocation: NSNumber]) -> Bool {
// 1. Slight or clear head tilt downward
let transform = faceAnchor.transform
let rotation = SCNMatrix4(transform)
let pitch = asin(-rotation.m32) //
let isHeadDown = pitch > emotionThresholds.shy.headPitch
// 2. Mouth closed with a slight smile
let mouthClose = blendShapes[.mouthClose]?.floatValue ?? 0
let smileLeft = blendShapes[.mouthSmileLeft]?.floatValue ?? 0
let smileRight = blendShapes[.mouthSmileRight]?.floatValue ?? 0
let smileIntensity = (smileLeft + smileRight) / 2
let isMouthClosedSmile = mouthClose > emotionThresholds.shy.mouthPress && smileIntensity > emotionThresholds.shy.smile
// 3. Eyes looking sideways or downward
let lookAt = faceAnchor.lookAtPoint
let isLookingSideways = abs(lookAt.x) > emotionThresholds.gaze.xThreshold // Looking left or right
let isLookingDown = lookAt.y < -emotionThresholds.gaze.yThreshold // Looking downward
return isHeadDown && isMouthClosedSmile && (isLookingSideways || isLookingDown)
}
func renderer(_ renderer: any SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
guard anchor is ARFaceAnchor else { return nil }
let node = SCNNode()
self.faceAnchorNode = node
updateDecorationOnNode(node: node, decorate: decorate)
return node
}
private func updateDecorationOnNode(node: SCNNode, decorate: Int) {
currentDecorationNode?.removeFromParentNode()
if decorate == 1 {
let container = SCNNode()
let stackChanModelNode = createStackChanModel()
container.addChildNode(stackChanModelNode)
let expressionPlaneNode = createPlane()
container.addChildNode(expressionPlaneNode)
node.addChildNode(container)
currentDecorationNode = container
} else if decorate == 2 {
let noseNode = createEmojiNoseNode(emoji: "🐽")
node.addChildNode(noseNode)
currentDecorationNode = noseNode
}
}
private func createEmojiNoseNode(emoji: String) -> SCNNode {
let size = CGSize(width: 300, height: 300)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
(emoji as NSString).draw(in: CGRect(origin: .zero, size: size),
withAttributes: [.font: UIFont.systemFont(ofSize: size.width - 20)])
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let nosePlane = SCNPlane(width: 0.05, height: 0.05)
nosePlane.firstMaterial?.diffuse.contents = image
nosePlane.firstMaterial?.isDoubleSided = true
let noseNode = SCNNode(geometry: nosePlane)
noseNode.name = "noseNode"
noseNode.position = SCNVector3(0, 0, 0.07)
return noseNode
}
private func createPlane() -> SCNNode {
let plane = SCNPlane(width: 0.16, height: 0.12)
let layerWidth = plane.width * 1000
let layerHeight = plane.height * 1000
expressionLayer.frame = CGRect(origin: .zero, size: CGSize(width: layerWidth, height: layerHeight))
expressionLayer.setNeedsDisplay()
let material = SCNMaterial()
material.diffuse.contents = UIColor.black
plane.materials = [material]
let planeNode = SCNNode(geometry: plane)
planeNode.name = "expressionPlane"
planeNode.position = SCNVector3(0, 0.03, 0.07)
return planeNode
}
private func createStackChanModel() -> SCNNode {
guard let scene = SCNScene(named: "StackChanModel.scn"),
let modelNode = scene.rootNode.childNodes.first else {
print("no model")
return SCNNode()
}
modelNode.name = "StackChanModel"
modelNode.scale = SCNVector3(0.004, 0.004, 0.004)
modelNode.opacity = 0.4
modelNode.position = SCNVector3(0, 0.03, 0)
modelNode.eulerAngles = SCNVector3Zero
modelNode.eulerAngles.x = -Float.pi / 2
//
if let foundation = modelNode.childNode(withName: "_00_stackchan450_3",recursively: false),let centralComponent = modelNode.childNode(withName: "_00_stackchan450_2", recursively: false) {
foundation.opacity = 0
centralComponent.opacity = 0
}
return modelNode
}
private func getGazeDirection(faceAnchor: ARFaceAnchor) -> String {
let lookAtPoint = faceAnchor.lookAtPoint
var direction = ""
if lookAtPoint.x < -emotionThresholds.gaze.xThreshold {
direction += "Left"
} else if lookAtPoint.x > emotionThresholds.gaze.xThreshold {
direction += "Right"
}
if lookAtPoint.y < -emotionThresholds.gaze.yThreshold {
direction += "Down"
} else if lookAtPoint.y > emotionThresholds.gaze.yThreshold {
direction += "Up"
}
return direction.isEmpty ? "Looking Forward" : direction + " Look"
}
private func getHeadDirection(faceAnchor: ARFaceAnchor) -> String {
let transform = faceAnchor.transform
let rotation = SCNMatrix4(transform)
let yaw = atan2(rotation.m31, rotation.m33)
let pitch = asin(-rotation.m32)
var horizontal = ""
var vertical = ""
if yaw < -emotionThresholds.head.yawThreshold {
horizontal = "Left"
} else if yaw > emotionThresholds.head.yawThreshold {
horizontal = "Right"
}
// Correct vertical direction
if pitch < -emotionThresholds.head.pitchThreshold {
vertical = "Up"
} else if pitch > emotionThresholds.head.pitchThreshold {
vertical = "Down"
}
if horizontal.isEmpty && vertical.isEmpty {
return "Head Facing Forward"
} else {
return "Head Facing " + vertical + horizontal
}
}
private lazy var expressionRenderer: UIGraphicsImageRenderer = {
let format = UIGraphicsImageRendererFormat.default()
format.scale = UIScreen.main.scale
format.opaque = false
return UIGraphicsImageRenderer(
size: expressionLayer.bounds.size,
format: format
)
}()
///
func renderer(_ renderer: any SCNSceneRenderer, updateAtTime time: TimeInterval) {
if captureScreen {
if time - lastCaptureTime >= 0.5 {
lastCaptureTime = time
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
let renderedImage = arView.snapshot()
if let jpedData = renderedImage.compress(to: stackChanTargetSize,memorySize: 0.02,cropCenter: true) {
self.frameStreamHandler?.sendFrameData(jpedData)
}
}
}
}
}
///
private func updateDecoration(expressionData: ExpressionData) {
DispatchQueue.main.async {
if self.decorate == 1 {
let scene = self.arView.scene
guard let planeNode = scene.rootNode.childNode(withName: "expressionPlane", recursively: true),
let plane = planeNode.geometry as? SCNPlane else {
return
}
self.expressionLayer.data = expressionData
self.expressionLayer.setNeedsDisplay()
let originalImage = self.expressionRenderer.image { ctx in
self.expressionLayer.render(in: ctx.cgContext)
}
let image = UIImage(
cgImage: originalImage.cgImage!,
scale: originalImage.scale,
orientation: .upMirrored
)
plane.materials.first?.diffuse.contents = image
}
}
}
private func dispose() {
arView.session.pause()
channel?.setMethodCallHandler(nil)
expressionChannel?.setStreamHandler(nil)
frameChannel?.setStreamHandler(nil)
arView.scene.rootNode.childNodes.forEach { $0.removeFromParentNode() }
}
}
class ExpressionStreamHandler: NSObject,FlutterStreamHandler {
private var eventSink: FlutterEventSink?
func sendExpressionData(_ data: String) {
guard let sink = eventSink else { return }
DispatchQueue.main.async {
sink(data)
}
}
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
return nil
}
}
class FrameStreamHandler: NSObject, FlutterStreamHandler {
private var eventSink: FlutterEventSink?
func sendFrameData(_ data: Data) {
guard let sink = eventSink else { return }
// 线
DispatchQueue.main.async {
sink(FlutterStandardTypedData(bytes: data))
}
}
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
return nil
}
}
private struct EmotionThresholds {
// Happy emotion thresholds
struct Happy {
let smile: Float = 0.3
let eyeSquint: Float = 0.15
let cheekSquint: Float = 0.1
}
// Shy emotion thresholds
struct Shy {
let headPitch: Float = 0.08
let eyeSquint: Float = 0.1
let mouthPress: Float = 0.25
let smile: Float = 0.15
}
// Amazed emotion thresholds
struct Amazed {
let eyeWide: Float = 0.4
let browInnerUp: Float = 0.3
let jawOpen: Float = 0.4
let mouthFunnel: Float = 0.3
}
// Angry emotion thresholds
struct Anger {
let browDown: Float = 0.35
let eyeSquint: Float = 0.25
let mouthFrown: Float = 0.2
let mouthPress: Float = 0.2
let noseSneer: Float = 0.15
let minScore: Int = 5
}
// Tired emotion thresholds
struct Tired {
let eyeClose: Float = 0.7
let eyeSquint: Float = 0.5
let jawOpen: Float = 0.3
}
// Gaze detection thresholds
struct Gaze {
let xThreshold: Float = 0.02
let yThreshold: Float = 0.02
}
// Head direction thresholds
struct Head {
let yawThreshold: Float = 0.25
let pitchThreshold: Float = 0.25
}
let happy = Happy()
let shy = Shy()
let amazed = Amazed()
let anger = Anger()
let tired = Tired()
let gaze = Gaze()
let head = Head()
}
+486
View File
@@ -0,0 +1,486 @@
//
// StackChanRobot.swift
// Runner
//
// Created by 鸿 on 2026/1/30.
//
import SceneKit
class StackChanRobot: NSObject, FlutterPlatformView, FlutterStreamHandler {
private let sceneView: SCNView
private let methodChannel: FlutterMethodChannel
private var currentDanceData: DanceData?
private let expressionLayer = ExpressionLayer(data: ExpressionData(leftEye: ExpressionItem(weight: 100), rightEye: ExpressionItem(weight: 100), mouth: ExpressionItem()))
private let planeNodeName = "expressionPlane"
private let rotateKey = "autoRotate"
private var topLook: Bool = false
private let methodChannelName = "com.stackchan.robot.method"
private var defaultCameraNode: SCNNode?
private var topCameraNode: SCNNode?
init(
frame: CGRect,
viewId: Int64,
messenger: FlutterBinaryMessenger,
args: Any?
) {
self.sceneView = SCNView(frame: frame)
self.methodChannel = FlutterMethodChannel(
name: methodChannelName + "_\(viewId)",
binaryMessenger: messenger
)
super.init()
methodChannel.setMethodCallHandler(handleMethodCall)
setupSceneView()
setupInitialScene()
}
func view() -> UIView {
return sceneView
}
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
return nil
}
private func handleMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "updateDanceData":
if let json = call.arguments as? String {
updateDanceData(from: json)
result(nil)
} else {
result(FlutterError(
code: "INVALID_ARGS",
message: "Expected JSON string",
details: nil
))
}
case "setTopLook":
if let topLook = call.arguments as? Bool {
self.topLook = topLook
setupCamera()
result(nil)
} else {
result(FlutterError(
code: "INVALID_ARGS",
message: "Expected boolean value",
details: nil
))
}
case "setAllowsCameraControl":
if let allowsControl = call.arguments as? Bool {
sceneView.allowsCameraControl = allowsControl
result(nil)
} else {
result(FlutterError(
code: "INVALID_ARGS",
message: "Expected boolean value",
details: nil
))
}
case "dispose":
cleanup()
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
private func setupSceneView() {
sceneView.antialiasingMode = .multisampling4X
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = false
sceneView.backgroundColor = .clear
sceneView.isPlaying = true
}
private func setupInitialScene() {
guard let scene = SCNScene(named: "StackChanModel.scn") else {
print("Failed to load StackChanModel.scn")
return
}
scene.rootNode.eulerAngles = SCNVector3Zero
scene.rootNode.eulerAngles.x = -Float.pi / 2
scene.rootNode.position.y = scene.rootNode.position.y + 25
scene.rootNode.position.z = scene.rootNode.position.z - 35
if let rootNode = scene.rootNode.childNodes.first {
setupRobotHierarchy(rootNode: rootNode, scene: scene)
if let defaultCamera = scene.rootNode.childNode(withName: "camera", recursively: true) {
defaultCameraNode = defaultCamera
} else {
defaultCameraNode = createDefaultCameraNode(rootNode: rootNode)
rootNode.addChildNode(defaultCameraNode!)
}
sceneView.pointOfView = defaultCameraNode
}
sceneView.scene = scene
}
private func createDefaultCameraNode(rootNode: SCNNode) -> SCNNode {
let cameraNode = SCNNode()
cameraNode.name = "defaultCamera"
let camera = SCNCamera()
camera.zFar = 200
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: -100, z: 0)
let lookAtConstraint = SCNLookAtConstraint(target: rootNode)
lookAtConstraint.isGimbalLockEnabled = true
cameraNode.constraints = [lookAtConstraint]
return cameraNode
}
private func createTopCameraNode(rootNode: SCNNode) -> SCNNode {
let cameraNode = SCNNode()
cameraNode.name = "leftTopCamera"
let camera = SCNCamera()
camera.zFar = 300
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: -100, z: 70)
let lookAtConstraint = SCNLookAtConstraint(target: rootNode)
lookAtConstraint.isGimbalLockEnabled = true
cameraNode.constraints = [lookAtConstraint]
return cameraNode
}
private func setupRobotHierarchy(rootNode: SCNNode, scene: SCNScene) {
guard let foundation = rootNode.childNode(withName: "_00_stackchan450_3", recursively: false),
let centralComponent = rootNode.childNode(withName: "_00_stackchan450_2", recursively: false),
let head = rootNode.childNode(withName: "_00_stackchan450_1", recursively: false) else {
return
}
let yawAxis = SCNNode()
yawAxis.name = "yawAxis"
let centralWorldPos = centralComponent.worldPosition
yawAxis.worldPosition.z = centralWorldPos.z + 15
foundation.addChildNode(yawAxis)
let centralWorldTransform = centralComponent.worldTransform
yawAxis.addChildNode(centralComponent)
centralComponent.setWorldTransform(centralWorldTransform)
// Setup pitch axis for head movement
let headWorldTransform = head.worldTransform
let pitchAxis = SCNNode()
pitchAxis.name = "pitchAxis"
pitchAxis.worldPosition.z = pitchAxis.worldPosition.z - 20
centralComponent.addChildNode(pitchAxis)
pitchAxis.addChildNode(head)
head.setWorldTransform(headWorldTransform)
// Add expression plane to head
addExpressionPlane(to: head)
}
private func addExpressionPlane(to head: SCNNode) {
let plane = SCNPlane(width: 42, height: 32)
let magnification: CGFloat = 5
let size = CGSize(width: magnification * plane.width, height: magnification * plane.height)
expressionLayer.frame = CGRect(origin: .zero, size: size)
expressionLayer.setNeedsDisplay()
let material = SCNMaterial()
plane.materials = [material]
let planeNode = SCNNode(geometry: plane)
planeNode.name = planeNodeName
planeNode.position = head.position
planeNode.position.z = planeNode.position.z - 4.5
head.addChildNode(planeNode)
}
private func setupCamera() {
guard let scene = sceneView.scene,
let rootNode = scene.rootNode.childNodes.first else {
return
}
rootNode.childNodes.filter { $0.name == "leftTopCamera" }.forEach { $0.removeFromParentNode() }
if topLook {
if topCameraNode == nil {
topCameraNode = createTopCameraNode(rootNode: rootNode)
rootNode.addChildNode(topCameraNode!)
}
sceneView.pointOfView = topCameraNode
} else {
if defaultCameraNode == nil {
defaultCameraNode = createDefaultCameraNode(rootNode: rootNode)
rootNode.addChildNode(defaultCameraNode!)
}
sceneView.pointOfView = defaultCameraNode
}
}
private func updateDanceData(from json: String) {
guard let danceData = DanceData.from(jsonString: json) else {
return
}
currentDanceData = danceData
DispatchQueue.main.async {
self.applyDanceData(danceData)
}
}
private func applyDanceData(_ data: DanceData) {
guard let scene = sceneView.scene,
let rootNode = scene.rootNode.childNodes.first else {
return
}
// Update servo positions
updateServos(rootNode: rootNode, data: data)
// Update RGB color
updateRGBColor(rootNode: rootNode, data: data)
// Update expression
updateExpression(data: data)
}
private func updateServos(rootNode: SCNNode, data: DanceData) {
if let yawAxis = rootNode.childNode(withName: "yawAxis", recursively: true),
let pitchAxis = rootNode.childNode(withName: "pitchAxis", recursively: true) {
yawAxis.removeAction(forKey: rotateKey)
// Update yaw (rotation around Y axis)
if data.yawServo.rotate == 0 {
let clampedYaw = max(-128, min(128, data.yawServo.angle / 10))
let yawRadians = Float(clampedYaw) * Float.pi / 180.0
yawAxis.rotation = SCNVector4(0, 1, 0, yawRadians)
} else {
let rotateSpeed = max(-100, min(100, data.yawServo.rotate / 10))
let radiansPerSecond = Float(rotateSpeed) / 100.0 * Float.pi * 2
let rotateAction = SCNAction.customAction(duration: .infinity) { node, _ in
let deltaTime: Float = 1.0 / 60.0
node.eulerAngles.y += radiansPerSecond * deltaTime
}
yawAxis.runAction(rotateAction, forKey: rotateKey)
}
// Update pitch (head tilt)
let clampedPitch = max(0, min(90, data.pitchServo.angle / 10))
let pitchRadians = Float(clampedPitch) * Float.pi / 180.0
pitchAxis.eulerAngles.x = -pitchRadians
}
}
private func updateRGBColor(rootNode: SCNNode, data: DanceData) {
rootNode.enumerateChildNodes { node, _ in
if let materials = node.geometry?.materials {
for material in materials {
if material.name == "MTL12" {
if let color = UIColor(hex: data.leftRgbColor) {
material.emission.contents = color
}
break
}
}
}
}
}
private func updateExpression(data: DanceData) {
guard let planeNode = sceneView.scene?.rootNode.childNode(withName: planeNodeName, recursively: true),
let plane = planeNode.geometry as? SCNPlane else {
return
}
let expressionData = ExpressionData(
leftEye: data.leftEye,
rightEye: data.rightEye,
mouth: data.mouth
)
expressionLayer.data = expressionData
expressionLayer.setNeedsDisplay()
let newImage = expressionRenderer().image { ctx in
self.expressionLayer.render(in: ctx.cgContext)
}
plane.firstMaterial?.diffuse.contents = newImage
}
private func expressionRenderer() -> UIGraphicsImageRenderer {
let format = UIGraphicsImageRendererFormat.default()
format.scale = UIScreen.main.scale
format.opaque = false
return UIGraphicsImageRenderer(
size: expressionLayer.bounds.size,
format: format
)
}
private func cleanup() {
// Stop all animations
sceneView.scene?.rootNode.childNodes.forEach { node in
node.removeAllActions()
node.removeFromParentNode()
}
// Clean up scene
sceneView.scene = nil
sceneView.isPlaying = false
// Remove method call handler
methodChannel.setMethodCallHandler(nil)
}
deinit {
cleanup()
}
}
class ExpressionLayer: CALayer {
var data: ExpressionData
let reverse: Bool
init(data: ExpressionData, reverse: Bool = false) {
self.data = data
self.reverse = reverse
super.init()
self.contentsScale = UIScreen.main.scale
self.setNeedsDisplay()
}
override init(layer: Any) {
if let layer = layer as? ExpressionLayer {
self.data = layer.data
self.reverse = layer.reverse
} else {
self.data = ExpressionData(leftEye: ExpressionItem(), rightEye: ExpressionItem(), mouth: ExpressionItem())
self.reverse = false
}
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(in ctx: CGContext) {
let rect = self.frame
// Background
ctx.setFillColor(UIColor.black.withAlphaComponent(0.7).cgColor)
ctx.fill(rect)
let eyeSize = rect.width / 10
func drawEye(_ item: ExpressionItem, at point: CGPoint) {
// Calculate scale based on size (-100 to 100)
// 0 -> 1.0 (keep current size)
// -100 -> 0.5 (half normal radius)
// 100 -> 2.0 (double normal radius)
let clampedSize = max(-100, min(100, item.size))
let sizeScale: CGFloat
if clampedSize >= 0 {
sizeScale = 1.0 + CGFloat(clampedSize) / 100.0
} else {
sizeScale = 1.0 + CGFloat(clampedSize) / 200.0
}
let scaledEyeSize = eyeSize * sizeScale
let visibleHeight = scaledEyeSize * (CGFloat(item.weight) / 100)
let centerX = point.x + CGFloat(item.x / 10) + eyeSize / 2
let centerY = point.y + CGFloat(item.y / 10) + eyeSize / 2
let eyeRect = CGRect(
x: centerX - scaledEyeSize / 2,
y: centerY - scaledEyeSize / 2,
width: scaledEyeSize,
height: scaledEyeSize
)
ctx.saveGState()
// Rotation
let rotationDegrees = CGFloat(item.rotation) / 10.0
let center = CGPoint(x: eyeRect.midX, y: eyeRect.midY)
ctx.translateBy(x: center.x, y: center.y)
ctx.rotate(by: rotationDegrees * .pi / 180)
ctx.translateBy(x: -center.x, y: -center.y)
// Clip height
let maskRect = CGRect(
x: eyeRect.minX,
y: eyeRect.maxY - visibleHeight,
width: scaledEyeSize,
height: visibleHeight
)
ctx.addRect(maskRect)
ctx.clip()
ctx.setFillColor(UIColor.white.cgColor)
ctx.fillEllipse(in: eyeRect)
ctx.restoreGState()
}
let eyeY = (rect.height * 0.4) - (eyeSize / 2)
let leftEyePoint = CGPoint(x: (rect.width / 4) - (eyeSize / 2), y: eyeY)
let rightEyePoint = CGPoint(x: (rect.width / 4 * 3) - (eyeSize / 2), y: eyeY)
if reverse {
// Temporarily swap rotation angles
let leftEyeRotation = data.leftEye.rotation
let rightEyeRotation = data.rightEye.rotation
var leftEye = data.leftEye
var rightEye = data.rightEye
leftEye.rotation = rightEyeRotation
rightEye.rotation = leftEyeRotation
drawEye(leftEye, at: rightEyePoint)
drawEye(rightEye, at: leftEyePoint)
} else {
drawEye(data.leftEye, at: leftEyePoint)
drawEye(data.rightEye, at: rightEyePoint)
}
// Draw mouth
ctx.saveGState()
let width = rect.width * 0.3 - CGFloat(data.mouth.weight / 10)
let height = 3 + CGFloat(data.mouth.weight) * 0.2
let x = ((rect.width - width) / 2) + CGFloat(data.mouth.x / 10)
let y = (rect.height * 0.65) + CGFloat(data.mouth.y / 10)
let rotationDegrees = CGFloat(data.mouth.rotation) / 10.0
let center = CGPoint(x: x + width / 2, y: y + height / 2)
ctx.translateBy(x: center.x, y: center.y)
ctx.rotate(by: rotationDegrees * .pi / 180)
ctx.translateBy(x: -center.x, y: -center.y)
let mouthRect = CGRect(x: x, y: y, width: width, height: height)
let mouthPath = UIBezierPath(roundedRect: mouthRect, cornerRadius: height / 2)
ctx.addPath(mouthPath.cgPath)
ctx.setFillColor(UIColor.white.cgColor)
ctx.fillPath()
ctx.restoreGState()
}
}
@@ -0,0 +1,136 @@
//
// StackChanRotaryRobot.swift
// Runner
//
// Created by 鸿 on 2026/1/30.
//
import SceneKit
class StackChanRotaryRobot: NSObject, FlutterPlatformView, FlutterStreamHandler {
private let expressionLayer = ExpressionLayer(data: ExpressionData(leftEye: ExpressionItem(weight: 100), rightEye: ExpressionItem(weight: 100), mouth: ExpressionItem()))
private let planeNodeName = "expressionPlane"
private let sceneView: SCNView
func view() -> UIView {
return sceneView
}
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
return nil
}
init(
frame: CGRect,
viewId: Int64,
messenger: FlutterBinaryMessenger,
args: Any?
) {
self.sceneView = SCNView(frame: frame)
super.init()
setupSceneView()
setupInitialScene()
}
private func setupSceneView() {
sceneView.antialiasingMode = .multisampling4X
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = false
sceneView.backgroundColor = .clear
sceneView.isPlaying = true
}
private func setupInitialScene() {
guard let scene = SCNScene(named: "StackChanModel.scn") else {
print("Failed to load StackChanModel.scn")
return
}
scene.rootNode.eulerAngles = SCNVector3Zero
scene.rootNode.eulerAngles.x = -Float.pi / 2
scene.rootNode.position.y = scene.rootNode.position.y + 25
scene.rootNode.position.z = scene.rootNode.position.z - 45
let clampedPitch = max(0, min(900, 200))
let pitchRatio = Float(clampedPitch) / 900.0
let pitchAngle = -Float.pi / 2 * (1 + pitchRatio)
scene.rootNode.eulerAngles.x = pitchAngle
if let rootNode = scene.rootNode.childNodes.first {
setupRobotHierarchy(rootNode: rootNode, scene: scene)
}
sceneView.scene = scene
}
private func setupRobotHierarchy(rootNode: SCNNode, scene: SCNScene) {
guard let foundation = rootNode.childNode(withName: "_00_stackchan450_3", recursively: false),
let centralComponent = rootNode.childNode(withName: "_00_stackchan450_2", recursively: false),
let head = rootNode.childNode(withName: "_00_stackchan450_1", recursively: false) else {
return
}
let yawAxis = SCNNode()
yawAxis.name = "yawAxis"
let centralWorldPos = centralComponent.worldPosition
yawAxis.worldPosition.z = centralWorldPos.z + 15
foundation.addChildNode(yawAxis)
let centralWorldTransform = centralComponent.worldTransform
yawAxis.addChildNode(centralComponent)
centralComponent.setWorldTransform(centralWorldTransform)
// Setup pitch axis for head movement
let headWorldTransform = head.worldTransform
let pitchAxis = SCNNode()
pitchAxis.name = "pitchAxis"
pitchAxis.worldPosition.z = pitchAxis.worldPosition.z - 20
centralComponent.addChildNode(pitchAxis)
pitchAxis.addChildNode(head)
head.setWorldTransform(headWorldTransform)
// Add expression plane to head
addExpressionPlane(to: head)
//
let rotateAction = SCNAction.rotateBy(x: 0, y: CGFloat(2 * Double.pi), z: 0, duration: 5)
let repeatAction = SCNAction.repeatForever(rotateAction)
scene.rootNode.runAction(repeatAction)
}
private func addExpressionPlane(to head: SCNNode) {
let plane = SCNPlane(width: 42, height: 32)
let magnification: CGFloat = 5
let size = CGSize(width: magnification * plane.width, height: magnification * plane.height)
expressionLayer.frame = CGRect(origin: .zero, size: size)
expressionLayer.setNeedsDisplay()
let newImage = expressionRenderer().image { ctx in
self.expressionLayer.render(in: ctx.cgContext)
}
let material = SCNMaterial()
material.diffuse.contents = newImage
plane.materials = [material]
let planeNode = SCNNode(geometry: plane)
planeNode.name = planeNodeName
planeNode.position = head.position
planeNode.position.z = planeNode.position.z - 4.5
head.addChildNode(planeNode)
}
private func expressionRenderer() -> UIGraphicsImageRenderer {
let format = UIGraphicsImageRendererFormat.default()
format.scale = UIScreen.main.scale
format.opaque = false
return UIGraphicsImageRenderer(
size: expressionLayer.bounds.size,
format: format
)
}
}
+64
View File
@@ -0,0 +1,64 @@
//
// ViewFactory.swift
// Runner
//
// Created by 鸿 on 2026/1/30.
//
import Flutter
import UIKit
class StackChanRobotViewFactory: NSObject, FlutterPlatformViewFactory {
private let messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> any FlutterPlatformView {
return StackChanRobot(frame: frame, viewId: viewId, messenger: messenger, args: args)
}
func createArgsCodec() -> any FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
}
class StackChanRotaryRobotViewFactory: NSObject, FlutterPlatformViewFactory {
private let messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> any FlutterPlatformView {
return StackChanRotaryRobot(frame: frame, viewId: viewId, messenger: messenger, args: args)
}
func createArgsCodec() -> any FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
}
class StackChanArViewFactory: NSObject, FlutterPlatformViewFactory {
private let messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> any FlutterPlatformView {
return StackChanArView(frame: frame, viewId: viewId, messenger: messenger, args: args)
}
func createArgsCodec() -> any FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
}
+12
View File
@@ -0,0 +1,12 @@
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.
}
}