mirror of
https://github.com/m5stack/StackChan.git
synced 2026-05-20 11:49:45 -07:00
ios code
This commit is contained in:
+2
-1
@@ -1 +1,2 @@
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
.idea/
|
||||
+62
-1
@@ -1 +1,62 @@
|
||||
# StackChan App
|
||||
# Project Setup and Running Instructions
|
||||
|
||||
## 1. Clone the repository
|
||||
```bash
|
||||
git clone https://github.com/m5stack/StackChan
|
||||
cd StackChan/app
|
||||
```
|
||||
|
||||
## 2. Open the project in Xcode
|
||||
Open the project in Xcode:
|
||||
|
||||
Double‑click the `.xcodeproj` file, or open Xcode → File → Open, then select the project.
|
||||
|
||||
1. Select your target device or simulator.
|
||||
|
||||
### Connect an iPhone (Optional but Recommended)
|
||||
- Connect your iPhone to the Mac using a USB cable.
|
||||
- Unlock the iPhone and tap **Trust This Computer** if prompted.
|
||||
- In Xcode, select your iPhone as the run destination at the top.
|
||||
|
||||
### Enable Developer Mode on iPhone (iOS 16+)
|
||||
> **Important:** Developer Mode will only appear after the iPhone has been connected to Xcode at least once.
|
||||
If you do not see this option, make sure your iPhone is connected to the Mac, unlocked, trusted, and recognized by Xcode.
|
||||
- On the iPhone, go to **Settings → Privacy & Security → Developer Mode**.
|
||||
- Turn on Developer Mode and restart the iPhone.
|
||||
- After restart, confirm enabling Developer Mode.
|
||||
|
||||
## 3. Configure Signing & Capabilities
|
||||
This step allows Xcode to install the app on your iPhone.
|
||||
|
||||
1. In Xcode, select the project in the left sidebar.
|
||||
2. Select the app target.
|
||||
3. Open the **Signing & Capabilities** tab.
|
||||
4. Sign in with your Apple ID (Xcode → Settings → Accounts → Add Apple ID).
|
||||
5. Set **Team** to your Apple ID.
|
||||
6. Change **Bundle Identifier** to a unique value, for example:
|
||||
`com.yourname.stackchan`
|
||||
7. Ensure no red error messages remain.
|
||||
|
||||
> **Note:** A free Apple ID is sufficient for testing on your own iPhone.
|
||||
|
||||
## 4. Modify network configuration
|
||||
Before running the app, you need to set the correct server IP:
|
||||
|
||||
1. Open the file `Network/Urls.swift`.
|
||||
2. Find the line defining the base URL, for example:
|
||||
```swift
|
||||
// Base URL configured according to the server's IP
|
||||
static let url = "192.168.51.24:12800/"
|
||||
```
|
||||
3. Replace the IP address (`192.168.51.24`) with the IP of the computer where the server is running.
|
||||
4. Save the file.
|
||||
|
||||
## 5. Run the project
|
||||
Press `Cmd + R` to build and run the app.
|
||||
|
||||
> **Note:** The first build may take several minutes as Xcode prepares the environment.
|
||||
|
||||
If running on an iPhone for the first time, you may need to trust yourself as a developer:
|
||||
- On your iPhone, go to **Settings → General → VPN & Device Management → Trust Developer** and trust the developer profile that appears.
|
||||
|
||||
The app will now connect to the server at the IP you configured.
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0E4478B92F0A538600010197 /* README.MD in Resources */ = {isa = PBXBuildFile; fileRef = 0E4478B82F0A538600010197 /* README.MD */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0E4478B82F0A538600010197 /* README.MD */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.MD; sourceTree = "<group>"; };
|
||||
0EBD7D382ECDA27C0001A9D1 /* StackChan.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StackChan.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
0EBD7E222ECDC9510001A9D1 /* Exceptions for "StackChan" folder in "StackChan" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 0EBD7D372ECDA27C0001A9D1 /* StackChan */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
0EBD7D3A2ECDA27C0001A9D1 /* StackChan */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
0EBD7E222ECDC9510001A9D1 /* Exceptions for "StackChan" folder in "StackChan" target */,
|
||||
);
|
||||
explicitFileTypes = {
|
||||
Info.plist = text.xml;
|
||||
};
|
||||
path = StackChan;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
0EBD7D352ECDA27C0001A9D1 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0EBD7D2F2ECDA27C0001A9D1 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0EBD7D3A2ECDA27C0001A9D1 /* StackChan */,
|
||||
0EBD7D392ECDA27C0001A9D1 /* Products */,
|
||||
0E4478B82F0A538600010197 /* README.MD */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0EBD7D392ECDA27C0001A9D1 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0EBD7D382ECDA27C0001A9D1 /* StackChan.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
0EBD7D372ECDA27C0001A9D1 /* StackChan */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0EBD7D432ECDA27D0001A9D1 /* Build configuration list for PBXNativeTarget "StackChan" */;
|
||||
buildPhases = (
|
||||
0EBD7D342ECDA27C0001A9D1 /* Sources */,
|
||||
0EBD7D352ECDA27C0001A9D1 /* Frameworks */,
|
||||
0EBD7D362ECDA27C0001A9D1 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
0EBD7D3A2ECDA27C0001A9D1 /* StackChan */,
|
||||
);
|
||||
name = StackChan;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = StackChan;
|
||||
productReference = 0EBD7D382ECDA27C0001A9D1 /* StackChan.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
0EBD7D302ECDA27C0001A9D1 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 2610;
|
||||
LastUpgradeCheck = 2620;
|
||||
TargetAttributes = {
|
||||
0EBD7D372ECDA27C0001A9D1 = {
|
||||
CreatedOnToolsVersion = 26.1.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 0EBD7D332ECDA27C0001A9D1 /* Build configuration list for PBXProject "StackChan" */;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 0EBD7D2F2ECDA27C0001A9D1;
|
||||
minimizedProjectReferenceProxies = 1;
|
||||
packageReferences = (
|
||||
);
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = 0EBD7D392ECDA27C0001A9D1 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
0EBD7D372ECDA27C0001A9D1 /* StackChan */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
0EBD7D362ECDA27C0001A9D1 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0E4478B92F0A538600010197 /* README.MD in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
0EBD7D342ECDA27C0001A9D1 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
0EBD7D412ECDA27D0001A9D1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES;
|
||||
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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = NG678HLKHZ;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
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 = 26.1;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
0EBD7D422ECDA27D0001A9D1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES;
|
||||
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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = NG678HLKHZ;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
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 = 26.1;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
0EBD7D442ECDA27D0001A9D1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReferenceAnchor = 0EBD7D3A2ECDA27C0001A9D1 /* StackChan */;
|
||||
baseConfigurationReferenceRelativePath = App.xcconfig;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = StackChan/StackChan.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = NG678HLKHZ;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
INFOPLIST_FILE = StackChan/Info.plist;
|
||||
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Bluetooth permission is required to connect to nearby devices";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Camera permission is required to scan the code";
|
||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is required to discover devices";
|
||||
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Location permission is required to access Wi-Fi information";
|
||||
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Location permission is required to access Wi-Fi information";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
MARKETING_VERSION = 1.0.3;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.m5stack.StackChan;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
0EBD7D452ECDA27D0001A9D1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReferenceAnchor = 0EBD7D3A2ECDA27C0001A9D1 /* StackChan */;
|
||||
baseConfigurationReferenceRelativePath = App.xcconfig;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = StackChan/StackChan.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = NG678HLKHZ;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
INFOPLIST_FILE = StackChan/Info.plist;
|
||||
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Bluetooth permission is required to connect to nearby devices";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Camera permission is required to scan the code";
|
||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is required to discover devices";
|
||||
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Location permission is required to access Wi-Fi information";
|
||||
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Location permission is required to access Wi-Fi information";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
MARKETING_VERSION = 1.0.3;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.m5stack.StackChan;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
0EBD7D332ECDA27C0001A9D1 /* Build configuration list for PBXProject "StackChan" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
0EBD7D412ECDA27D0001A9D1 /* Debug */,
|
||||
0EBD7D422ECDA27D0001A9D1 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
0EBD7D432ECDA27D0001A9D1 /* Build configuration list for PBXNativeTarget "StackChan" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
0EBD7D442ECDA27D0001A9D1 /* Debug */,
|
||||
0EBD7D452ECDA27D0001A9D1 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 0EBD7D302ECDA27C0001A9D1 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0EBD7D372ECDA27C0001A9D1"
|
||||
BuildableName = "StackChan.app"
|
||||
BlueprintName = "StackChan"
|
||||
ReferencedContainer = "container:StackChan.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0EBD7D372ECDA27C0001A9D1"
|
||||
BuildableName = "StackChan.app"
|
||||
BlueprintName = "StackChan"
|
||||
ReferencedContainer = "container:StackChan.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0EBD7D372ECDA27C0001A9D1"
|
||||
BuildableName = "StackChan.app"
|
||||
BlueprintName = "StackChan"
|
||||
ReferencedContainer = "container:StackChan.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Bucket
|
||||
uuid = "632D2E91-955D-4E23-9652-CB7F63F6388B"
|
||||
type = "1"
|
||||
version = "2.0">
|
||||
</Bucket>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?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>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>StackChan.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>0EBD7D372ECDA27C0001A9D1</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
import CoreBluetooth
|
||||
import ARKit
|
||||
import Combine
|
||||
|
||||
enum PageType: Hashable {
|
||||
case minicryEmotion
|
||||
case cameraPage
|
||||
case dance
|
||||
}
|
||||
|
||||
class AppState: ObservableObject {
|
||||
static let shared = AppState()
|
||||
private init() {}
|
||||
|
||||
static let deviceId = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString
|
||||
|
||||
static var isRelease : Bool = true
|
||||
@Published var showAlert: Bool = false
|
||||
@Published var alertTitle: String = ""
|
||||
var alertAction: (() -> Void)? = nil
|
||||
|
||||
func presentAlert(title: String,action: (() -> Void)? = nil) {
|
||||
self.alertTitle = title
|
||||
self.alertAction = action
|
||||
self.showAlert = true
|
||||
}
|
||||
|
||||
@AppStorage("deviceMac") var deviceMac: String = ""
|
||||
|
||||
@Published var stackChanPath: [PageType] = []
|
||||
@Published var nearbyPath: [PageType] = []
|
||||
@Published var settingsPath: [PageType] = []
|
||||
|
||||
@Published var showBindingDevice = false
|
||||
@Published var forcedDisplayBindingDevice = true
|
||||
|
||||
@Published var showCjamgeNameAlert: Bool = false
|
||||
|
||||
@Published var showBindingDeviceAlert: Bool = false
|
||||
|
||||
@Published var newName: String = ""
|
||||
@Published var deviceInfo: Device = Device()
|
||||
|
||||
let detector = DistanceDetector()
|
||||
@Published var showSwitchFace: Bool = false
|
||||
|
||||
@Published var blufDeviceList: [BlufiDeviceInfo] = []
|
||||
|
||||
/// Whether currently pairing a device
|
||||
@Published var showDeviceWifiSet = false
|
||||
|
||||
// Manual shutdown time, if just manually shut down, temporarily do not configure
|
||||
var manualShutdownTime: Date? = nil
|
||||
|
||||
@Published var deviceIsOnline: Bool = false
|
||||
|
||||
func connectBulDevice(macAddress: String) {
|
||||
if BlufiUtil.shared.blueSwitch {
|
||||
BlufiUtil.shared.startScan()
|
||||
} else {
|
||||
BlufiUtil.shared.centralManagerDidUpdateState = { state in
|
||||
switch state {
|
||||
case .poweredOn:
|
||||
BlufiUtil.shared.startScan()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
BlufiUtil.shared.characteristicCallback = { characteristic in
|
||||
if characteristic.properties.contains(.write) || characteristic.properties.contains(.writeWithoutResponse) {
|
||||
|
||||
if characteristic.uuid.uuidString == "E2E5E5E2-1234-5678-1234-56789ABCDEF0" {
|
||||
BlufiUtil.shared.writeExpressionCharacteristic = characteristic
|
||||
print("✏️ Expression writable characteristic assigned: \(characteristic.uuid)")
|
||||
}
|
||||
|
||||
if characteristic.uuid.uuidString == "E2E5E5E1-1234-5678-1234-56789ABCDEF0" {
|
||||
BlufiUtil.shared.writeHeadCharacteristic = characteristic
|
||||
print("✏️ Head writable characteristic assigned: \(characteristic.uuid)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func connectWebSocket() {
|
||||
let webSocketUrl = Urls.getWebSocketUrl() + "?mac=" + deviceMac + "&deviceType=App&deviceId=" + AppState.deviceId
|
||||
WebSocketUtil.shared.connect(urlString: webSocketUrl)
|
||||
}
|
||||
|
||||
func sendWebSocketMessage(_ msgType: MsgType,_ data: Data? = nil) {
|
||||
var buffer = Data([msgType.rawValue])
|
||||
|
||||
let payload = data ?? Data()
|
||||
|
||||
// payload length
|
||||
let dataLen = UInt32(payload.count)
|
||||
buffer.append(UInt8((dataLen >> 24) & 0xFF))
|
||||
buffer.append(UInt8((dataLen >> 16) & 0xFF))
|
||||
buffer.append(UInt8((dataLen >> 8) & 0xFF))
|
||||
buffer.append(UInt8(dataLen & 0xFF))
|
||||
|
||||
// data
|
||||
buffer.append(payload)
|
||||
|
||||
WebSocketUtil.shared.send(data: buffer)
|
||||
}
|
||||
|
||||
/// Parse message data
|
||||
func parseMessage(message: Data) -> (MsgType?,Data?) {
|
||||
guard message.count >= 5 else {
|
||||
return (nil,nil)
|
||||
}
|
||||
|
||||
let typeByte = message[0]
|
||||
guard let msgType = MsgType(rawValue: typeByte) else {
|
||||
return (nil,nil)
|
||||
}
|
||||
|
||||
let lengthData = message[1...4]
|
||||
let dataLength = lengthData.reduce(0) { (result, byte) -> UInt32 in
|
||||
return (result << 8) | UInt32(byte)
|
||||
}
|
||||
|
||||
if message.count < 5 + Int(dataLength) {
|
||||
return (nil,nil)
|
||||
}
|
||||
|
||||
let payload = message[5..<(5 + Int(dataLength))]
|
||||
|
||||
return (msgType, Data(payload))
|
||||
}
|
||||
|
||||
func updateDeviceInfo() {
|
||||
let map = [
|
||||
ValueConstant.mac: deviceMac,
|
||||
ValueConstant.name: deviceInfo.name,
|
||||
]
|
||||
Networking.shared.put(pathUrl: Urls.deviceInfo, parameters: map) { result in
|
||||
switch result {
|
||||
case .success(let success):
|
||||
do {
|
||||
let response = try Response<String>.decode(from: success)
|
||||
if response.isSuccess {
|
||||
print("Update successful")
|
||||
}
|
||||
} catch {
|
||||
print("Failed to parse data")
|
||||
}
|
||||
case .failure(let failure):
|
||||
print("Request failed:", failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Distance detection, callback when close
|
||||
func startDistanceDetection() {
|
||||
detector.startDistanceDetection(
|
||||
distanceUpdate: { distance in
|
||||
let distanceInCm = distance * 100
|
||||
if distanceInCm < 5 {
|
||||
if self.showSwitchFace == false {
|
||||
self.showSwitchFace = true
|
||||
}
|
||||
}
|
||||
},
|
||||
belowThreshold: {
|
||||
// Execute your business logic
|
||||
// For example: stop machine, send notification, etc.
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
func stopDistanceDetection() {
|
||||
detector.stopDistanceDetection()
|
||||
}
|
||||
|
||||
// Wrapper for ARSessionDelegate
|
||||
class ARSessionDelegateWrapper: NSObject, ARSessionDelegate {
|
||||
var onFrameUpdate: (ARFrame) -> Void
|
||||
|
||||
init(onFrameUpdate: @escaping (ARFrame) -> Void) {
|
||||
self.onFrameUpdate = onFrameUpdate
|
||||
}
|
||||
|
||||
func session(_ session: ARSession, didUpdate frame: ARFrame) {
|
||||
onFrameUpdate(frame)
|
||||
}
|
||||
}
|
||||
|
||||
func getDeviceInfo() {
|
||||
let map = [
|
||||
ValueConstant.mac: deviceMac
|
||||
]
|
||||
Networking.shared.get(pathUrl: Urls.deviceInfo,parameters: map) { result in
|
||||
switch result {
|
||||
case .success(let success):
|
||||
do {
|
||||
let response = try Response<Device>.decode(from: success)
|
||||
if response.isSuccess, let deviceInfo = response.data {
|
||||
withAnimation {
|
||||
self.deviceInfo = deviceInfo
|
||||
self.newName = self.deviceInfo.name ?? ""
|
||||
}
|
||||
if deviceInfo.name == "" {
|
||||
self.showCjamgeNameAlert = true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Failed to parse data")
|
||||
}
|
||||
case .failure(let failure):
|
||||
print("Request failed:", failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable Bluetooth functionality
|
||||
func openBlufi() {
|
||||
BlufiUtil.shared.blufDevicesMonitoring = { discovereDevices in
|
||||
self.blufDeviceList = discovereDevices
|
||||
|
||||
// Check manualShutdownTime, if exists and not exceeding 5 seconds, temporarily do not show popup
|
||||
if let shutdownTime = self.manualShutdownTime {
|
||||
let timeInterval = Date().timeIntervalSince(shutdownTime)
|
||||
if timeInterval < 5 {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !self.showDeviceWifiSet {
|
||||
if !self.blufDeviceList.isEmpty {
|
||||
self.showDeviceWifiSet = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// webSocket Message Monitoring
|
||||
func webSocketMessageMonitoring() {
|
||||
WebSocketUtil.shared.addObserver(for: "App") { (message: URLSessionWebSocketTask.Message) in
|
||||
switch message {
|
||||
case .data(let data):
|
||||
let result = self.parseMessage(message: data)
|
||||
if let msgType = result.0 {
|
||||
switch msgType {
|
||||
case MsgType.deviceOnline:
|
||||
self.deviceIsOnline = false
|
||||
case MsgType.deviceOffline:
|
||||
self.deviceIsOnline = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
case .string(let text):
|
||||
print("Received a regular message: \(text)")
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.057",
|
||||
"green" : "0.689",
|
||||
"red" : "0.936"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"localizable" : true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "app_logo.jpg",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 167 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.7 MiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "7.595.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "stackChanLogo.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 167 KiB |
@@ -0,0 +1,29 @@
|
||||
<?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>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_stackchan-mpc._tcp</string>
|
||||
</array>
|
||||
<key>com.apple.developer.networking.wifi-info</key>
|
||||
<true/>
|
||||
|
||||
<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>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>StackChan World</string>
|
||||
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Save the photo to the album</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
struct BlufiModel<T:Codable>: Codable {
|
||||
|
||||
var cmd: String? = nil
|
||||
var data: T? = nil
|
||||
|
||||
func toJson() -> String? {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
guard let jsonData = try? encoder.encode(self) else { return nil }
|
||||
return String(data: jsonData, encoding: .utf8)
|
||||
}
|
||||
|
||||
static func fromJson(_ json: String) -> BlufiModel<T>? {
|
||||
guard let jsonData = json.data(using: .utf8) else { return nil }
|
||||
let decoder = JSONDecoder()
|
||||
return try? decoder.decode(BlufiModel<T>.self, from: jsonData)
|
||||
}
|
||||
}
|
||||
|
||||
struct BlufiWifi : Codable {
|
||||
var ssid: String?
|
||||
var password: String?
|
||||
|
||||
func toJson() -> String? {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
guard let jsonData = try? encoder.encode(self) else { return nil }
|
||||
return String(data: jsonData, encoding: .utf8)
|
||||
}
|
||||
|
||||
static func fromJson(_ json: String) -> BlufiWifi? {
|
||||
guard let jsonData = json.data(using: .utf8) else { return nil }
|
||||
let decoder = JSONDecoder()
|
||||
return try? decoder.decode(BlufiWifi.self, from: jsonData)
|
||||
}
|
||||
}
|
||||
|
||||
struct BlufiNotifyState : Codable {
|
||||
var type: Int?
|
||||
var state: String?
|
||||
|
||||
func toJson() -> String? {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
guard let jsonData = try? encoder.encode(self) else { return nil }
|
||||
return String(data: jsonData, encoding: .utf8)
|
||||
}
|
||||
|
||||
static func fromJson(_ json: String) -> BlufiNotifyState? {
|
||||
guard let jsonData = json.data(using: .utf8) else { return nil }
|
||||
let decoder = JSONDecoder()
|
||||
return try? decoder.decode(BlufiNotifyState.self, from: jsonData)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user