Initial commit — RoadCode import
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
BlackRoadWatch/Assets.xcassets/Contents.json
Normal file
6
BlackRoadWatch/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
449
BlackRoadWatch/BlackRoadWatch.xcodeproj/project.pbxproj
Normal file
449
BlackRoadWatch/BlackRoadWatch.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,449 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
06A49EBA736D5D349FEB60C1 /* BlackRoadWatch Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = F57DDDDC2CFC8609BA08627F /* BlackRoadWatch Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
08B97A6B21DEADBB21C5A848 /* BlackRoadUDP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45798D9899ECB366CC3F4513 /* BlackRoadUDP.swift */; };
|
||||
59DCF8D7AD06A60A170639AB /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = F62B21585AA36CFC68A2B4CF /* Models.swift */; };
|
||||
5F4762D869B85501D3A40F1B /* BlackRoadWatchiOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0168D441082EBB3FCDFE8F1F /* BlackRoadWatchiOSApp.swift */; };
|
||||
9CB463BE1D4E16497C34CFBC /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = F62B21585AA36CFC68A2B4CF /* Models.swift */; };
|
||||
E4D63839B49219F0F337ED24 /* iOSContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A083C48A674E71041C778BD7 /* iOSContentView.swift */; };
|
||||
F140298AD85CEDFEF44B9343 /* WatchContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D312190199877815DCC7EB9A /* WatchContentView.swift */; };
|
||||
FAB953EB9D4BBE897E277D65 /* BlackRoadWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD20721649D1343E408CE1AE /* BlackRoadWatchApp.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
FFF1AA1FF1DED0FEBEA855F0 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 661181E6F125ECBFC570E509 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 36F2274064001FDC43A2AAC3;
|
||||
remoteInfo = "BlackRoadWatch Watch App";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
55B1012CC02294FB044FC20C /* Embed Watch Content */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
06A49EBA736D5D349FEB60C1 /* BlackRoadWatch Watch App.app in Embed Watch Content */,
|
||||
);
|
||||
name = "Embed Watch Content";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0168D441082EBB3FCDFE8F1F /* BlackRoadWatchiOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlackRoadWatchiOSApp.swift; sourceTree = "<group>"; };
|
||||
1F5ACD36B72339BE05304981 /* BlackRoadWatch.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = BlackRoadWatch.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
45798D9899ECB366CC3F4513 /* BlackRoadUDP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlackRoadUDP.swift; sourceTree = "<group>"; };
|
||||
58CF0DEF177004EBD1D3DA4E /* BlackRoadWatch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BlackRoadWatch.entitlements; sourceTree = "<group>"; };
|
||||
86439B315A3C86CDAE4A3277 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
A083C48A674E71041C778BD7 /* iOSContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSContentView.swift; sourceTree = "<group>"; };
|
||||
AD20721649D1343E408CE1AE /* BlackRoadWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlackRoadWatchApp.swift; sourceTree = "<group>"; };
|
||||
C61BF0584E5FF866A9B8D008 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
D312190199877815DCC7EB9A /* WatchContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchContentView.swift; sourceTree = "<group>"; };
|
||||
F57DDDDC2CFC8609BA08627F /* BlackRoadWatch Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "BlackRoadWatch Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F62B21585AA36CFC68A2B4CF /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
4B8E2F18D2DF55FD90EBDE1E /* WatchApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AD20721649D1343E408CE1AE /* BlackRoadWatchApp.swift */,
|
||||
86439B315A3C86CDAE4A3277 /* Info.plist */,
|
||||
D312190199877815DCC7EB9A /* WatchContentView.swift */,
|
||||
);
|
||||
path = WatchApp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4F0B72A9736B1A08D4CBB97B /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F57DDDDC2CFC8609BA08627F /* BlackRoadWatch Watch App.app */,
|
||||
1F5ACD36B72339BE05304981 /* BlackRoadWatch.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9EABC8C1259D3F4F89B68D1C /* Shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F62B21585AA36CFC68A2B4CF /* Models.swift */,
|
||||
);
|
||||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C1D4CE122B56B2F6F92FE0AE = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CF1939ABCA0745EFEB78164E /* Sources */,
|
||||
4F0B72A9736B1A08D4CBB97B /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CD03CA4B917D9D7A338A5EE0 /* iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
45798D9899ECB366CC3F4513 /* BlackRoadUDP.swift */,
|
||||
58CF0DEF177004EBD1D3DA4E /* BlackRoadWatch.entitlements */,
|
||||
0168D441082EBB3FCDFE8F1F /* BlackRoadWatchiOSApp.swift */,
|
||||
C61BF0584E5FF866A9B8D008 /* Info.plist */,
|
||||
A083C48A674E71041C778BD7 /* iOSContentView.swift */,
|
||||
);
|
||||
path = iOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CF1939ABCA0745EFEB78164E /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CD03CA4B917D9D7A338A5EE0 /* iOS */,
|
||||
9EABC8C1259D3F4F89B68D1C /* Shared */,
|
||||
4B8E2F18D2DF55FD90EBDE1E /* WatchApp */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
1E2F518FE655AA070BD7F31E /* BlackRoadWatch */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 2C3E6B575CAD06601FA9A66E /* Build configuration list for PBXNativeTarget "BlackRoadWatch" */;
|
||||
buildPhases = (
|
||||
51982AE077DFF57523B5398C /* Sources */,
|
||||
55B1012CC02294FB044FC20C /* Embed Watch Content */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
6469046C4543B260843F6983 /* PBXTargetDependency */,
|
||||
);
|
||||
name = BlackRoadWatch;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = BlackRoadWatch;
|
||||
productReference = 1F5ACD36B72339BE05304981 /* BlackRoadWatch.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
36F2274064001FDC43A2AAC3 /* BlackRoadWatch Watch App */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 27A827CA2AFE224F95EEB096 /* Build configuration list for PBXNativeTarget "BlackRoadWatch Watch App" */;
|
||||
buildPhases = (
|
||||
978BFC5B6146346108FA2AF2 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "BlackRoadWatch Watch App";
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = "BlackRoadWatch Watch App";
|
||||
productReference = F57DDDDC2CFC8609BA08627F /* BlackRoadWatch Watch App.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
661181E6F125ECBFC570E509 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 1500;
|
||||
TargetAttributes = {
|
||||
1E2F518FE655AA070BD7F31E = {
|
||||
DevelopmentTeam = "";
|
||||
};
|
||||
36F2274064001FDC43A2AAC3 = {
|
||||
DevelopmentTeam = "";
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 2A3E59B8C954BB8E0A0C6098 /* Build configuration list for PBXProject "BlackRoadWatch" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
Base,
|
||||
en,
|
||||
);
|
||||
mainGroup = C1D4CE122B56B2F6F92FE0AE;
|
||||
minimizedProjectReferenceProxies = 1;
|
||||
preferredProjectObjectVersion = 77;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
1E2F518FE655AA070BD7F31E /* BlackRoadWatch */,
|
||||
36F2274064001FDC43A2AAC3 /* BlackRoadWatch Watch App */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
51982AE077DFF57523B5398C /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
08B97A6B21DEADBB21C5A848 /* BlackRoadUDP.swift in Sources */,
|
||||
5F4762D869B85501D3A40F1B /* BlackRoadWatchiOSApp.swift in Sources */,
|
||||
59DCF8D7AD06A60A170639AB /* Models.swift in Sources */,
|
||||
E4D63839B49219F0F337ED24 /* iOSContentView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
978BFC5B6146346108FA2AF2 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FAB953EB9D4BBE897E277D65 /* BlackRoadWatchApp.swift in Sources */,
|
||||
9CB463BE1D4E16497C34CFBC /* Models.swift in Sources */,
|
||||
F140298AD85CEDFEF44B9343 /* WatchContentView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
6469046C4543B260843F6983 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 36F2274064001FDC43A2AAC3 /* BlackRoadWatch Watch App */;
|
||||
targetProxy = FFF1AA1FF1DED0FEBEA855F0 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
08C35BB631165F12D82D3899 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
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 = "";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
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 = 16.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.9;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 9.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
30668BB9305FEAB8F2B0397E /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = Sources/iOS/BlackRoadWatch.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
INFOPLIST_FILE = Sources/iOS/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.blackroad.watch;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
3A76BF9546F705DBE9CA98E2 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = Sources/iOS/BlackRoadWatch.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
INFOPLIST_FILE = Sources/iOS/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.blackroad.watch;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
98CBE1F767B8F1E684DA8703 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
INFOPLIST_FILE = Sources/WatchApp/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.blackroad.watch.watchkitapp;
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 9.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
BFBC78AE562A0F69BFFBEC66 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
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 = "";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"DEBUG=1",
|
||||
);
|
||||
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 = 16.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.9;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 9.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DCA7C67617B3C60725832B59 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
INFOPLIST_FILE = Sources/WatchApp/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.blackroad.watch.watchkitapp;
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 9.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
27A827CA2AFE224F95EEB096 /* Build configuration list for PBXNativeTarget "BlackRoadWatch Watch App" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
98CBE1F767B8F1E684DA8703 /* Debug */,
|
||||
DCA7C67617B3C60725832B59 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
2A3E59B8C954BB8E0A0C6098 /* Build configuration list for PBXProject "BlackRoadWatch" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
BFBC78AE562A0F69BFFBEC66 /* Debug */,
|
||||
08C35BB631165F12D82D3899 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
2C3E6B575CAD06601FA9A66E /* Build configuration list for PBXNativeTarget "BlackRoadWatch" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3A76BF9546F705DBE9CA98E2 /* Debug */,
|
||||
30668BB9305FEAB8F2B0397E /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 661181E6F125ECBFC570E509 /* Project object */;
|
||||
}
|
||||
7
BlackRoadWatch/BlackRoadWatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
BlackRoadWatch/BlackRoadWatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
39
BlackRoadWatch/Sources/Shared/Models.swift
Normal file
39
BlackRoadWatch/Sources/Shared/Models.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
import Foundation
|
||||
|
||||
// MARK: - Shared Data Models (used by both iOS and watchOS)
|
||||
|
||||
struct SensorData: Codable {
|
||||
var temperature: Double
|
||||
var humidity: Double
|
||||
var light: Int
|
||||
var accelX: Double
|
||||
var accelY: Double
|
||||
var accelZ: Double
|
||||
var batteryMV: Int
|
||||
var uptimeSec: UInt32
|
||||
}
|
||||
|
||||
struct AIStatus: Codable {
|
||||
var modelID: Int
|
||||
var confidence: Int
|
||||
var inferenceMS: Int
|
||||
var totalInferences: UInt32
|
||||
var npuLoad: Int
|
||||
var npuTemp: Int
|
||||
var classID: Int
|
||||
}
|
||||
|
||||
struct SystemHealth: Codable {
|
||||
var fleetOnline: Int
|
||||
var fleetTotal: Int
|
||||
var agentsActive: Int
|
||||
var trafficGreen: Int
|
||||
var trafficYellow: Int
|
||||
var trafficRed: Int
|
||||
var tasksPending: Int
|
||||
var tasksDone: Int
|
||||
var memoryEntries: UInt32
|
||||
var reposCount: Int
|
||||
var cfProjects: Int
|
||||
var cpuLoad: Double
|
||||
}
|
||||
77
BlackRoadWatch/Sources/WatchApp/BlackRoadWatchApp.swift
Normal file
77
BlackRoadWatch/Sources/WatchApp/BlackRoadWatchApp.swift
Normal file
@@ -0,0 +1,77 @@
|
||||
import SwiftUI
|
||||
import WatchConnectivity
|
||||
|
||||
// MARK: - Watch App Entry Point
|
||||
|
||||
@main
|
||||
struct BlackRoadWatchApp: App {
|
||||
@StateObject private var dataStore = WatchDataStore.shared
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
WatchContentView()
|
||||
.environmentObject(dataStore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Watch Data Store (receives from iPhone)
|
||||
|
||||
class WatchDataStore: NSObject, ObservableObject {
|
||||
static let shared = WatchDataStore()
|
||||
|
||||
@Published var sensor: SensorData?
|
||||
@Published var ai: AIStatus?
|
||||
@Published var health: SystemHealth?
|
||||
@Published var lastUpdate: Date?
|
||||
@Published var isConnected = false
|
||||
|
||||
private var wcSession: WCSession?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
guard WCSession.isSupported() else { return }
|
||||
wcSession = WCSession.default
|
||||
wcSession?.delegate = self
|
||||
wcSession?.activate()
|
||||
}
|
||||
|
||||
private func processContext(_ context: [String: Any]) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let sensorDict = context["sensor"],
|
||||
let sensorData = try? JSONSerialization.data(withJSONObject: sensorDict),
|
||||
let sensor = try? JSONDecoder().decode(SensorData.self, from: sensorData) {
|
||||
self.sensor = sensor
|
||||
}
|
||||
|
||||
if let aiDict = context["ai"],
|
||||
let aiData = try? JSONSerialization.data(withJSONObject: aiDict),
|
||||
let ai = try? JSONDecoder().decode(AIStatus.self, from: aiData) {
|
||||
self.ai = ai
|
||||
}
|
||||
|
||||
if let healthDict = context["health"],
|
||||
let healthData = try? JSONSerialization.data(withJSONObject: healthDict),
|
||||
let health = try? JSONDecoder().decode(SystemHealth.self, from: healthData) {
|
||||
self.health = health
|
||||
}
|
||||
|
||||
self.lastUpdate = Date()
|
||||
self.isConnected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WatchDataStore: WCSessionDelegate {
|
||||
func session(_ session: WCSession, activationDidCompleteWith state: WCSessionActivationState, error: Error?) {}
|
||||
|
||||
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
|
||||
processContext(message)
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, didReceiveApplicationContext context: [String: Any]) {
|
||||
processContext(context)
|
||||
}
|
||||
}
|
||||
28
BlackRoadWatch/Sources/WatchApp/Info.plist
Normal file
28
BlackRoadWatch/Sources/WatchApp/Info.plist
Normal file
@@ -0,0 +1,28 @@
|
||||
<?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>CFBundleDisplayName</key>
|
||||
<string>BlackRoad</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>WKApplication</key>
|
||||
<true/>
|
||||
<key>WKCompanionAppBundleIdentifier</key>
|
||||
<string>io.blackroad.watch</string>
|
||||
</dict>
|
||||
</plist>
|
||||
335
BlackRoadWatch/Sources/WatchApp/WatchContentView.swift
Normal file
335
BlackRoadWatch/Sources/WatchApp/WatchContentView.swift
Normal file
@@ -0,0 +1,335 @@
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Brand Colors (Watch)
|
||||
|
||||
extension Color {
|
||||
static let brHotPink = Color(red: 1.0, green: 0.114, blue: 0.424)
|
||||
static let brAmber = Color(red: 0.961, green: 0.651, blue: 0.137)
|
||||
static let brElectricBlue = Color(red: 0.161, green: 0.475, blue: 1.0)
|
||||
static let brViolet = Color(red: 0.612, green: 0.153, blue: 0.690)
|
||||
}
|
||||
|
||||
// MARK: - Main Watch View
|
||||
|
||||
struct WatchContentView: View {
|
||||
@EnvironmentObject var store: WatchDataStore
|
||||
@State private var currentPage = 0
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $currentPage) {
|
||||
WatchFaceView()
|
||||
.tag(0)
|
||||
FleetDashboardView()
|
||||
.tag(1)
|
||||
SensorView()
|
||||
.tag(2)
|
||||
AIView()
|
||||
.tag(3)
|
||||
}
|
||||
.tabViewStyle(.page)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Page 1: Watch Face
|
||||
|
||||
struct WatchFaceView: View {
|
||||
@EnvironmentObject var store: WatchDataStore
|
||||
@State private var currentTime = Date()
|
||||
|
||||
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
LinearGradient(
|
||||
colors: [.black, Color(white: 0.05)],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
|
||||
VStack(spacing: 4) {
|
||||
Text(timeString)
|
||||
.font(.system(size: 48, weight: .thin, design: .rounded))
|
||||
.foregroundStyle(
|
||||
LinearGradient(
|
||||
colors: [.brAmber, .brHotPink],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
)
|
||||
|
||||
Text(dateString)
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
.foregroundColor(.gray)
|
||||
|
||||
Spacer().frame(height: 8)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
VStack(spacing: 2) {
|
||||
Image(systemName: "server.rack")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.brElectricBlue)
|
||||
Text("\(store.health?.fleetOnline ?? 0)/\(store.health?.fleetTotal ?? 0)")
|
||||
.font(.system(size: 11, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
VStack(spacing: 2) {
|
||||
Circle()
|
||||
.fill(.green)
|
||||
.frame(width: 10, height: 10)
|
||||
Text("\(store.health?.trafficGreen ?? 0)")
|
||||
.font(.system(size: 11, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
VStack(spacing: 2) {
|
||||
Image(systemName: "brain")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.brViolet)
|
||||
Text("\(store.health?.agentsActive ?? 0)")
|
||||
.font(.system(size: 11, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
VStack(spacing: 2) {
|
||||
Image(systemName: "thermometer.medium")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.brAmber)
|
||||
Text(String(format: "%.0f\u{00B0}", store.sensor?.temperature ?? 0))
|
||||
.font(.system(size: 11, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Circle()
|
||||
.fill(store.isConnected ? .brHotPink : .gray)
|
||||
.frame(width: 6, height: 6)
|
||||
Text(store.isConnected ? "BLACKROAD" : "OFFLINE")
|
||||
.font(.system(size: 9, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(store.isConnected ? .brHotPink : .gray)
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.onReceive(timer) { input in
|
||||
currentTime = input
|
||||
}
|
||||
}
|
||||
|
||||
private var timeString: String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "h:mm"
|
||||
return formatter.string(from: currentTime)
|
||||
}
|
||||
|
||||
private var dateString: String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "EEEE, MMM d"
|
||||
return formatter.string(from: currentTime).uppercased()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Page 2: Fleet Dashboard
|
||||
|
||||
struct FleetDashboardView: View {
|
||||
@EnvironmentObject var store: WatchDataStore
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("FLEET")
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.brHotPink)
|
||||
|
||||
if let health = store.health {
|
||||
StatRow(icon: "server.rack", label: "Devices",
|
||||
value: "\(health.fleetOnline)/\(health.fleetTotal)",
|
||||
color: .brElectricBlue)
|
||||
StatRow(icon: "brain", label: "Agents",
|
||||
value: "\(health.agentsActive)",
|
||||
color: .brViolet)
|
||||
StatRow(icon: "circle.fill", label: "Green",
|
||||
value: "\(health.trafficGreen)",
|
||||
color: .green)
|
||||
StatRow(icon: "doc.text", label: "Repos",
|
||||
value: "\(health.reposCount)",
|
||||
color: .brAmber)
|
||||
StatRow(icon: "cloud", label: "CF Projects",
|
||||
value: "\(health.cfProjects)",
|
||||
color: .brElectricBlue)
|
||||
StatRow(icon: "checkmark.circle", label: "Tasks Done",
|
||||
value: "\(health.tasksDone)",
|
||||
color: .green)
|
||||
StatRow(icon: "memorychip", label: "Memory",
|
||||
value: "\(health.memoryEntries)",
|
||||
color: .brViolet)
|
||||
} else {
|
||||
Text("Waiting for data...")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Page 3: Sensors
|
||||
|
||||
struct SensorView: View {
|
||||
@EnvironmentObject var store: WatchDataStore
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("SENSORS")
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.brAmber)
|
||||
|
||||
if let sensor = store.sensor {
|
||||
StatRow(icon: "thermometer", label: "Temp",
|
||||
value: String(format: "%.1f\u{00B0}C", sensor.temperature),
|
||||
color: .brAmber)
|
||||
StatRow(icon: "humidity", label: "Humidity",
|
||||
value: String(format: "%.1f%%", sensor.humidity),
|
||||
color: .brElectricBlue)
|
||||
StatRow(icon: "battery.100", label: "Battery",
|
||||
value: "\(sensor.batteryMV)mV",
|
||||
color: sensor.batteryMV > 3500 ? .green : .red)
|
||||
StatRow(icon: "clock", label: "Uptime",
|
||||
value: formatUptime(sensor.uptimeSec),
|
||||
color: .gray)
|
||||
} else {
|
||||
Text("No sensor data")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
private func formatUptime(_ seconds: UInt32) -> String {
|
||||
let h = seconds / 3600
|
||||
let m = (seconds % 3600) / 60
|
||||
let s = seconds % 60
|
||||
return String(format: "%02d:%02d:%02d", h, m, s)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Page 4: AI Status
|
||||
|
||||
struct AIView: View {
|
||||
@EnvironmentObject var store: WatchDataStore
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("NPU")
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.brViolet)
|
||||
|
||||
if let ai = store.ai {
|
||||
// Confidence bar
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack {
|
||||
Text("Confidence")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
Spacer()
|
||||
Text("\(ai.confidence)%")
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.brHotPink)
|
||||
}
|
||||
GeometryReader { geo in
|
||||
ZStack(alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(Color.white.opacity(0.1))
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [.brAmber, .brHotPink],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
)
|
||||
.frame(width: geo.size.width * CGFloat(ai.confidence) / 100.0)
|
||||
}
|
||||
}
|
||||
.frame(height: 6)
|
||||
}
|
||||
|
||||
StatRow(icon: "number", label: "Total",
|
||||
value: "\(ai.totalInferences)",
|
||||
color: .brAmber)
|
||||
|
||||
// NPU load bar
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack {
|
||||
Text("NPU Load")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
Spacer()
|
||||
Text("\(ai.npuLoad)%")
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.brElectricBlue)
|
||||
}
|
||||
GeometryReader { geo in
|
||||
ZStack(alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(Color.white.opacity(0.1))
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [.brElectricBlue, .brViolet],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
)
|
||||
.frame(width: geo.size.width * CGFloat(ai.npuLoad) / 100.0)
|
||||
}
|
||||
}
|
||||
.frame(height: 6)
|
||||
}
|
||||
|
||||
StatRow(icon: "thermometer", label: "NPU Temp",
|
||||
value: "\(ai.npuTemp)\u{00B0}C",
|
||||
color: ai.npuTemp > 70 ? .red : .brAmber)
|
||||
} else {
|
||||
Text("No NPU data")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Reusable Components
|
||||
|
||||
struct StatRow: View {
|
||||
let icon: String
|
||||
let label: String
|
||||
let value: String
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(color)
|
||||
.frame(width: 20)
|
||||
Text(label)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.gray)
|
||||
Spacer()
|
||||
Text(value)
|
||||
.font(.system(size: 12, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}
|
||||
}
|
||||
222
BlackRoadWatch/Sources/iOS/BlackRoadUDP.swift
Normal file
222
BlackRoadWatch/Sources/iOS/BlackRoadUDP.swift
Normal file
@@ -0,0 +1,222 @@
|
||||
import Foundation
|
||||
import Network
|
||||
import WatchConnectivity
|
||||
import Combine
|
||||
|
||||
// MARK: - UDP Listener for M1s Firmware Broadcasts
|
||||
|
||||
class BlackRoadUDPManager: NSObject, ObservableObject {
|
||||
static let shared = BlackRoadUDPManager()
|
||||
|
||||
@Published var isListening = false
|
||||
@Published var isConnected = false
|
||||
@Published var watchReachable = false
|
||||
@Published var sensorData: SensorData?
|
||||
@Published var aiStatus: AIStatus?
|
||||
@Published var systemHealth: SystemHealth?
|
||||
@Published var lastUpdate: Date?
|
||||
@Published var packetsReceived: UInt64 = 0
|
||||
@Published var sourceAddress: String = ""
|
||||
|
||||
private var listener: NWListener?
|
||||
private var wcSession: WCSession?
|
||||
private let udpPort: UInt16 = 8420
|
||||
private let queue = DispatchQueue(label: "io.blackroad.udp", qos: .userInteractive)
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
setupWatchConnectivity()
|
||||
}
|
||||
|
||||
// MARK: - UDP Listener
|
||||
|
||||
func startListening() {
|
||||
guard listener == nil else { return }
|
||||
|
||||
do {
|
||||
let params = NWParameters.udp
|
||||
params.allowLocalEndpointReuse = true
|
||||
params.requiredInterfaceType = .wifi
|
||||
|
||||
listener = try NWListener(using: params, on: NWEndpoint.Port(rawValue: udpPort)!)
|
||||
|
||||
listener?.stateUpdateHandler = { [weak self] state in
|
||||
DispatchQueue.main.async {
|
||||
switch state {
|
||||
case .ready:
|
||||
self?.isListening = true
|
||||
print("[BR] UDP listener ready on port \(self?.udpPort ?? 0)")
|
||||
case .failed(let error):
|
||||
self?.isListening = false
|
||||
print("[BR] UDP listener failed: \(error)")
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||
self?.stopListening()
|
||||
self?.startListening()
|
||||
}
|
||||
case .cancelled:
|
||||
self?.isListening = false
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listener?.newConnectionHandler = { [weak self] connection in
|
||||
self?.handleConnection(connection)
|
||||
}
|
||||
|
||||
listener?.start(queue: queue)
|
||||
} catch {
|
||||
print("[BR] Failed to create UDP listener: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func stopListening() {
|
||||
listener?.cancel()
|
||||
listener = nil
|
||||
DispatchQueue.main.async {
|
||||
self.isListening = false
|
||||
self.isConnected = false
|
||||
}
|
||||
}
|
||||
|
||||
private func handleConnection(_ connection: NWConnection) {
|
||||
connection.start(queue: queue)
|
||||
receiveData(on: connection)
|
||||
}
|
||||
|
||||
private func receiveData(on connection: NWConnection) {
|
||||
connection.receiveMessage { [weak self] data, context, isComplete, error in
|
||||
if let data = data, !data.isEmpty {
|
||||
self?.processPacket(data, from: connection)
|
||||
}
|
||||
if error == nil {
|
||||
self?.receiveData(on: connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - JSON Parsing
|
||||
|
||||
private func processPacket(_ data: Data, from connection: NWConnection) {
|
||||
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let type = json["type"] as? String, type == "br_watch" else {
|
||||
return
|
||||
}
|
||||
|
||||
if let endpoint = connection.currentPath?.remoteEndpoint,
|
||||
case let .hostPort(host, _) = endpoint {
|
||||
let addr = "\(host)"
|
||||
DispatchQueue.main.async { self.sourceAddress = addr }
|
||||
}
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.packetsReceived += 1
|
||||
self.isConnected = true
|
||||
self.lastUpdate = Date()
|
||||
|
||||
if let sensorDict = json["sensor"] as? [String: Any] {
|
||||
self.sensorData = SensorData(
|
||||
temperature: sensorDict["temp"] as? Double ?? 0,
|
||||
humidity: sensorDict["hum"] as? Double ?? 0,
|
||||
light: 0,
|
||||
accelX: 0, accelY: 0, accelZ: 0,
|
||||
batteryMV: sensorDict["bat"] as? Int ?? 0,
|
||||
uptimeSec: UInt32(sensorDict["up"] as? Int ?? 0)
|
||||
)
|
||||
}
|
||||
|
||||
if let aiDict = json["ai"] as? [String: Any] {
|
||||
self.aiStatus = AIStatus(
|
||||
modelID: 1,
|
||||
confidence: aiDict["conf"] as? Int ?? 0,
|
||||
inferenceMS: 0,
|
||||
totalInferences: UInt32(aiDict["infers"] as? Int ?? 0),
|
||||
npuLoad: aiDict["load"] as? Int ?? 0,
|
||||
npuTemp: aiDict["temp"] as? Int ?? 0,
|
||||
classID: 0
|
||||
)
|
||||
}
|
||||
|
||||
if let fleetDict = json["fleet"] as? [String: Any] {
|
||||
self.systemHealth = SystemHealth(
|
||||
fleetOnline: fleetDict["on"] as? Int ?? 0,
|
||||
fleetTotal: fleetDict["total"] as? Int ?? 0,
|
||||
agentsActive: fleetDict["agents"] as? Int ?? 0,
|
||||
trafficGreen: fleetDict["green"] as? Int ?? 0,
|
||||
trafficYellow: 0,
|
||||
trafficRed: 0,
|
||||
tasksPending: 0,
|
||||
tasksDone: fleetDict["tasks"] as? Int ?? 0,
|
||||
memoryEntries: UInt32(fleetDict["mem"] as? Int ?? 0),
|
||||
reposCount: fleetDict["repos"] as? Int ?? 0,
|
||||
cfProjects: fleetDict["cf"] as? Int ?? 0,
|
||||
cpuLoad: 0
|
||||
)
|
||||
}
|
||||
|
||||
self.sendToWatch()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Watch Connectivity
|
||||
|
||||
private func setupWatchConnectivity() {
|
||||
guard WCSession.isSupported() else { return }
|
||||
wcSession = WCSession.default
|
||||
wcSession?.delegate = self
|
||||
wcSession?.activate()
|
||||
}
|
||||
|
||||
private func sendToWatch() {
|
||||
guard let session = wcSession, session.activationState == .activated else { return }
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
var context: [String: Any] = [:]
|
||||
|
||||
if let sensor = sensorData,
|
||||
let sensorJSON = try? encoder.encode(sensor),
|
||||
let sensorDict = try? JSONSerialization.jsonObject(with: sensorJSON) {
|
||||
context["sensor"] = sensorDict
|
||||
}
|
||||
if let ai = aiStatus,
|
||||
let aiJSON = try? encoder.encode(ai),
|
||||
let aiDict = try? JSONSerialization.jsonObject(with: aiJSON) {
|
||||
context["ai"] = aiDict
|
||||
}
|
||||
if let health = systemHealth,
|
||||
let healthJSON = try? encoder.encode(health),
|
||||
let healthDict = try? JSONSerialization.jsonObject(with: healthJSON) {
|
||||
context["health"] = healthDict
|
||||
}
|
||||
context["timestamp"] = Date().timeIntervalSince1970
|
||||
|
||||
try? session.updateApplicationContext(context)
|
||||
|
||||
if session.isReachable {
|
||||
session.sendMessage(context, replyHandler: nil)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.watchReachable = session.isReachable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WCSessionDelegate
|
||||
|
||||
extension BlackRoadUDPManager: WCSessionDelegate {
|
||||
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
||||
print("[BR] Watch session activated: \(activationState.rawValue)")
|
||||
DispatchQueue.main.async { self.watchReachable = session.isReachable }
|
||||
}
|
||||
|
||||
func sessionDidBecomeInactive(_ session: WCSession) {}
|
||||
func sessionDidDeactivate(_ session: WCSession) { session.activate() }
|
||||
|
||||
func sessionReachabilityDidChange(_ session: WCSession) {
|
||||
DispatchQueue.main.async { self.watchReachable = session.isReachable }
|
||||
}
|
||||
}
|
||||
5
BlackRoadWatch/Sources/iOS/BlackRoadWatch.entitlements
Normal file
5
BlackRoadWatch/Sources/iOS/BlackRoadWatch.entitlements
Normal file
@@ -0,0 +1,5 @@
|
||||
<?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/>
|
||||
</plist>
|
||||
16
BlackRoadWatch/Sources/iOS/BlackRoadWatchiOSApp.swift
Normal file
16
BlackRoadWatch/Sources/iOS/BlackRoadWatchiOSApp.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct BlackRoadWatchiOSApp: App {
|
||||
@StateObject private var udpManager = BlackRoadUDPManager.shared
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
iOSContentView()
|
||||
.environmentObject(udpManager)
|
||||
.onAppear {
|
||||
udpManager.startListening()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
BlackRoadWatch/Sources/iOS/Info.plist
Normal file
46
BlackRoadWatch/Sources/iOS/Info.plist
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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>CFBundleDisplayName</key>
|
||||
<string>BlackRoad Watch</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict>
|
||||
<key>UIColorName</key>
|
||||
<string>Black</string>
|
||||
</dict>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
<string>wifi</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>BlackRoad Watch needs local network access to receive data from the M1s Dock via UDP.</string>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_blackroad._udp</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
208
BlackRoadWatch/Sources/iOS/iOSContentView.swift
Normal file
208
BlackRoadWatch/Sources/iOS/iOSContentView.swift
Normal file
@@ -0,0 +1,208 @@
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - BlackRoad Brand Colors
|
||||
|
||||
extension Color {
|
||||
static let brHotPink = Color(red: 1.0, green: 0.114, blue: 0.424)
|
||||
static let brAmber = Color(red: 0.961, green: 0.651, blue: 0.137)
|
||||
static let brElectricBlue = Color(red: 0.161, green: 0.475, blue: 1.0)
|
||||
static let brViolet = Color(red: 0.612, green: 0.153, blue: 0.690)
|
||||
}
|
||||
|
||||
// MARK: - iOS Bridge View
|
||||
|
||||
struct iOSContentView: View {
|
||||
@EnvironmentObject var udp: BlackRoadUDPManager
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
VStack(spacing: 20) {
|
||||
connectionCard
|
||||
|
||||
if udp.isConnected {
|
||||
sensorCard
|
||||
aiCard
|
||||
fleetCard
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.background(Color.black)
|
||||
.navigationTitle("BlackRoad Watch")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbarColorScheme(.dark, for: .navigationBar)
|
||||
.toolbarBackground(.visible, for: .navigationBar)
|
||||
.toolbarBackground(Color.black, for: .navigationBar)
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
|
||||
// MARK: - Connection Card
|
||||
|
||||
private var connectionCard: some View {
|
||||
VStack(spacing: 12) {
|
||||
HStack {
|
||||
Text("BLACKROAD")
|
||||
.font(.system(size: 13, weight: .black, design: .monospaced))
|
||||
.foregroundStyle(
|
||||
LinearGradient(
|
||||
colors: [.brAmber, .brHotPink],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
)
|
||||
Spacer()
|
||||
Text("BRIDGE")
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
Divider().background(Color.gray.opacity(0.3))
|
||||
|
||||
HStack {
|
||||
Circle()
|
||||
.fill(udp.isConnected ? Color.green : Color.red)
|
||||
.frame(width: 10, height: 10)
|
||||
Text("M1s Dock")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundColor(.white)
|
||||
Spacer()
|
||||
if udp.isConnected {
|
||||
Text(udp.sourceAddress)
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.foregroundColor(.gray)
|
||||
} else {
|
||||
Text(udp.isListening ? "Listening on :8420" : "Starting...")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
Circle()
|
||||
.fill(udp.watchReachable ? Color.green : Color.orange)
|
||||
.frame(width: 10, height: 10)
|
||||
Text("Apple Watch")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundColor(.white)
|
||||
Spacer()
|
||||
Text(udp.watchReachable ? "Connected" : "Waiting")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Label("\(udp.packetsReceived)", systemImage: "arrow.down.circle")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.foregroundColor(.brElectricBlue)
|
||||
Spacer()
|
||||
if let last = udp.lastUpdate {
|
||||
Text(last, style: .relative)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color.white.opacity(0.05))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.strokeBorder(
|
||||
LinearGradient(
|
||||
colors: [.brAmber.opacity(0.5), .brHotPink.opacity(0.3)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: 1
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Sensor Card
|
||||
|
||||
private var sensorCard: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("SENSORS", systemImage: "thermometer")
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.brAmber)
|
||||
|
||||
if let s = udp.sensorData {
|
||||
dataRow("Temperature", String(format: "%.1f\u{00B0}C", s.temperature), .brAmber)
|
||||
dataRow("Humidity", String(format: "%.1f%%", s.humidity), .brElectricBlue)
|
||||
dataRow("Battery", "\(s.batteryMV)mV", s.batteryMV > 3500 ? .green : .red)
|
||||
dataRow("Uptime", formatUptime(s.uptimeSec), .gray)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
.background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.05)))
|
||||
}
|
||||
|
||||
// MARK: - AI Card
|
||||
|
||||
private var aiCard: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("NPU", systemImage: "brain")
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.brViolet)
|
||||
|
||||
if let a = udp.aiStatus {
|
||||
dataRow("Load", "\(a.npuLoad)%", .brElectricBlue)
|
||||
dataRow("Temp", "\(a.npuTemp)\u{00B0}C", a.npuTemp > 70 ? .red : .brAmber)
|
||||
dataRow("Confidence", "\(a.confidence)%", .brHotPink)
|
||||
dataRow("Inferences", "\(a.totalInferences)", .brAmber)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
.background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.05)))
|
||||
}
|
||||
|
||||
// MARK: - Fleet Card
|
||||
|
||||
private var fleetCard: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("FLEET", systemImage: "server.rack")
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.brHotPink)
|
||||
|
||||
if let h = udp.systemHealth {
|
||||
dataRow("Devices", "\(h.fleetOnline)/\(h.fleetTotal)", .brElectricBlue)
|
||||
dataRow("Agents", "\(h.agentsActive)", .brViolet)
|
||||
dataRow("Green", "\(h.trafficGreen)", .green)
|
||||
dataRow("Tasks Done", "\(h.tasksDone)", .green)
|
||||
dataRow("Repos", "\(h.reposCount)", .brAmber)
|
||||
dataRow("CF Projects", "\(h.cfProjects)", .brElectricBlue)
|
||||
dataRow("Memory", "\(h.memoryEntries)", .brViolet)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
.background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.05)))
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func dataRow(_ label: String, _ value: String, _ color: Color) -> some View {
|
||||
HStack {
|
||||
Text(label)
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(.gray)
|
||||
Spacer()
|
||||
Text(value)
|
||||
.font(.system(size: 13, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
private func formatUptime(_ seconds: UInt32) -> String {
|
||||
let h = seconds / 3600
|
||||
let m = (seconds % 3600) / 60
|
||||
let s = seconds % 60
|
||||
return String(format: "%02d:%02d:%02d", h, m, s)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "watchos",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
BlackRoadWatch/WatchAssets.xcassets/Contents.json
Normal file
6
BlackRoadWatch/WatchAssets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
51
BlackRoadWatch/project.yml
Normal file
51
BlackRoadWatch/project.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
name: BlackRoadWatch
|
||||
options:
|
||||
bundleIdPrefix: io.blackroad
|
||||
deploymentTarget:
|
||||
iOS: "16.0"
|
||||
watchOS: "9.0"
|
||||
xcodeVersion: "15.0"
|
||||
createIntermediateGroups: true
|
||||
|
||||
settings:
|
||||
base:
|
||||
SWIFT_VERSION: "5.9"
|
||||
DEVELOPMENT_TEAM: ""
|
||||
|
||||
targets:
|
||||
BlackRoadWatch:
|
||||
type: application
|
||||
platform: iOS
|
||||
sources:
|
||||
- path: Sources/iOS
|
||||
- path: Sources/Shared
|
||||
settings:
|
||||
base:
|
||||
INFOPLIST_FILE: Sources/iOS/Info.plist
|
||||
PRODUCT_BUNDLE_IDENTIFIER: io.blackroad.watch
|
||||
MARKETING_VERSION: "1.0.0"
|
||||
CURRENT_PROJECT_VERSION: "1"
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
||||
CODE_SIGN_ENTITLEMENTS: Sources/iOS/BlackRoadWatch.entitlements
|
||||
entitlements:
|
||||
path: Sources/iOS/BlackRoadWatch.entitlements
|
||||
dependencies:
|
||||
- target: BlackRoadWatch Watch App
|
||||
embed: true
|
||||
|
||||
BlackRoadWatch Watch App:
|
||||
type: application
|
||||
platform: watchOS
|
||||
sources:
|
||||
- path: Sources/WatchApp
|
||||
- path: Sources/Shared
|
||||
settings:
|
||||
base:
|
||||
INFOPLIST_FILE: Sources/WatchApp/Info.plist
|
||||
PRODUCT_BUNDLE_IDENTIFIER: io.blackroad.watch.watchkitapp
|
||||
MARKETING_VERSION: "1.0.0"
|
||||
CURRENT_PROJECT_VERSION: "1"
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
||||
WATCHOS_DEPLOYMENT_TARGET: "9.0"
|
||||
SDKROOT: watchos
|
||||
SUPPORTS_MACCATALYST: false
|
||||
55
BlackRoadWatch/setup.sh
Executable file
55
BlackRoadWatch/setup.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
# BlackRoad Watch - Xcode Project Setup
|
||||
# Run this after installing Xcode to generate the .xcodeproj
|
||||
|
||||
set -e
|
||||
|
||||
PINK='\033[38;5;205m'
|
||||
AMBER='\033[38;5;214m'
|
||||
GREEN='\033[38;5;82m'
|
||||
RESET='\033[0m'
|
||||
|
||||
echo -e "${PINK}[BR]${RESET} BlackRoad Watch - Project Setup"
|
||||
echo ""
|
||||
|
||||
# Check for Xcode
|
||||
if ! xcode-select -p &>/dev/null; then
|
||||
echo -e "${AMBER}[!]${RESET} Xcode not found. Install from App Store first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
XCODE_PATH=$(xcode-select -p)
|
||||
if [[ "$XCODE_PATH" == *"CommandLineTools"* ]]; then
|
||||
echo -e "${AMBER}[!]${RESET} Only Command Line Tools found."
|
||||
echo " Install Xcode.app from the App Store, then run:"
|
||||
echo " sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}[OK]${RESET} Xcode found at: $XCODE_PATH"
|
||||
|
||||
# Install xcodegen if needed
|
||||
if ! command -v xcodegen &>/dev/null; then
|
||||
echo -e "${AMBER}[*]${RESET} Installing XcodeGen..."
|
||||
brew install xcodegen
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}[OK]${RESET} XcodeGen available"
|
||||
|
||||
# Generate project
|
||||
cd "$(dirname "$0")"
|
||||
echo -e "${AMBER}[*]${RESET} Generating Xcode project..."
|
||||
xcodegen generate
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}[OK]${RESET} Project generated! Opening..."
|
||||
open BlackRoadWatch.xcodeproj
|
||||
|
||||
echo ""
|
||||
echo -e "${PINK}[BR]${RESET} Next steps:"
|
||||
echo " 1. Set your Development Team in Xcode (Signing & Capabilities)"
|
||||
echo " 2. Connect your iPhone"
|
||||
echo " 3. Select 'BlackRoadWatch' scheme and build (Cmd+R)"
|
||||
echo " 4. The watch app deploys automatically with the iPhone app"
|
||||
echo ""
|
||||
echo " Make sure the M1s Dock is on the same WiFi network as your iPhone!"
|
||||
Reference in New Issue
Block a user