diff --git a/.gitignore b/.gitignore index 369a65ca5..05aea1981 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,7 @@ Podfile.lock **/Pods/* **/*.xcworkspace/* **/*.xcodeproj/* +xcuserdata/ !samples/counter/apps/Counter.xcodeproj/project.pbxproj -*.podspec \ No newline at end of file +!Circuit.xcodeproj/project.pbxproj +*.podspec diff --git a/Circuit.xcodeproj/project.pbxproj b/Circuit.xcodeproj/project.pbxproj new file mode 100644 index 000000000..cee95a29d --- /dev/null +++ b/Circuit.xcodeproj/project.pbxproj @@ -0,0 +1,818 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 1D0026E32AA8F5BE0072496E /* ObservablePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0026E22AA8F5BE0072496E /* ObservablePresenter.swift */; }; + 1DE43D022A2265EE00EB0E36 /* CircuitRuntimeObjC.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DE43D012A2265EE00EB0E36 /* CircuitRuntimeObjC.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1DE43D132A22660400EB0E36 /* CircuitRuntime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */; }; + 1DE43D182A22660B00EB0E36 /* CircuitRuntimeObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */; }; + 1DE43D1E2A22669D00EB0E36 /* CircuitRuntimeObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D1D2A22669D00EB0E36 /* CircuitRuntimeObjC.m */; }; + 1DE43D212A22670F00EB0E36 /* CircuitSwiftUINavigator.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DE43D1F2A2266AF00EB0E36 /* CircuitSwiftUINavigator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1DE43D232A2267EB00EB0E36 /* CircuitPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D222A2267EB00EB0E36 /* CircuitPresenter.swift */; }; + 1DE43D282A226BF200EB0E36 /* CircuitNavigationStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D272A226BF200EB0E36 /* CircuitNavigationStack.swift */; }; + 1DE43D2A2A226C3A00EB0E36 /* CircuitNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D292A226C3A00EB0E36 /* CircuitNavigator.swift */; }; + 1DE43D2C2A226E2E00EB0E36 /* CircuitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D2B2A226E2E00EB0E36 /* CircuitView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 1DE43D152A22660400EB0E36 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1DE43CE72A22655F00EB0E36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1DE43CF12A2265C600EB0E36; + remoteInfo = CircuitRuntime; + }; + 1DE43D1A2A22660B00EB0E36 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1DE43CE72A22655F00EB0E36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1DE43CFE2A2265EE00EB0E36; + remoteInfo = CircuitRuntimeObjC; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 1D0026E22AA8F5BE0072496E /* ObservablePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservablePresenter.swift; sourceTree = ""; }; + 1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CircuitRuntime.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CircuitRuntimeObjC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DE43D012A2265EE00EB0E36 /* CircuitRuntimeObjC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CircuitRuntimeObjC.h; sourceTree = ""; }; + 1DE43D0B2A2265FD00EB0E36 /* CircuitSwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CircuitSwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DE43D1D2A22669D00EB0E36 /* CircuitRuntimeObjC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CircuitRuntimeObjC.m; sourceTree = ""; }; + 1DE43D1F2A2266AF00EB0E36 /* CircuitSwiftUINavigator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CircuitSwiftUINavigator.h; sourceTree = ""; }; + 1DE43D222A2267EB00EB0E36 /* CircuitPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitPresenter.swift; sourceTree = ""; }; + 1DE43D272A226BF200EB0E36 /* CircuitNavigationStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitNavigationStack.swift; sourceTree = ""; }; + 1DE43D292A226C3A00EB0E36 /* CircuitNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitNavigator.swift; sourceTree = ""; }; + 1DE43D2B2A226E2E00EB0E36 /* CircuitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1DE43CEF2A2265C600EB0E36 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DE43D182A22660B00EB0E36 /* CircuitRuntimeObjC.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DE43CFC2A2265EE00EB0E36 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DE43D082A2265FD00EB0E36 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DE43D132A22660400EB0E36 /* CircuitRuntime.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1DE43CE62A22655F00EB0E36 = { + isa = PBXGroup; + children = ( + 1DE43CF42A2265C600EB0E36 /* CircuitRuntime */, + 1DE43D002A2265EE00EB0E36 /* CircuitRuntimeObjC */, + 1DE43D0C2A2265FD00EB0E36 /* CircuitSwiftUI */, + 1DE43CF32A2265C600EB0E36 /* Products */, + 1DE43D122A22660400EB0E36 /* Frameworks */, + ); + sourceTree = ""; + }; + 1DE43CF32A2265C600EB0E36 /* Products */ = { + isa = PBXGroup; + children = ( + 1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */, + 1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */, + 1DE43D0B2A2265FD00EB0E36 /* CircuitSwiftUI.framework */, + ); + name = Products; + sourceTree = ""; + }; + 1DE43CF42A2265C600EB0E36 /* CircuitRuntime */ = { + isa = PBXGroup; + children = ( + 1DE43D222A2267EB00EB0E36 /* CircuitPresenter.swift */, + 1DE43D292A226C3A00EB0E36 /* CircuitNavigator.swift */, + ); + path = CircuitRuntime; + sourceTree = ""; + }; + 1DE43D002A2265EE00EB0E36 /* CircuitRuntimeObjC */ = { + isa = PBXGroup; + children = ( + 1DE43D012A2265EE00EB0E36 /* CircuitRuntimeObjC.h */, + 1DE43D1D2A22669D00EB0E36 /* CircuitRuntimeObjC.m */, + 1DE43D1F2A2266AF00EB0E36 /* CircuitSwiftUINavigator.h */, + ); + path = CircuitRuntimeObjC; + sourceTree = ""; + }; + 1DE43D0C2A2265FD00EB0E36 /* CircuitSwiftUI */ = { + isa = PBXGroup; + children = ( + 1DE43D272A226BF200EB0E36 /* CircuitNavigationStack.swift */, + 1DE43D2B2A226E2E00EB0E36 /* CircuitView.swift */, + 1D0026E22AA8F5BE0072496E /* ObservablePresenter.swift */, + ); + path = CircuitSwiftUI; + sourceTree = ""; + }; + 1DE43D122A22660400EB0E36 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 1DE43CED2A2265C600EB0E36 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DE43CFA2A2265EE00EB0E36 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DE43D212A22670F00EB0E36 /* CircuitSwiftUINavigator.h in Headers */, + 1DE43D022A2265EE00EB0E36 /* CircuitRuntimeObjC.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DE43D062A2265FD00EB0E36 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 1DE43CF12A2265C600EB0E36 /* CircuitRuntime */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DE43CF72A2265C600EB0E36 /* Build configuration list for PBXNativeTarget "CircuitRuntime" */; + buildPhases = ( + 1DE43CED2A2265C600EB0E36 /* Headers */, + 1DE43CEE2A2265C600EB0E36 /* Sources */, + 1DE43CEF2A2265C600EB0E36 /* Frameworks */, + 1DE43CF02A2265C600EB0E36 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 1DE43D1B2A22660B00EB0E36 /* PBXTargetDependency */, + ); + name = CircuitRuntime; + packageProductDependencies = ( + ); + productName = CircuitRuntime; + productReference = 1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */; + productType = "com.apple.product-type.framework"; + }; + 1DE43CFE2A2265EE00EB0E36 /* CircuitRuntimeObjC */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DE43D032A2265EE00EB0E36 /* Build configuration list for PBXNativeTarget "CircuitRuntimeObjC" */; + buildPhases = ( + 1DE43CFA2A2265EE00EB0E36 /* Headers */, + 1DE43CFB2A2265EE00EB0E36 /* Sources */, + 1DE43CFC2A2265EE00EB0E36 /* Frameworks */, + 1DE43CFD2A2265EE00EB0E36 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CircuitRuntimeObjC; + productName = CircuitRuntimeObjC; + productReference = 1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */; + productType = "com.apple.product-type.framework"; + }; + 1DE43D0A2A2265FD00EB0E36 /* CircuitSwiftUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DE43D0F2A2265FD00EB0E36 /* Build configuration list for PBXNativeTarget "CircuitSwiftUI" */; + buildPhases = ( + 1DE43D062A2265FD00EB0E36 /* Headers */, + 1DE43D072A2265FD00EB0E36 /* Sources */, + 1DE43D082A2265FD00EB0E36 /* Frameworks */, + 1DE43D092A2265FD00EB0E36 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 1DE43D162A22660400EB0E36 /* PBXTargetDependency */, + ); + name = CircuitSwiftUI; + packageProductDependencies = ( + ); + productName = CircuitSwiftUI; + productReference = 1DE43D0B2A2265FD00EB0E36 /* CircuitSwiftUI.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 1DE43CE72A22655F00EB0E36 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1430; + TargetAttributes = { + 1DE43CF12A2265C600EB0E36 = { + CreatedOnToolsVersion = 14.3; + LastSwiftMigration = 1430; + }; + 1DE43CFE2A2265EE00EB0E36 = { + CreatedOnToolsVersion = 14.3; + }; + 1DE43D0A2A2265FD00EB0E36 = { + CreatedOnToolsVersion = 14.3; + LastSwiftMigration = 1430; + }; + }; + }; + buildConfigurationList = 1DE43CEA2A22655F00EB0E36 /* Build configuration list for PBXProject "Circuit" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 1DE43CE62A22655F00EB0E36; + packageReferences = ( + ); + productRefGroup = 1DE43CF32A2265C600EB0E36 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1DE43CF12A2265C600EB0E36 /* CircuitRuntime */, + 1DE43CFE2A2265EE00EB0E36 /* CircuitRuntimeObjC */, + 1DE43D0A2A2265FD00EB0E36 /* CircuitSwiftUI */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1DE43CF02A2265C600EB0E36 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DE43CFD2A2265EE00EB0E36 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DE43D092A2265FD00EB0E36 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1DE43CEE2A2265C600EB0E36 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DE43D232A2267EB00EB0E36 /* CircuitPresenter.swift in Sources */, + 1DE43D2A2A226C3A00EB0E36 /* CircuitNavigator.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DE43CFB2A2265EE00EB0E36 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DE43D1E2A22669D00EB0E36 /* CircuitRuntimeObjC.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DE43D072A2265FD00EB0E36 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DE43D2C2A226E2E00EB0E36 /* CircuitView.swift in Sources */, + 1D0026E32AA8F5BE0072496E /* ObservablePresenter.swift in Sources */, + 1DE43D282A226BF200EB0E36 /* CircuitNavigationStack.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 1DE43D162A22660400EB0E36 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1DE43CF12A2265C600EB0E36 /* CircuitRuntime */; + targetProxy = 1DE43D152A22660400EB0E36 /* PBXContainerItemProxy */; + }; + 1DE43D1B2A22660B00EB0E36 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1DE43CFE2A2265EE00EB0E36 /* CircuitRuntimeObjC */; + targetProxy = 1DE43D1A2A22660B00EB0E36 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 1DE43CEB2A22655F00EB0E36 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Debug; + }; + 1DE43CEC2A22655F00EB0E36 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Release; + }; + 1DE43CF82A2265C600EB0E36 /* 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; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + 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; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.1; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitRuntime; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 1DE43CF92A2265C600EB0E36 /* 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; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + 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; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.1; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitRuntime; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 1DE43D042A2265EE00EB0E36 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitRuntimeObjC; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1DE43D052A2265EE00EB0E36 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitRuntimeObjC; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 1DE43D102A2265FD00EB0E36 /* 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; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + 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; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.1; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitSwiftUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 1DE43D112A2265FD00EB0E36 /* 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; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + 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; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.1; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitSwiftUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DE43CEA2A22655F00EB0E36 /* Build configuration list for PBXProject "Circuit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DE43CEB2A22655F00EB0E36 /* Debug */, + 1DE43CEC2A22655F00EB0E36 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DE43CF72A2265C600EB0E36 /* Build configuration list for PBXNativeTarget "CircuitRuntime" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DE43CF82A2265C600EB0E36 /* Debug */, + 1DE43CF92A2265C600EB0E36 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DE43D032A2265EE00EB0E36 /* Build configuration list for PBXNativeTarget "CircuitRuntimeObjC" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DE43D042A2265EE00EB0E36 /* Debug */, + 1DE43D052A2265EE00EB0E36 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DE43D0F2A2265FD00EB0E36 /* Build configuration list for PBXNativeTarget "CircuitSwiftUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DE43D102A2265FD00EB0E36 /* Debug */, + 1DE43D112A2265FD00EB0E36 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 1DE43CE72A22655F00EB0E36 /* Project object */; +} diff --git a/CircuitRuntime/CircuitNavigator.swift b/CircuitRuntime/CircuitNavigator.swift new file mode 100644 index 000000000..a9dab5a73 --- /dev/null +++ b/CircuitRuntime/CircuitNavigator.swift @@ -0,0 +1,37 @@ +// +// CircuitNavigator.swift +// CircuitRuntime +// +// Created by Rick Clephas on 27/05/2023. +// + +import Foundation +import CircuitRuntimeObjC + +public class CircuitNavigator: ObservableObject, CircuitSwiftUINavigator { + + @Published public var root: NSObject + @Published public var path: [NSObject] + + public init(_ root: NSObject, _ path: NSObject...) { + self.root = root + self.path = path + } + + public func goTo(screen: NSObject) { + path.append(screen) + } + + public func pop() -> NSObject? { + return path.popLast() + } + + public func resetRoot(newRoot: NSObject) -> [NSObject] { + let oldRoot = root + var oldPath = path + root = newRoot + path = [] + oldPath.insert(oldRoot, at: 0) + return oldPath + } +} diff --git a/CircuitRuntime/CircuitPresenter.swift b/CircuitRuntime/CircuitPresenter.swift new file mode 100644 index 000000000..232cb6646 --- /dev/null +++ b/CircuitRuntime/CircuitPresenter.swift @@ -0,0 +1,15 @@ +// +// CircuitPresenter.swift +// CircuitRuntime +// +// Created by Rick Clephas on 27/05/2023. +// + +import CircuitRuntimeObjC + +public protocol CircuitPresenter: AnyObject { + var state: Any { get } + var navigator: CircuitSwiftUINavigator? { get set } + func setStateWillChangeListener(listener: @escaping () -> Void) + func cancel() +} diff --git a/CircuitRuntimeObjC/CircuitRuntimeObjC.h b/CircuitRuntimeObjC/CircuitRuntimeObjC.h new file mode 100644 index 000000000..c831c8ecd --- /dev/null +++ b/CircuitRuntimeObjC/CircuitRuntimeObjC.h @@ -0,0 +1,8 @@ +// +// CircuitRuntimeObjC.h +// CircuitRuntimeObjC +// +// Created by Rick Clephas on 27/05/2023. +// + +#import "CircuitSwiftUINavigator.h" diff --git a/CircuitRuntimeObjC/CircuitRuntimeObjC.m b/CircuitRuntimeObjC/CircuitRuntimeObjC.m new file mode 100644 index 000000000..a21ae655a --- /dev/null +++ b/CircuitRuntimeObjC/CircuitRuntimeObjC.m @@ -0,0 +1 @@ +// We need this empty file, else SPM won't build this diff --git a/CircuitRuntimeObjC/CircuitSwiftUINavigator.h b/CircuitRuntimeObjC/CircuitSwiftUINavigator.h new file mode 100644 index 000000000..3f24a2e10 --- /dev/null +++ b/CircuitRuntimeObjC/CircuitSwiftUINavigator.h @@ -0,0 +1,19 @@ +// +// CircuitSwiftUINavigator.h +// Circuit +// +// Created by Rick Clephas on 27/05/2023. +// + +#ifndef CircuitSwiftUINavigator_h +#define CircuitSwiftUINavigator_h + +#import + +@protocol CircuitSwiftUINavigator +- (void)goToScreen:(NSObject * _Nonnull)screen __attribute__((swift_name("goTo(screen:)"))); +- (NSObject * _Nullable)pop __attribute__((swift_name("pop()"))); +- (NSArray * _Nonnull)resetRootNewRoot:(NSObject * _Nonnull)newRoot __attribute__((swift_name("resetRoot(newRoot:)"))); +@end + +#endif /* CircuitSwiftUINavigator_h */ diff --git a/CircuitSwiftUI/CircuitNavigationStack.swift b/CircuitSwiftUI/CircuitNavigationStack.swift new file mode 100644 index 000000000..c4b7e6d49 --- /dev/null +++ b/CircuitSwiftUI/CircuitNavigationStack.swift @@ -0,0 +1,32 @@ +// +// CircuitNavigationStack.swift +// CircuitSwiftUI +// +// Created by Rick Clephas on 27/05/2023. +// + +import CircuitRuntime +import SwiftUI + +public struct CircuitNavigationStack: View { + + @ObservedObject private var navigator: CircuitNavigator + private let content: (NSObject) -> Content + + public init(_ navigator: CircuitNavigator, @ViewBuilder _ content: @escaping (_ screen: NSObject) -> Content) { + self._navigator = ObservedObject(wrappedValue: navigator) + self.content = content + } + + public var body: some View { + NavigationStack(path: $navigator.path) { + screen(navigator.root).navigationDestination(for: NSObject.self) { path in + screen(path) + } + }.environmentObject(navigator) + } + + private func screen(_ screen: NSObject) -> some View { + content(screen).id(screen) + } +} diff --git a/CircuitSwiftUI/CircuitView.swift b/CircuitSwiftUI/CircuitView.swift new file mode 100644 index 000000000..ffb5625e1 --- /dev/null +++ b/CircuitSwiftUI/CircuitView.swift @@ -0,0 +1,32 @@ +// +// CircuitView.swift +// CircuitSwiftUI +// +// Created by Rick Clephas on 27/05/2023. +// + +import CircuitRuntime +import SwiftUI + +public struct CircuitView: View { + + @EnvironmentObject private var navigator: CircuitNavigator + @StateObject private var observableObject: ObservablePresenter + private var stateKeyPath: KeyPath + private var content: (State) -> Content + + public init(_ presenter: @autoclosure @escaping () -> Presenter, + @ViewBuilder _ content: @escaping (State) -> Content, + _ stateKeyPath: KeyPath = \Presenter.state + ) { + self._observableObject = StateObject(wrappedValue: observablePresenter(for: presenter())) + self.stateKeyPath = stateKeyPath + self.content = content + } + + public var body: some View { + content(observableObject.presenter[keyPath: stateKeyPath]).onAppear { + observableObject.presenter.navigator = navigator + } + } +} diff --git a/CircuitSwiftUI/ObservablePresenter.swift b/CircuitSwiftUI/ObservablePresenter.swift new file mode 100644 index 000000000..70a3e5330 --- /dev/null +++ b/CircuitSwiftUI/ObservablePresenter.swift @@ -0,0 +1,47 @@ +// +// ObservablePresenter.swift +// CircuitSwiftUI +// +// Created by Rick Clephas on 06/09/2023. +// + +import Foundation +import CircuitRuntime + +internal class ObservablePresenter: ObservableObject { + let presenter: Presenter + + init(_ presenter: Presenter) { + self.presenter = presenter + presenter.setStateWillChangeListener { [weak self] in + self?.objectWillChange.send() + } + } + + deinit { + presenter.cancel() + } +} + +private var observablePresenterKey = "observablePresenter" + +private class WeakObservablePresenter { + weak var observablePresenter: ObservablePresenter? + init(_ observablePresenter: ObservablePresenter) { + self.observablePresenter = observablePresenter + } +} + +internal func observablePresenter(for presenter: Presenter) -> ObservablePresenter { + if let object = objc_getAssociatedObject(presenter, &observablePresenterKey) { + guard let observablePresenter = (object as! WeakObservablePresenter).observablePresenter else { + fatalError("ObservablePresenter has been deallocated") + } + return observablePresenter + } else { + let observablePresenter = ObservablePresenter(presenter) + let object = WeakObservablePresenter(observablePresenter) + objc_setAssociatedObject(presenter, &observablePresenterKey, object, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return observablePresenter + } +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 000000000..719e8dc34 --- /dev/null +++ b/Package.swift @@ -0,0 +1,35 @@ +// swift-tools-version:5.7 +import PackageDescription + +let package = Package( + name: "Circuit", + platforms: [.iOS(.v16)], + products: [ + .library( + name: "CircuitRuntime", + targets: ["CircuitRuntime"] + ), + .library( + name: "CircuitSwiftUI", + targets: ["CircuitSwiftUI"] + ) + ], + targets: [ + .target( + name: "CircuitRuntimeObjC", + path: "CircuitRuntimeObjC", + publicHeadersPath: "." + ), + .target( + name: "CircuitRuntime", + dependencies: [.target(name: "CircuitRuntimeObjC")], + path: "CircuitRuntime" + ), + .target( + name: "CircuitSwiftUI", + dependencies: [.target(name: "CircuitRuntime")], + path: "CircuitSwiftUI" + ) + ], + swiftLanguageVersions: [.v5] +) diff --git a/build.gradle.kts b/build.gradle.kts index bbafa528e..a5f0edfcf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -177,7 +177,7 @@ subprojects { if (hasCompose) { dependencies { add(PLUGIN_CLASSPATH_CONFIGURATION_NAME, libs.androidx.compose.compiler) - add(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME, libs.androidx.compose.compiler) +// add(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME, libs.androidx.compose.compiler) } } diff --git a/circuit-swiftui/build.gradle.kts b/circuit-swiftui/build.gradle.kts new file mode 100644 index 000000000..ba365ebab --- /dev/null +++ b/circuit-swiftui/build.gradle.kts @@ -0,0 +1,33 @@ +// Copyright (C) 2022 Slack Technologies, LLC +// SPDX-License-Identifier: Apache-2.0 +plugins { + kotlin("multiplatform") + alias(libs.plugins.compose) +} + +kotlin { + // region KMP Targets + val iosArm64 = iosArm64() + val iosX64 = iosX64() + val iosSimulatorArm64 = iosSimulatorArm64() + // endregion + + sourceSets { + commonMain { + dependencies { + api(projects.circuitRuntimePresenter) + implementation(libs.molecule.runtime) + } + } + } + + listOf( + iosArm64, iosX64, iosSimulatorArm64 + ).forEach { + it.compilations.getByName("main") { + cinterops.create("CircuitRuntimeObjC") { + includeDirs("$projectDir/../CircuitRuntimeObjC") + } + } + } +} diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt new file mode 100644 index 000000000..2489e5e22 --- /dev/null +++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt @@ -0,0 +1,21 @@ +package com.slack.circuit.swiftui + +import com.slack.circuit.runtime.Navigator +import com.slack.circuit.runtime.Screen +import com.slack.circuit.swiftui.objc.CircuitSwiftUINavigatorProtocol +import platform.darwin.NSObject + +internal class SwiftUINavigator: Navigator { + + var navigator: CircuitSwiftUINavigatorProtocol? = null + + private fun requireNavigator(): CircuitSwiftUINavigatorProtocol = + navigator ?: throw RuntimeException("SwiftUINavigator hasn't been initialized") + + override fun goTo(screen: Screen): Unit = requireNavigator().goToScreen(screen as NSObject) + + override fun pop(): Screen? = requireNavigator().pop() as Screen? + + override fun resetRoot(newRoot: Screen): List = + requireNavigator().resetRootNewRoot(newRoot as NSObject).map { it as Screen } +} diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt new file mode 100644 index 000000000..cb1e85de4 --- /dev/null +++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt @@ -0,0 +1,63 @@ +package com.slack.circuit.swiftui + +import androidx.compose.runtime.Composable +import app.cash.molecule.RecompositionClock +import app.cash.molecule.launchMolecule +import com.slack.circuit.runtime.CircuitUiState +import com.slack.circuit.runtime.Navigator +import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuit.runtime.presenter.presenterOf +import com.slack.circuit.swiftui.objc.CircuitSwiftUINavigatorProtocol +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel + +public class SwiftUIPresenter internal constructor( + private val swiftUINavigator: SwiftUINavigator, + private val presenter: Presenter +): SwiftUIPresenterProtocol() { + public interface Factory { + public fun presenter(): SwiftUIPresenter + } + + private val coroutineScope = CoroutineScope(Dispatchers.Main) + + public override lateinit var state: UiState + private set + + public override var navigator: CircuitSwiftUINavigatorProtocol? + get() = swiftUINavigator.navigator + set(value) { swiftUINavigator.navigator = value } + + private var stateWillChangeListener: (() -> Unit)? = null + + public override fun setStateWillChangeListener(listener: () -> Unit) { + if (this.stateWillChangeListener != null) { + throw IllegalStateException("SwiftUIPresenter can't be wrapped more than once") + } + stateWillChangeListener = listener + } + + public override fun cancel() { + coroutineScope.cancel() + } + + init { + coroutineScope.launchMolecule( + clock = RecompositionClock.Immediate, + emitter = { value -> + stateWillChangeListener?.invoke() + state = value + } + ) { + presenter.present() + } + } +} + +public fun swiftUIPresenterOf( + body: @Composable (Navigator) -> UiState +): SwiftUIPresenter { + val navigator = SwiftUINavigator() + return SwiftUIPresenter(navigator, presenterOf { body(navigator) }) +} diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenterProtocol.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenterProtocol.kt new file mode 100644 index 000000000..3a994e0b9 --- /dev/null +++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenterProtocol.kt @@ -0,0 +1,10 @@ +package com.slack.circuit.swiftui + +import com.slack.circuit.swiftui.objc.CircuitSwiftUINavigatorProtocol + +public sealed class SwiftUIPresenterProtocol { + public abstract val state: Any + public abstract var navigator: CircuitSwiftUINavigatorProtocol? + public abstract fun setStateWillChangeListener(listener: () -> Unit) + public abstract fun cancel() +} diff --git a/circuit-swiftui/src/nativeInterop/cinterop/CircuitRuntimeObjC.def b/circuit-swiftui/src/nativeInterop/cinterop/CircuitRuntimeObjC.def new file mode 100644 index 000000000..4cf6588f0 --- /dev/null +++ b/circuit-swiftui/src/nativeInterop/cinterop/CircuitRuntimeObjC.def @@ -0,0 +1,3 @@ +language = Objective-C +package = com.slack.circuit.swiftui.objc +headers = CircuitSwiftUINavigator.h diff --git a/gradle.properties b/gradle.properties index a690f7470..49fd5e1ed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -42,6 +42,8 @@ kotlin.mpp.stability.nowarn=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.mpp.androidGradlePluginCompatibility.nowarn=true +kotlin.mpp.enableCInteropCommonization=true + # Enable Gradle configuration caching # TODO disabled because of compose/kotlin multiplatform org.gradle.configuration-cache=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b176fa49e..395275906 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,8 +11,8 @@ coil = "2.4.0" compose-animation = "1.4.3" # Pre-release versions for testing Kotlin previews can be found here # https://androidx.dev/storage/compose-compiler/repository -compose-compiler = "1.4.7" -composeCompilerKotlinVersion = "1.8.21" +compose-compiler = "1.4.6" +composeCompilerKotlinVersion = "1.8.20" compose-foundation = "1.4.3" compose-material = "1.4.3" compose-material3 = "1.1.0" @@ -28,10 +28,10 @@ detekt = "1.23.0" dokka = "1.8.10" eithernet = "1.4.0" jdk = "19" -kotlin = "1.8.21" +kotlin = "1.8.20" kotlinpoet = "1.13.2" kotlinx-coroutines = "1.7.1" -ksp = "1.8.21-1.0.11" +ksp = "1.8.20-1.0.11" ktfmt = "0.44" leakcanary = "2.11" material = "1.6.1" diff --git a/samples/counter/apps/Counter.xcodeproj/project.pbxproj b/samples/counter/apps/Counter.xcodeproj/project.pbxproj index 8d95f2292..4e49d06df 100644 --- a/samples/counter/apps/Counter.xcodeproj/project.pbxproj +++ b/samples/counter/apps/Counter.xcodeproj/project.pbxproj @@ -3,15 +3,18 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; + 1DC5EA872A22732C00037003 /* CircuitSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 1DC5EA862A22732C00037003 /* CircuitSwiftUI */; }; + 1DC5EA892A22785A00037003 /* PrimeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC5EA882A22785A00037003 /* PrimeView.swift */; }; + 1DE43CE32A210A1400EB0E36 /* Circuit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43CE22A210A1400EB0E36 /* Circuit.swift */; }; 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; 602DC854301CF43C8E7B0F6D /* Pods_Counter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D2DFDA1A8E1896075A1701 /* Pods_Counter.framework */; }; - 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; + 7555FF83242A565900829871 /* CounterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* CounterView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -30,11 +33,14 @@ /* Begin PBXFileReference section */ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 1DC5EA882A22785A00037003 /* PrimeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimeView.swift; sourceTree = ""; }; + 1DE43CE22A210A1400EB0E36 /* Circuit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Circuit.swift; sourceTree = ""; }; + 1DE43D322A2272A300EB0E36 /* circuit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = circuit; path = ../../..; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; 27D2DFDA1A8E1896075A1701 /* Pods_Counter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Counter.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35C96B8C8A190485ECDD3046 /* Pods-Counter.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Counter.debug.xcconfig"; path = "Target Support Files/Pods-Counter/Pods-Counter.debug.xcconfig"; sourceTree = ""; }; 7555FF7B242A565900829871 /* Counter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Counter.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 7555FF82242A565900829871 /* CounterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E90321D9A8A0D137411EFA43 /* Pods-Counter.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Counter.release.xcconfig"; path = "Target Support Files/Pods-Counter/Pods-Counter.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -44,6 +50,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1DC5EA872A22732C00037003 /* CircuitSwiftUI in Frameworks */, 602DC854301CF43C8E7B0F6D /* Pods_Counter.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -68,9 +75,18 @@ path = "Preview Content"; sourceTree = ""; }; + 1DE43CE02A2109E500EB0E36 /* Packages */ = { + isa = PBXGroup; + children = ( + 1DE43D322A2272A300EB0E36 /* circuit */, + ); + name = Packages; + sourceTree = ""; + }; 7555FF72242A565900829871 = { isa = PBXGroup; children = ( + 1DE43CE02A2109E500EB0E36 /* Packages */, 7555FF7D242A565900829871 /* Counter */, 7555FF7C242A565900829871 /* Products */, 7555FFB0242A642200829871 /* Frameworks */, @@ -90,10 +106,12 @@ isa = PBXGroup; children = ( 058557BA273AAA24004C7B11 /* Assets.xcassets */, - 7555FF82242A565900829871 /* ContentView.swift */, + 7555FF82242A565900829871 /* CounterView.swift */, 7555FF8C242A565B00829871 /* Info.plist */, 2152FB032600AC8F00CF470E /* iOSApp.swift */, 058557D7273AAEEB004C7B11 /* Preview Content */, + 1DE43CE22A210A1400EB0E36 /* Circuit.swift */, + 1DC5EA882A22785A00037003 /* PrimeView.swift */, ); path = Counter; sourceTree = ""; @@ -125,6 +143,9 @@ dependencies = ( ); name = Counter; + packageProductDependencies = ( + 1DC5EA862A22732C00037003 /* CircuitSwiftUI */, + ); productName = Counter; productReference = 7555FF7B242A565900829871 /* Counter.app */; productType = "com.apple.product-type.application"; @@ -221,8 +242,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1DE43CE32A210A1400EB0E36 /* Circuit.swift in Sources */, 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, - 7555FF83242A565900829871 /* ContentView.swift in Sources */, + 1DC5EA892A22785A00037003 /* PrimeView.swift in Sources */, + 7555FF83242A565900829871 /* CounterView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -358,6 +381,7 @@ "$(SRCROOT)/../build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = Counter/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -387,6 +411,7 @@ "$(SRCROOT)/../build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = Counter/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -425,6 +450,13 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + 1DC5EA862A22732C00037003 /* CircuitSwiftUI */ = { + isa = XCSwiftPackageProductDependency; + productName = CircuitSwiftUI; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 7555FF73242A565900829871 /* Project object */; } diff --git a/samples/counter/apps/Counter/Circuit.swift b/samples/counter/apps/Counter/Circuit.swift new file mode 100644 index 000000000..04e71b224 --- /dev/null +++ b/samples/counter/apps/Counter/Circuit.swift @@ -0,0 +1,12 @@ +// +// Circuit.swift +// Counter +// +// Created by Rick Clephas on 26/05/2023. +// Copyright © 2023 orgName. All rights reserved. +// + +import counter +import CircuitRuntime + +extension Circuit_swiftuiSwiftUIPresenterProtocol: CircuitPresenter { } diff --git a/samples/counter/apps/Counter/ContentView.swift b/samples/counter/apps/Counter/ContentView.swift deleted file mode 100644 index cbb90840c..000000000 --- a/samples/counter/apps/Counter/ContentView.swift +++ /dev/null @@ -1,63 +0,0 @@ -import SwiftUI -import counter - -struct ContentView: View { - @ObservedObject var presenter = SwiftCounterPresenter() - - var body: some View { - NavigationView { - VStack(alignment: .center) { - Text("Count \(presenter.state?.count ?? 0)") - .font(.system(size: 36)) - HStack(spacing: 10) { - Button(action: { - presenter.state?.eventSink(CounterScreenEventDecrement.shared) - }) { - Text("-") - .font(.system(size: 36, weight: .black, design: .monospaced)) - } - .padding() - .foregroundColor(.white) - .background(Color.blue) - Button(action: { - presenter.state?.eventSink(CounterScreenEventIncrement.shared) - }) { - Text("+") - .font(.system(size: 36, weight: .black, design: .monospaced)) - } - .padding() - .foregroundColor(.white) - .background(Color.blue) - } - } - .navigationBarTitle("Counter") - } - } -} - -// TODO we hide all this behind the Circuit UI interface somehow? Then we can pass it state only -@MainActor -class SwiftCounterPresenter: BasePresenter { - init() { - // TODO why can't swift infer these generics? - super.init( - delegate: SwiftSupportKt.asSwiftPresenter(SwiftSupportKt.doNewCounterPresenter()) - as! SwiftPresenter) - } -} - -class BasePresenter: ObservableObject { - @Published var state: T? = nil - - init(delegate: SwiftPresenter) { - delegate.subscribe { state in - self.state = state - } - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/samples/counter/apps/Counter/CounterView.swift b/samples/counter/apps/Counter/CounterView.swift new file mode 100644 index 000000000..3657f6ec7 --- /dev/null +++ b/samples/counter/apps/Counter/CounterView.swift @@ -0,0 +1,44 @@ +import SwiftUI +import counter + +struct CounterView: View { + + var state: CounterScreenState + + var body: some View { + VStack(alignment: .center) { + Text("Count \(state.count)") + .font(.system(size: 36)) + HStack(spacing: 10) { + Button(action: { + state.eventSink(CounterScreenEventDecrement.shared) + }) { + Text("-") + .font(.system(size: 36, weight: .black, design: .monospaced)) + } + .padding() + .foregroundColor(.white) + .background(Color.blue) + Button(action: { + state.eventSink(CounterScreenEventIncrement.shared) + }) { + Text("+") + .font(.system(size: 36, weight: .black, design: .monospaced)) + } + .padding() + .foregroundColor(.white) + .background(Color.blue) + } + Button("Prime?") { + state.eventSink(CounterScreenEventGoTo(screen: IosPrimeScreen(number: state.count))) + }.padding() + } + .navigationBarTitle("Counter") + } +} + +struct CounterView_Previews: PreviewProvider { + static var previews: some View { + CounterView(state: CounterScreenState(count: 0, eventSink: { _ in })) + } +} diff --git a/samples/counter/apps/Counter/PrimeView.swift b/samples/counter/apps/Counter/PrimeView.swift new file mode 100644 index 000000000..283a4e31c --- /dev/null +++ b/samples/counter/apps/Counter/PrimeView.swift @@ -0,0 +1,35 @@ +// +// PrimeView.swift +// Counter +// +// Created by Rick Clephas on 27/05/2023. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI +import counter + +struct PrimeView: View { + + var state: PrimeScreenState + + var body: some View { + Text("\(state.number)") + .font(.system(size: 36)) + .padding() + if (state.isPrime) { + Text("\(state.number) is a prime number!") + } else { + Text("\(state.number) is not a prime number.") + } + Button("Back") { + state.eventSink(PrimeScreenEventPop()) + }.padding() + } +} + +struct PrimeView_Previews: PreviewProvider { + static var previews: some View { + PrimeView(state: PrimeScreenState(number: 0, isPrime: false, eventSink: { _ in })) + } +} diff --git a/samples/counter/apps/Counter/iOSApp.swift b/samples/counter/apps/Counter/iOSApp.swift index 0648e8602..ddfb5d8b0 100644 --- a/samples/counter/apps/Counter/iOSApp.swift +++ b/samples/counter/apps/Counter/iOSApp.swift @@ -1,10 +1,25 @@ import SwiftUI +import counter +import CircuitRuntime +import CircuitSwiftUI @main struct iOSApp: App { + + @StateObject private var navigator = CircuitNavigator(IosCounterScreen.shared) + var body: some Scene { WindowGroup { - ContentView() - } + CircuitNavigationStack(navigator) { screen in + switch screen { + case let screen as IosCounterScreen: + CircuitView(screen.presenter(), CounterView.init) + case let screen as IosPrimeScreen: + CircuitView(screen.presenter(), PrimeView.init) + default: + fatalError("Unsupported screen: \(screen)") + } + } + } } -} \ No newline at end of file +} diff --git a/samples/counter/build.gradle.kts b/samples/counter/build.gradle.kts index 356ea5217..2ec765643 100644 --- a/samples/counter/build.gradle.kts +++ b/samples/counter/build.gradle.kts @@ -45,7 +45,11 @@ kotlin { } } maybeCreate("commonTest").apply { dependencies { implementation(libs.kotlin.test) } } - val iosMain by sourceSets.getting + val iosMain by sourceSets.getting { + dependencies { + implementation(projects.circuitSwiftui) + } + } val iosSimulatorArm64Main by sourceSets.getting // Set up dependencies between the source sets iosSimulatorArm64Main.dependsOn(iosMain) diff --git a/samples/counter/src/commonMain/kotlin/com/slack/circuit/sample/counter/SwiftSupport.kt b/samples/counter/src/commonMain/kotlin/com/slack/circuit/sample/counter/SwiftSupport.kt deleted file mode 100644 index b3103b39e..000000000 --- a/samples/counter/src/commonMain/kotlin/com/slack/circuit/sample/counter/SwiftSupport.kt +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2023 Slack Technologies, LLC -// SPDX-License-Identifier: Apache-2.0 -package com.slack.circuit.sample.counter - -import app.cash.molecule.RecompositionClock -import app.cash.molecule.launchMolecule -import com.slack.circuit.runtime.CircuitUiState -import com.slack.circuit.runtime.Navigator -import com.slack.circuit.runtime.presenter.Presenter -import com.slack.circuit.runtime.presenter.presenterOf -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.IO -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch - -fun newCounterPresenter() = presenterOf { CounterPresenter(Navigator.NoOp) } - -fun Presenter.asSwiftPresenter(): SwiftPresenter { - return SwiftPresenter(this) -} - -// Adapted from the KotlinConf app -// https://github.com/JetBrains/kotlinconf-app/blob/642404f3454d384be966c34d6b254b195e8d2892/shared/src/commonMain/kotlin/org/jetbrains/kotlinconf/utils/Coroutines.kt#L6 -// No interface because interfaces don't support generics in Kotlin/Native. -// TODO let's try to generify this pattern somehow. -class SwiftPresenter -internal constructor( - private val delegate: Presenter, -) { - // TODO ew globalscope - @OptIn(DelicateCoroutinesApi::class) - fun subscribe(block: (UiState) -> Unit): Job { - return GlobalScope.launch(Dispatchers.IO) { - launchMolecule(RecompositionClock.Immediate) { delegate.present() }.collect { block(it) } - } - } -} diff --git a/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/Screens.kt b/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/Screens.kt new file mode 100644 index 000000000..203787478 --- /dev/null +++ b/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/Screens.kt @@ -0,0 +1,14 @@ +package com.slack.circuit.sample.counter + +import com.slack.circuit.swiftui.SwiftUIPresenter +import com.slack.circuit.swiftui.swiftUIPresenterOf + +object IosCounterScreen: CounterScreen, SwiftUIPresenter.Factory { + override fun presenter() = swiftUIPresenterOf { CounterPresenter(it) } +} + +data class IosPrimeScreen( + override val number: Int +): PrimeScreen, SwiftUIPresenter.Factory { + override fun presenter() = swiftUIPresenterOf { PrimePresenter(it, number) } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7ba18abdb..df25d70f1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -199,6 +199,7 @@ include( ":circuit-runtime", ":circuit-runtime-presenter", ":circuit-runtime-ui", + ":circuit-swiftui", ":circuit-test", ":samples:counter", ":samples:counter:apps",