diff --git a/.gitignore b/.gitignore index efb916b2..39b82caf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,49 @@ +.DS_Store + build/** .build + +# We might want to create a real project config at some point, but for now we +# want to ignore adding the auto-generated one. +.vscode/ + +# Xcode + +## Build generated +build/ +DerivedData + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata + +## Other +*.xccheckout +*.moved-aside +*.xcuserstate +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +.build/ +.swiftpm/ + +# Carthage +Carthage/Build +.UlyssesRoot +.Ulysses-Settings.plist +.Ulysses-Group.plist diff --git a/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.pbxproj b/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.pbxproj new file mode 100644 index 00000000..aaab107c --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.pbxproj @@ -0,0 +1,426 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 23252B5629D739E400BB0370 /* HelloConnectionsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23252B5529D739E400BB0370 /* HelloConnectionsApp.swift */; }; + 23252B5829D739E400BB0370 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23252B5729D739E400BB0370 /* ContentView.swift */; }; + 23252B5A29D739E500BB0370 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23252B5929D739E500BB0370 /* Assets.xcassets */; }; + 23252B5D29D739E500BB0370 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23252B5C29D739E500BB0370 /* Preview Assets.xcassets */; }; + 23D1FD2429D8A73200620802 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D1FD2329D8A73200620802 /* Config.swift */; }; + 23D1FD2629D8A9FF00620802 /* DiscoveredEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D1FD2529D8A9FF00620802 /* DiscoveredEndpoint.swift */; }; + 23D1FD2829D8AA2200620802 /* ConnectionRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D1FD2729D8AA2200620802 /* ConnectionRequest.swift */; }; + 23D1FD2A29D8AA3E00620802 /* ConnectedEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D1FD2929D8AA3E00620802 /* ConnectedEndpoint.swift */; }; + 23D1FD2C29D8AA5100620802 /* Payload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D1FD2B29D8AA5100620802 /* Payload.swift */; }; + 23D1FD2E29D8AC0C00620802 /* DiscoveredEndpointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D1FD2D29D8AC0C00620802 /* DiscoveredEndpointView.swift */; }; + 23D1FD3029D8AC2E00620802 /* ConnectionRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D1FD2F29D8AC2E00620802 /* ConnectionRequestView.swift */; }; + 23D1FD3229D8AC4900620802 /* ConnectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D1FD3129D8AC4900620802 /* ConnectedView.swift */; }; + 23D1FD3429D8AC5E00620802 /* PayloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D1FD3329D8AC5E00620802 /* PayloadView.swift */; }; + 23F6A70829D7621F00558F56 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23F6A70729D7621F00558F56 /* Model.swift */; }; + 23F6A71129D772B800558F56 /* NearbyConnections in Frameworks */ = {isa = PBXBuildFile; productRef = 23F6A71029D772B800558F56 /* NearbyConnections */; }; + 23F6A71329D7ADCE00558F56 /* EndpointDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23F6A71229D7ADCE00558F56 /* EndpointDetail.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 23252B5229D739E400BB0370 /* iOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 23252B5529D739E400BB0370 /* HelloConnectionsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelloConnectionsApp.swift; sourceTree = ""; }; + 23252B5729D739E400BB0370 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 23252B5929D739E500BB0370 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 23252B5C29D739E500BB0370 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 23D1FD2329D8A73200620802 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; + 23D1FD2529D8A9FF00620802 /* DiscoveredEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveredEndpoint.swift; sourceTree = ""; }; + 23D1FD2729D8AA2200620802 /* ConnectionRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionRequest.swift; sourceTree = ""; }; + 23D1FD2929D8AA3E00620802 /* ConnectedEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedEndpoint.swift; sourceTree = ""; }; + 23D1FD2B29D8AA5100620802 /* Payload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Payload.swift; sourceTree = ""; }; + 23D1FD2D29D8AC0C00620802 /* DiscoveredEndpointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveredEndpointView.swift; sourceTree = ""; }; + 23D1FD2F29D8AC2E00620802 /* ConnectionRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionRequestView.swift; sourceTree = ""; }; + 23D1FD3129D8AC4900620802 /* ConnectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedView.swift; sourceTree = ""; }; + 23D1FD3329D8AC5E00620802 /* PayloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayloadView.swift; sourceTree = ""; }; + 23F6A70729D7621F00558F56 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; + 23F6A70E29D76DC400558F56 /* nearby */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = nearby; path = ../../../..; sourceTree = ""; }; + 23F6A71229D7ADCE00558F56 /* EndpointDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointDetail.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 23252B4F29D739E400BB0370 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 23F6A71129D772B800558F56 /* NearbyConnections in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 23252B4929D739E400BB0370 = { + isa = PBXGroup; + children = ( + 23F6A70D29D76DC400558F56 /* Packages */, + 23252B5429D739E400BB0370 /* iOS Example */, + 23252B5329D739E400BB0370 /* Products */, + 23F6A70F29D772B800558F56 /* Frameworks */, + ); + sourceTree = ""; + }; + 23252B5329D739E400BB0370 /* Products */ = { + isa = PBXGroup; + children = ( + 23252B5229D739E400BB0370 /* iOS Example.app */, + ); + name = Products; + sourceTree = ""; + }; + 23252B5429D739E400BB0370 /* iOS Example */ = { + isa = PBXGroup; + children = ( + 23F6A71429D7B0AB00558F56 /* Model */, + 23252B5529D739E400BB0370 /* HelloConnectionsApp.swift */, + 23252B5729D739E400BB0370 /* ContentView.swift */, + 23F6A71229D7ADCE00558F56 /* EndpointDetail.swift */, + 23D1FD3329D8AC5E00620802 /* PayloadView.swift */, + 23D1FD3129D8AC4900620802 /* ConnectedView.swift */, + 23D1FD2F29D8AC2E00620802 /* ConnectionRequestView.swift */, + 23D1FD2D29D8AC0C00620802 /* DiscoveredEndpointView.swift */, + 23252B5929D739E500BB0370 /* Assets.xcassets */, + 23252B5B29D739E500BB0370 /* Preview Content */, + ); + path = "iOS Example"; + sourceTree = ""; + }; + 23252B5B29D739E500BB0370 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 23252B5C29D739E500BB0370 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 23F6A70D29D76DC400558F56 /* Packages */ = { + isa = PBXGroup; + children = ( + 23F6A70E29D76DC400558F56 /* nearby */, + ); + name = Packages; + sourceTree = ""; + }; + 23F6A70F29D772B800558F56 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 23F6A71429D7B0AB00558F56 /* Model */ = { + isa = PBXGroup; + children = ( + 23F6A70729D7621F00558F56 /* Model.swift */, + 23D1FD2B29D8AA5100620802 /* Payload.swift */, + 23D1FD2929D8AA3E00620802 /* ConnectedEndpoint.swift */, + 23D1FD2729D8AA2200620802 /* ConnectionRequest.swift */, + 23D1FD2529D8A9FF00620802 /* DiscoveredEndpoint.swift */, + 23D1FD2329D8A73200620802 /* Config.swift */, + ); + path = Model; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 23252B5129D739E400BB0370 /* iOS Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 23252B6029D739E500BB0370 /* Build configuration list for PBXNativeTarget "iOS Example" */; + buildPhases = ( + 23252B4E29D739E400BB0370 /* Sources */, + 23252B4F29D739E400BB0370 /* Frameworks */, + 23252B5029D739E400BB0370 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "iOS Example"; + packageProductDependencies = ( + 23F6A71029D772B800558F56 /* NearbyConnections */, + ); + productName = "iOS Example"; + productReference = 23252B5229D739E400BB0370 /* iOS Example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 23252B4A29D739E400BB0370 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1420; + LastUpgradeCheck = 1420; + TargetAttributes = { + 23252B5129D739E400BB0370 = { + CreatedOnToolsVersion = 14.2; + }; + }; + }; + buildConfigurationList = 23252B4D29D739E400BB0370 /* Build configuration list for PBXProject "iOS Example" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 23252B4929D739E400BB0370; + productRefGroup = 23252B5329D739E400BB0370 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 23252B5129D739E400BB0370 /* iOS Example */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 23252B5029D739E400BB0370 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 23252B5D29D739E500BB0370 /* Preview Assets.xcassets in Resources */, + 23252B5A29D739E500BB0370 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 23252B4E29D739E400BB0370 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 23252B5829D739E400BB0370 /* ContentView.swift in Sources */, + 23F6A70829D7621F00558F56 /* Model.swift in Sources */, + 23D1FD2429D8A73200620802 /* Config.swift in Sources */, + 23D1FD2E29D8AC0C00620802 /* DiscoveredEndpointView.swift in Sources */, + 23D1FD3429D8AC5E00620802 /* PayloadView.swift in Sources */, + 23D1FD2C29D8AA5100620802 /* Payload.swift in Sources */, + 23252B5629D739E400BB0370 /* HelloConnectionsApp.swift in Sources */, + 23D1FD2629D8A9FF00620802 /* DiscoveredEndpoint.swift in Sources */, + 23D1FD3029D8AC2E00620802 /* ConnectionRequestView.swift in Sources */, + 23D1FD3229D8AC4900620802 /* ConnectedView.swift in Sources */, + 23D1FD2829D8AA2200620802 /* ConnectionRequest.swift in Sources */, + 23D1FD2A29D8AA3E00620802 /* ConnectedEndpoint.swift in Sources */, + 23F6A71329D7ADCE00558F56 /* EndpointDetail.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 23252B5E29D739E500BB0370 /* 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++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; + 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 = ( + "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 = 16.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 23252B5F29D739E500BB0370 /* 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++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"; + 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.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 23252B6129D739E500BB0370 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"iOS Example/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.google.nearby.iOS-Example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 23252B6229D739E500BB0370 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"iOS Example/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.google.nearby.iOS-Example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 23252B4D29D739E400BB0370 /* Build configuration list for PBXProject "iOS Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 23252B5E29D739E500BB0370 /* Debug */, + 23252B5F29D739E500BB0370 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 23252B6029D739E500BB0370 /* Build configuration list for PBXNativeTarget "iOS Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 23252B6129D739E500BB0370 /* Debug */, + 23252B6229D739E500BB0370 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + 23F6A71029D772B800558F56 /* NearbyConnections */ = { + isa = XCSwiftPackageProductDependency; + productName = NearbyConnections; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 23252B4A29D739E400BB0370 /* Project object */; +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..677a6086 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "abseil", + "repositoryURL": "https://github.com/bourdakos1/abseil-cpp-SwiftPM.git", + "state": { + "branch": "cxx17", + "revision": "a7042563d167160f56e614cd1e2a32616510d9ec", + "version": null + } + }, + { + "package": "BoringSSL-GRPC", + "repositoryURL": "https://github.com/firebase/boringssl-SwiftPM.git", + "state": { + "branch": null, + "revision": "734a8247442fde37df4364c21f6a0085b6a36728", + "version": "0.7.2" + } + } + ] + }, + "version": 1 +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/Assets.xcassets/AccentColor.colorset/Contents.json b/connections/swift/NearbyConnections/Example/iOS Example/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/connections/swift/NearbyConnections/Example/iOS Example/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..13613e3e --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/Assets.xcassets/Contents.json b/connections/swift/NearbyConnections/Example/iOS Example/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/ConnectedView.swift b/connections/swift/NearbyConnections/Example/iOS Example/ConnectedView.swift new file mode 100644 index 00000000..57dfa6f0 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/ConnectedView.swift @@ -0,0 +1,57 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct ConnectedView: View { + var connection: ConnectedEndpoint + var onSendBytes : () -> () + var onDisconnect : () -> () + + var body: some View { + Section("Connection Actions") { + Button("Send Bytes") { + onSendBytes() + } + Button("Disconnect", role: .destructive) { + onDisconnect() + } + } + + if !connection.payloads.isEmpty { + Section(header: Text("Payloads")) { + ForEach(connection.payloads) { payload in + PayloadView(payload: payload) + } + } + } + } +} + +struct ConnectedView_Previews: PreviewProvider { + static var previews: some View { + Form { + ConnectedView( + connection: ConnectedEndpoint( + id: "", + endpointName: "Example" + ), + onSendBytes: {}, + onDisconnect: {} + ) + } + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/ConnectionRequestView.swift b/connections/swift/NearbyConnections/Example/iOS Example/ConnectionRequestView.swift new file mode 100644 index 00000000..9f5f59a9 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/ConnectionRequestView.swift @@ -0,0 +1,55 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct ConnectionRequestView: View { + var connectionRequest: ConnectionRequest + var onAcceptConnection : () -> () + var onRejectConnection : () -> () + + @State private var hasResponded = false + + var body: some View { + Section(footer: Text("Security code: \(connectionRequest.pin)")) { + Button("Accept") { + hasResponded = true + onAcceptConnection() + }.disabled(hasResponded) + Button("Reject", role: .destructive) { + hasResponded = true + onRejectConnection() + }.disabled(hasResponded) + } + } +} + +struct ConnectionRequestView_Previews: PreviewProvider { + static var previews: some View { + Form { + ConnectionRequestView( + connectionRequest: ConnectionRequest( + id: "", + endpointName: "Example", + pin: "1234", + shouldAccept: { _ in } + ), + onAcceptConnection: {}, + onRejectConnection: {} + ) + } + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/ContentView.swift b/connections/swift/NearbyConnections/Example/iOS Example/ContentView.swift new file mode 100644 index 00000000..651df340 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/ContentView.swift @@ -0,0 +1,84 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import NearbyConnections + +struct ContentView: View { + @EnvironmentObject private var model: Model + + let strategies: [Strategy: String] = [ + .cluster: "Cluster", + .pointToPoint: "Point to Point", + .star: "Star" + ] + + var body: some View { + NavigationSplitView { + Form { + TextField("Endpoint Name", text: $model.endpointName) + + Picker("Strategy", selection: $model.strategy) { + ForEach(Array(strategies), id: \.key) { key, value in + Text(value).tag(key) + } + } + + Toggle("Advertising", isOn: $model.isAdvertisingEnabled) + + Toggle("Discovery", isOn: $model.isDiscoveryEnabled) + + if !model.requests.isEmpty { + Section(header: Text("Pending Connections")) { + ForEach(model.requests) { request in + NavigationLink(value: request.id) { + Text(request.endpointName) + } + } + } + } + + if !model.connections.isEmpty { + Section(header: Text("Connections")) { + ForEach(model.connections) { connection in + NavigationLink(value: connection.id) { + Text(connection.endpointName) + } + } + } + } + + if !model.endpoints.isEmpty { + Section(header: Text("Endpoints")) { + ForEach(model.endpoints) { endpoint in + NavigationLink(value: endpoint.id) { + Text(endpoint.endpointName) + } + } + } + } + } + .navigationDestination(for: String.self) { endpointID in + EndpointDetail(endpointID: endpointID) + } + .navigationTitle("Hello Connections") + } detail: { + Text("No Endpoints Selected") + .font(.headline) + .foregroundColor(.secondary) + } + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/DiscoveredEndpointView.swift b/connections/swift/NearbyConnections/Example/iOS Example/DiscoveredEndpointView.swift new file mode 100644 index 00000000..c446e1ab --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/DiscoveredEndpointView.swift @@ -0,0 +1,47 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct DiscoveredEndpointView: View { + var endpoint: DiscoveredEndpoint + var onRequestConnection : () -> () + + @State private var hasResponded = false + + var body: some View { + Section { + Button("Connect") { + hasResponded = true + onRequestConnection() + }.disabled(hasResponded) + } + } +} + +struct DiscoveredEndpointView_Previews: PreviewProvider { + static var previews: some View { + Form { + DiscoveredEndpointView( + endpoint: DiscoveredEndpoint( + id: "", + endpointName: "Example" + ), + onRequestConnection: {} + ) + } + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/EndpointDetail.swift b/connections/swift/NearbyConnections/Example/iOS Example/EndpointDetail.swift new file mode 100644 index 00000000..7875082f --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/EndpointDetail.swift @@ -0,0 +1,55 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct EndpointDetail: View { + var endpointID: String + + @EnvironmentObject private var model: Model + + var body: some View { + let endpoint = model.endpoints.first { $0.id == endpointID } + let connectionRequest = model.requests.first { $0.id == endpointID } + let connection = model.connections.first { $0.id == endpointID } + let name = connectionRequest?.endpointName ?? connection?.endpointName ?? endpoint?.endpointName + + Form { + if let endpoint { + DiscoveredEndpointView(endpoint: endpoint, onRequestConnection: { + model.requestConnection(to: endpointID) + }) + } + + if let connectionRequest { + ConnectionRequestView(connectionRequest: connectionRequest, onAcceptConnection: { + connectionRequest.shouldAccept(true) + }, onRejectConnection: { + connectionRequest.shouldAccept(false) + }) + } + + if let connection { + ConnectedView(connection: connection, onSendBytes: { + model.sendBytes(to: [endpointID]) + }, onDisconnect: { + model.disconnect(from: endpointID) + }) + } + } + .navigationTitle(name ?? "") + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/HelloConnectionsApp.swift b/connections/swift/NearbyConnections/Example/iOS Example/HelloConnectionsApp.swift new file mode 100644 index 00000000..5778c0e9 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/HelloConnectionsApp.swift @@ -0,0 +1,29 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +@main +struct HelloConnectionsApp: App { + @StateObject private var model = Model() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(model) + } + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/Model/Config.swift b/connections/swift/NearbyConnections/Example/iOS Example/Model/Config.swift new file mode 100644 index 00000000..64165f8b --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/Model/Config.swift @@ -0,0 +1,37 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +#if os(iOS) || os(watchOS) || os(tvOS) +import UIKit +#endif +import NearbyConnections + +class Config { + static let serviceId = "com.google.location.nearby.apps.helloconnections" + static let defaultStategy = Strategy.cluster + static let defaultAdvertisingState = false + static let defaultDiscoveryState = false + static let bytePayload = "hello world" + +#if os(iOS) || os(watchOS) || os(tvOS) + static let defaultEndpointName = UIDevice.current.name +#elseif os(macOS) + static let defaultEndpointName = Host.current().localizedName ?? "Unknown macOS Device" +#else + static let defaultEndpointName = "Unknown Device" +#endif +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/Model/ConnectedEndpoint.swift b/connections/swift/NearbyConnections/Example/iOS Example/Model/ConnectedEndpoint.swift new file mode 100644 index 00000000..be5bb5f9 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/Model/ConnectedEndpoint.swift @@ -0,0 +1,24 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import NearbyConnections + +struct ConnectedEndpoint: Identifiable { + let id: EndpointID + let endpointName: String + var payloads: [Payload] = [] +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/Model/ConnectionRequest.swift b/connections/swift/NearbyConnections/Example/iOS Example/Model/ConnectionRequest.swift new file mode 100644 index 00000000..9b584c18 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/Model/ConnectionRequest.swift @@ -0,0 +1,25 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import NearbyConnections + +struct ConnectionRequest: Identifiable { + let id: EndpointID + let endpointName: String + let pin: String + let shouldAccept: ((Bool) -> Void) +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/Model/DiscoveredEndpoint.swift b/connections/swift/NearbyConnections/Example/iOS Example/Model/DiscoveredEndpoint.swift new file mode 100644 index 00000000..ece0ab72 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/Model/DiscoveredEndpoint.swift @@ -0,0 +1,23 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import NearbyConnections + +struct DiscoveredEndpoint: Identifiable { + let id: EndpointID + let endpointName: String +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/Model/Model.swift b/connections/swift/NearbyConnections/Example/iOS Example/Model/Model.swift new file mode 100644 index 00000000..8c4a10b1 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/Model/Model.swift @@ -0,0 +1,251 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import NearbyConnections + +class Model: ObservableObject { + @Published var endpointName = Config.defaultEndpointName { + didSet { + invalidateAdvertising() + } + } + @Published var strategy = Config.defaultStategy { + didSet { + invalidateAdvertising() + invalidateDiscovery() + } + } + @Published var isAdvertisingEnabled = Config.defaultAdvertisingState { + didSet { + invalidateAdvertising() + } + } + @Published var isDiscoveryEnabled = Config.defaultDiscoveryState { + didSet { + invalidateDiscovery() + } + } + + @Published private(set) var requests: [ConnectionRequest] = [] + @Published private(set) var connections: [ConnectedEndpoint] = [] + @Published private(set) var endpoints: [DiscoveredEndpoint] = [] + + var connectionManager: ConnectionManager! + var advertiser: Advertiser? + var discoverer: Discoverer? + + init() { + invalidateAdvertising() + invalidateDiscovery() + } + + private var isAdvertising = Config.defaultAdvertisingState + private func invalidateAdvertising() { + defer { + isAdvertising = isAdvertisingEnabled + } + if isAdvertising { + advertiser?.stopAdvertising() + } + if !isAdvertisingEnabled { + return + } + connectionManager = ConnectionManager(serviceID: Config.serviceId, strategy: strategy) + connectionManager.delegate = self + + advertiser = Advertiser(connectionManager: connectionManager) + advertiser?.delegate = self + advertiser?.startAdvertising(using: endpointName.data(using: .utf8)!) + } + + private var isDiscovering = Config.defaultDiscoveryState + private func invalidateDiscovery() { + defer { + isDiscovering = isDiscoveryEnabled + } + if isDiscovering { + discoverer?.stopDiscovery() + } + if !isDiscoveryEnabled { + return + } + connectionManager = ConnectionManager(serviceID: Config.serviceId, strategy: strategy) + connectionManager.delegate = self + + discoverer = Discoverer(connectionManager: connectionManager) + discoverer?.delegate = self + discoverer?.startDiscovery() + } + + func requestConnection(to endpointID: EndpointID) { + discoverer?.requestConnection(to: endpointID, using: endpointName.data(using: .utf8)!) + } + + func disconnect(from endpointID: EndpointID) { + connectionManager.disconnect(from: endpointID) + } + + func sendBytes(to endpointIDs: [EndpointID]) { + let payloadID = PayloadID.unique() + let token = connectionManager.send(Config.bytePayload.data(using: .utf8)!, to: endpointIDs, id: payloadID) + let payload = Payload( + id: payloadID, + type: .bytes, + status: .inProgress(Progress()), + isIncoming: false, + cancellationToken: token + ) + for endpointID in endpointIDs { + guard let index = connections.firstIndex(where: { $0.id == endpointID }) else { + return + } + connections[index].payloads.insert(payload, at: 0) + } + } +} + +extension Model: DiscovererDelegate { + func discoverer(_ discoverer: Discoverer, didFind endpointID: EndpointID, with context: Data) { + let endpoint = DiscoveredEndpoint( + id: endpointID, + endpointName: String(data: context, encoding: .utf8)! + ) + endpoints.insert(endpoint, at: 0) + } + + func discoverer(_ discoverer: Discoverer, didLose endpointID: EndpointID) { + guard let index = endpoints.firstIndex(where: { $0.id == endpointID }) else { + return + } + endpoints.remove(at: index) + } +} + +extension Model: AdvertiserDelegate { + func advertiser(_ advertiser: Advertiser, didReceiveConnectionRequestFrom endpointID: EndpointID, with context: Data, connectionRequestHandler: @escaping (Bool) -> Void) { + let endpoint = DiscoveredEndpoint( + id: endpointID, + endpointName: String(data: context, encoding: .utf8)! + ) + endpoints.insert(endpoint, at: 0) + connectionRequestHandler(true) + } +} + +extension Model: ConnectionManagerDelegate { + func connectionManager(_ connectionManager: ConnectionManager, didReceive verificationCode: String, from endpointID: EndpointID, verificationHandler: @escaping (Bool) -> Void) { + guard let index = endpoints.firstIndex(where: { $0.id == endpointID }) else { + return + } + let endpoint = endpoints.remove(at: index) + let request = ConnectionRequest( + id: endpointID, + endpointName: endpoint.endpointName, + pin: verificationCode, + shouldAccept: { accept in + verificationHandler(accept) + } + ) + requests.insert(request, at: 0) + } + + func connectionManager(_ connectionManager: ConnectionManager, didReceive data: Data, withID payloadID: PayloadID, from endpointID: EndpointID) { + let payload = Payload( + id: payloadID, + type: .bytes, + status: .success, + isIncoming: true, + cancellationToken: nil + ) + guard let index = connections.firstIndex(where: { $0.id == endpointID }) else { + return + } + connections[index].payloads.insert(payload, at: 0) + } + + func connectionManager(_ connectionManager: ConnectionManager, didReceive stream: InputStream, withID payloadID: PayloadID, from endpointID: EndpointID, cancellationToken token: CancellationToken) { + let payload = Payload( + id: payloadID, + type: .stream, + status: .success, + isIncoming: true, + cancellationToken: token + ) + guard let index = connections.firstIndex(where: { $0.id == endpointID }) else { + return + } + connections[index].payloads.insert(payload, at: 0) + } + + func connectionManager(_ connectionManager: ConnectionManager, didStartReceivingResourceWithID payloadID: PayloadID, from endpointID: EndpointID, at localURL: URL, withName name: String, cancellationToken token: CancellationToken) { + let payload = Payload( + id: payloadID, + type: .file, + status: .inProgress(Progress()), + isIncoming: true, + cancellationToken: token + ) + guard let index = connections.firstIndex(where: { $0.id == endpointID }) else { + return + } + connections[index].payloads.insert(payload, at: 0) + } + + func connectionManager(_ connectionManager: ConnectionManager, didReceiveTransferUpdate update: TransferUpdate, from endpointID: EndpointID, forPayload payloadID: PayloadID) { + guard let connectionIndex = connections.firstIndex(where: { $0.id == endpointID }), + let payloadIndex = connections[connectionIndex].payloads.firstIndex(where: { $0.id == payloadID }) else { + return + } + switch update { + case .success: + connections[connectionIndex].payloads[payloadIndex].status = .success + case .canceled: + connections[connectionIndex].payloads[payloadIndex].status = .canceled + case .failure: + connections[connectionIndex].payloads[payloadIndex].status = .failure + case let .progress(progress): + connections[connectionIndex].payloads[payloadIndex].status = .inProgress(progress) + } + } + + func connectionManager(_ connectionManager: ConnectionManager, didChangeTo state: ConnectionState, for endpointID: EndpointID) { + switch (state) { + case .connecting: + break + case .connected: + guard let index = requests.firstIndex(where: { $0.id == endpointID }) else { + return + } + let request = requests.remove(at: index) + let connection = ConnectedEndpoint( + id: endpointID, + endpointName: request.endpointName + ) + connections.insert(connection, at: 0) + case .disconnected: + guard let index = connections.firstIndex(where: { $0.id == endpointID }) else { + return + } + connections.remove(at: index) + case .rejected: + guard let index = requests.firstIndex(where: { $0.id == endpointID }) else { + return + } + requests.remove(at: index) + } + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/Model/Payload.swift b/connections/swift/NearbyConnections/Example/iOS Example/Model/Payload.swift new file mode 100644 index 00000000..a3eaf1d9 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/Model/Payload.swift @@ -0,0 +1,33 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import NearbyConnections + +struct Payload: Identifiable { + let id: PayloadID + var type: PayloadType + var status: Status + let isIncoming: Bool + let cancellationToken: CancellationToken? + + enum PayloadType { + case bytes, stream, file + } + enum Status { + case inProgress(Progress), success, failure, canceled + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/PayloadView.swift b/connections/swift/NearbyConnections/Example/iOS Example/PayloadView.swift new file mode 100644 index 00000000..5ec63b38 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/PayloadView.swift @@ -0,0 +1,121 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct PayloadView: View { + var payload: Payload + + var body: some View { + HStack(alignment: .top) { + Image(systemName: payload.isIncoming + ? "square.and.arrow.down" + : "square.and.arrow.up") + VStack(alignment: .leading) { + switch payload.type { + case .bytes: + Text("Bytes") + case .file: + Text("File") + case .stream: + Text("Stream") + } + HStack { + switch payload.status { + case let .inProgress(progress): + Image(systemName: "xmark.circle.fill").onTapGesture { + payload.cancellationToken?.cancel() + }.foregroundColor(.secondary) + ProgressView(value: Float(progress.completedUnitCount), total: Float(progress.totalUnitCount)) + case .canceled: + Text("Canceled") + .font(.caption) + .foregroundColor(.secondary) + case .failure: + Text("Failed") + .font(.caption) + .foregroundColor(.secondary) + case .success: + Text(payload.isIncoming ? "Received" : "Sent") + .font(.caption) + .foregroundColor(.secondary) + } + } + } + } + } +} + +struct PayloadView_Previews: PreviewProvider { + static var previews: some View { + List { + PayloadView( + payload: Payload( + id: 0, + type: .bytes, + status: .success, + isIncoming: false, + cancellationToken: nil + ) + ) + PayloadView( + payload: Payload( + id: 0, + type: .bytes, + status: .success, + isIncoming: true, + cancellationToken: nil + ) + ) + PayloadView( + payload: Payload( + id: 0, + type: .bytes, + status: .canceled, + isIncoming: true, + cancellationToken: nil + ) + ) + PayloadView( + payload: Payload( + id: 0, + type: .bytes, + status: .failure, + isIncoming: true, + cancellationToken: nil + ) + ) + PayloadView( + payload: Payload( + id: 0, + type: .file, + status: .inProgress(Progress(totalUnitCount: 100)), + isIncoming: true, + cancellationToken: nil + ) + ) + PayloadView( + payload: Payload( + id: 0, + type: .stream, + status: .inProgress(Progress()), + isIncoming: true, + cancellationToken: nil + ) + ) + } + } +} diff --git a/connections/swift/NearbyConnections/Example/iOS Example/Preview Content/Preview Assets.xcassets/Contents.json b/connections/swift/NearbyConnections/Example/iOS Example/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/connections/swift/NearbyConnections/Example/iOS Example/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +}