From a9a4067b410d8195085a5094b03a35f2877dac50 Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 4 Feb 2026 12:17:19 -0800 Subject: [PATCH 1/8] initial commit of new example app OneSignalSwiftUIExample --- .../contents.xcworkspacedata | 3 + .../project.pbxproj | 340 ++++++++++++++++++ .../OneSignalSwiftUIExample.xcscheme | 78 ++++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++ .../Assets.xcassets/Contents.json | 6 + .../OneSignalSwiftUIExample/ContentView.swift | 24 ++ .../OneSignalSwiftUIExampleApp.swift | 17 + 8 files changed, 514 insertions(+) create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/xcshareddata/xcschemes/OneSignalSwiftUIExample.xcscheme create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/Contents.json create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ContentView.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/OneSignalSwiftUIExampleApp.swift diff --git a/iOS_SDK/OneSignalSDK.xcworkspace/contents.xcworkspacedata b/iOS_SDK/OneSignalSDK.xcworkspace/contents.xcworkspacedata index 9979cb733..9e7617845 100644 --- a/iOS_SDK/OneSignalSDK.xcworkspace/contents.xcworkspacedata +++ b/iOS_SDK/OneSignalSDK.xcworkspace/contents.xcworkspacedata @@ -1,6 +1,9 @@ + + diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj new file mode 100644 index 000000000..83222dfb2 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj @@ -0,0 +1,340 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 3C25C0552F33E031005E5E9A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3C25C0512F33E031005E5E9A /* Assets.xcassets */; }; + 3C25C0562F33E031005E5E9A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C25C0522F33E031005E5E9A /* ContentView.swift */; }; + 3C25C0572F33E031005E5E9A /* OneSignalSwiftUIExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C25C0532F33E031005E5E9A /* OneSignalSwiftUIExampleApp.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 3C25C0432F33E016005E5E9A /* OneSignalSwiftUIExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OneSignalSwiftUIExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 3C25C0512F33E031005E5E9A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 3C25C0522F33E031005E5E9A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 3C25C0532F33E031005E5E9A /* OneSignalSwiftUIExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalSwiftUIExampleApp.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3C25C0402F33E016005E5E9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3C25C03A2F33E016005E5E9A = { + isa = PBXGroup; + children = ( + 3C25C0542F33E031005E5E9A /* OneSignalSwiftUIExample */, + 3C25C0442F33E016005E5E9A /* Products */, + ); + sourceTree = ""; + }; + 3C25C0442F33E016005E5E9A /* Products */ = { + isa = PBXGroup; + children = ( + 3C25C0432F33E016005E5E9A /* OneSignalSwiftUIExample.app */, + ); + name = Products; + sourceTree = ""; + }; + 3C25C0542F33E031005E5E9A /* OneSignalSwiftUIExample */ = { + isa = PBXGroup; + children = ( + 3C25C0512F33E031005E5E9A /* Assets.xcassets */, + 3C25C0522F33E031005E5E9A /* ContentView.swift */, + 3C25C0532F33E031005E5E9A /* OneSignalSwiftUIExampleApp.swift */, + ); + path = OneSignalSwiftUIExample; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 3C25C0422F33E016005E5E9A /* OneSignalSwiftUIExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3C25C04E2F33E019005E5E9A /* Build configuration list for PBXNativeTarget "OneSignalSwiftUIExample" */; + buildPhases = ( + 3C25C03F2F33E016005E5E9A /* Sources */, + 3C25C0402F33E016005E5E9A /* Frameworks */, + 3C25C0412F33E016005E5E9A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OneSignalSwiftUIExample; + packageProductDependencies = ( + ); + productName = OneSignalSwiftUIExample; + productReference = 3C25C0432F33E016005E5E9A /* OneSignalSwiftUIExample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 3C25C03B2F33E016005E5E9A /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + 3C25C0422F33E016005E5E9A = { + CreatedOnToolsVersion = 16.4; + }; + }; + }; + buildConfigurationList = 3C25C03E2F33E016005E5E9A /* Build configuration list for PBXProject "OneSignalSwiftUIExample" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 3C25C03A2F33E016005E5E9A; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 3C25C0442F33E016005E5E9A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3C25C0422F33E016005E5E9A /* OneSignalSwiftUIExample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 3C25C0412F33E016005E5E9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3C25C0552F33E031005E5E9A /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3C25C03F2F33E016005E5E9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3C25C0562F33E031005E5E9A /* ContentView.swift in Sources */, + 3C25C0572F33E031005E5E9A /* OneSignalSwiftUIExampleApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 3C25C04C2F33E019005E5E9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 99SW8E36CT; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 3C25C04D2F33E019005E5E9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 99SW8E36CT; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 3C25C04F2F33E019005E5E9A /* 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_TEAM = 99SW8E36CT; + 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.onesignal.OneSignalSwiftUIExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3C25C0502F33E019005E5E9A /* 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_TEAM = 99SW8E36CT; + 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.onesignal.OneSignalSwiftUIExample; + 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 */ + 3C25C03E2F33E016005E5E9A /* Build configuration list for PBXProject "OneSignalSwiftUIExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3C25C04C2F33E019005E5E9A /* Debug */, + 3C25C04D2F33E019005E5E9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3C25C04E2F33E019005E5E9A /* Build configuration list for PBXNativeTarget "OneSignalSwiftUIExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3C25C04F2F33E019005E5E9A /* Debug */, + 3C25C0502F33E019005E5E9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 3C25C03B2F33E016005E5E9A /* Project object */; +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/xcshareddata/xcschemes/OneSignalSwiftUIExample.xcscheme b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/xcshareddata/xcschemes/OneSignalSwiftUIExample.xcscheme new file mode 100644 index 000000000..8dcded243 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/xcshareddata/xcschemes/OneSignalSwiftUIExample.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..230588010 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/Contents.json b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ContentView.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ContentView.swift new file mode 100644 index 000000000..7be85958c --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// OneSignalSwiftUIExample +// +// Created by Nan Li on 2/4/26. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/OneSignalSwiftUIExampleApp.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/OneSignalSwiftUIExampleApp.swift new file mode 100644 index 000000000..f2c416246 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/OneSignalSwiftUIExampleApp.swift @@ -0,0 +1,17 @@ +// +// OneSignalSwiftUIExampleApp.swift +// OneSignalSwiftUIExample +// +// Created by Nan Li on 2/4/26. +// + +import SwiftUI + +@main +struct OneSignalSwiftUIExampleApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} From d8e08c63d5fda5a9a68af7aa6879b4ba2c8ca635 Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 4 Feb 2026 14:40:01 -0800 Subject: [PATCH 2/8] Generate app using screenshots * Do any small tweaking as needed * Add notification examples --- .../OneSignalSwiftUIExample.entitlements | 8 + .../App/OneSignalSwiftUIExampleApp.swift | 137 ++++++ .../AccentColor.colorset/Contents.json | 38 ++ .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + .../OneSignalSwiftUIExample/Info.plist | 58 +++ .../Models/AppModels.swift | 145 ++++++ .../Services/OneSignalService.swift | 236 ++++++++++ .../ViewModels/OneSignalViewModel.swift | 350 ++++++++++++++ .../Views/Components/AddItemSheet.swift | 126 ++++++ .../Views/Components/KeyValueRow.swift | 158 +++++++ .../Views/Components/NotificationGrid.swift | 137 ++++++ .../Views/Components/ToastView.swift | 80 ++++ .../Views/ContentView.swift | 75 +++ .../Views/Sections/AppInfoSection.swift | 69 +++ .../Views/Sections/LocationSection.swift | 69 +++ .../Views/Sections/MessagingSection.swift | 179 ++++++++ .../Views/Sections/NotificationSection.swift | 60 +++ .../Views/Sections/SubscriptionSection.swift | 116 +++++ .../Views/Sections/TagsSection.swift | 67 +++ .../Views/Sections/UserSection.swift | 103 +++++ OneSignalSwiftUIExample/README.md | 136 ++++++ .../OneSignalSwiftUIExample.entitlements | 8 + .../project.pbxproj | 426 +++++++++++++----- .../OneSignalSwiftUIExample.xcscheme | 27 +- .../App/OneSignalSwiftUIExampleApp.swift | 135 ++++++ .../AccentColor.colorset/Contents.json | 27 ++ .../AppIcon.appiconset/Contents.json | 22 - .../OneSignalSwiftUIExample/ContentView.swift | 24 - .../OneSignalSwiftUIExample/Info.plist | 56 +++ .../Models/AppModels.swift | 146 ++++++ .../OneSignalSwiftUIExampleApp.swift | 17 - .../Services/NotificationSender.swift | 368 +++++++++++++++ .../Services/OneSignalService.swift | 232 ++++++++++ .../ViewModels/OneSignalViewModel.swift | 357 +++++++++++++++ .../Views/Components/AddItemSheet.swift | 175 +++++++ .../Views/Components/KeyValueRow.swift | 291 ++++++++++++ .../Views/Components/NotificationGrid.swift | 138 ++++++ .../Views/Components/ToastView.swift | 80 ++++ .../Views/ContentView.swift | 98 ++++ .../Views/Sections/AppInfoSection.swift | 55 +++ .../Views/Sections/LocationSection.swift | 62 +++ .../Views/Sections/MessagingSection.swift | 199 ++++++++ .../Views/Sections/NotificationSection.swift | 58 +++ .../Views/Sections/SubscriptionSection.swift | 130 ++++++ .../Views/Sections/TagsSection.swift | 66 +++ .../Views/Sections/UserSection.swift | 89 ++++ iOS_SDK/OneSignalSwiftUIExample/README.md | 153 +++++++ 48 files changed, 5624 insertions(+), 181 deletions(-) create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/Contents.json create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift create mode 100644 OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift create mode 100644 OneSignalSwiftUIExample/README.md create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift delete mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ContentView.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift delete mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/OneSignalSwiftUIExampleApp.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/NotificationSender.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/README.md diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements b/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements new file mode 100644 index 000000000..903def2af --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift new file mode 100644 index 000000000..64b465526 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift @@ -0,0 +1,137 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI +import OneSignalFramework +import OneSignalInAppMessages +import OneSignalLocation + +@main +struct OneSignalSwiftUIExampleApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @StateObject private var viewModel = OneSignalViewModel() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(viewModel) + } + } +} + +// MARK: - App Delegate + +class AppDelegate: NSObject, UIApplicationDelegate { + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + // Initialize OneSignal + OneSignalService.shared.initialize(launchOptions: launchOptions) + + // Set up notification lifecycle listeners + setupNotificationListeners() + + // Set up in-app message listeners + setupInAppMessageListeners() + + return true + } + + private func setupNotificationListeners() { + // Foreground notification display + OneSignal.Notifications.addForegroundLifecycleListener(NotificationLifecycleHandler.shared) + + // Notification click handling + OneSignal.Notifications.addClickListener(NotificationClickHandler.shared) + } + + private func setupInAppMessageListeners() { + // In-app message lifecycle + OneSignal.InAppMessages.addLifecycleListener(InAppMessageLifecycleHandler.shared) + + // In-app message click handling + OneSignal.InAppMessages.addClickListener(InAppMessageClickHandler.shared) + + // Start with IAM paused + OneSignal.InAppMessages.paused = true + } +} + +// MARK: - Notification Handlers + +class NotificationLifecycleHandler: NSObject, OSNotificationLifecycleListener { + static let shared = NotificationLifecycleHandler() + + func onWillDisplay(event: OSNotificationWillDisplayEvent) { + print("[OneSignal] Notification will display: \(event.notification.title ?? "No title")") + // Optionally modify display behavior + // event.preventDefault() // Prevent automatic display + // event.notification.display() // Manually display later + } +} + +class NotificationClickHandler: NSObject, OSNotificationClickListener { + static let shared = NotificationClickHandler() + + func onClick(event: OSNotificationClickEvent) { + print("[OneSignal] Notification clicked: \(event.notification.title ?? "No title")") + // Handle notification click - navigate to specific screen, etc. + } +} + +// MARK: - In-App Message Handlers + +class InAppMessageLifecycleHandler: NSObject, OSInAppMessageLifecycleListener { + static let shared = InAppMessageLifecycleHandler() + + func onWillDisplay(event: OSInAppMessageWillDisplayEvent) { + print("[OneSignal] IAM will display: \(event.message.messageId)") + } + + func onDidDisplay(event: OSInAppMessageDidDisplayEvent) { + print("[OneSignal] IAM did display: \(event.message.messageId)") + } + + func onWillDismiss(event: OSInAppMessageWillDismissEvent) { + print("[OneSignal] IAM will dismiss: \(event.message.messageId)") + } + + func onDidDismiss(event: OSInAppMessageDidDismissEvent) { + print("[OneSignal] IAM did dismiss: \(event.message.messageId)") + } +} + +class InAppMessageClickHandler: NSObject, OSInAppMessageClickListener { + static let shared = InAppMessageClickHandler() + + func onClick(event: OSInAppMessageClickEvent) { + print("[OneSignal] IAM clicked: \(event.result.actionId ?? "No action ID")") + // Handle IAM click - navigate, track event, etc. + } +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..2c54006ed --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4D", + "green" : "0x4B", + "red" : "0xE5" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x6D", + "green" : "0x6B", + "red" : "0xF5" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..13613e3ee --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/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/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/Contents.json b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist new file mode 100644 index 000000000..aba8d06a4 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist @@ -0,0 +1,58 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + OneSignal SwiftUI + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSLocationWhenInUseUsageDescription + This app uses your location to personalize notifications and content. + NSLocationAlwaysAndWhenInUseUsageDescription + This app uses your location to personalize notifications and content even when the app is in the background. + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UIBackgroundModes + + remote-notification + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift new file mode 100644 index 000000000..6cef48660 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift @@ -0,0 +1,145 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation + +// MARK: - Key-Value Item + +/// A generic key-value pair used for aliases, tags, and triggers +struct KeyValueItem: Identifiable, Equatable { + let id = UUID() + let key: String + let value: String +} + +// MARK: - Notification Type + +/// Types of test push notifications that can be sent +enum NotificationType: String, CaseIterable, Identifiable { + case general = "General" + case greetings = "Greetings" + case promotions = "Promotions" + case breakingNews = "Breaking News" + case abandonedCart = "Abandoned Cart" + case newPost = "New Post" + case reEngagement = "Re-Engagement" + case rating = "Rating" + + var id: String { rawValue } + + var iconName: String { + switch self { + case .general: return "bell.fill" + case .greetings: return "hand.wave.fill" + case .promotions: return "tag.fill" + case .breakingNews: return "newspaper.fill" + case .abandonedCart: return "cart.fill" + case .newPost: return "photo.fill" + case .reEngagement: return "hand.tap.fill" + case .rating: return "star.fill" + } + } +} + +// MARK: - In-App Message Type + +/// Types of in-app messages that can be displayed +enum InAppMessageType: String, CaseIterable, Identifiable { + case topBanner = "Top Banner" + case bottomBanner = "Bottom Banner" + case centerModal = "Center Modal" + case fullScreen = "Full Screen" + + var id: String { rawValue } + + var iconName: String { + switch self { + case .topBanner: return "rectangle.topthird.inset.filled" + case .bottomBanner: return "rectangle.bottomthird.inset.filled" + case .centerModal: return "rectangle.center.inset.filled" + case .fullScreen: return "rectangle.inset.filled" + } + } +} + +// MARK: - Add Item Type + +/// Types of items that can be added via the add sheet +enum AddItemType { + case alias + case email + case sms + case tag + case trigger + case externalUserId + + var title: String { + switch self { + case .alias: return "Add Alias" + case .email: return "Add Email" + case .sms: return "Add SMS" + case .tag: return "Add Tag" + case .trigger: return "Add Trigger" + case .externalUserId: return "Login User" + } + } + + var requiresKeyValue: Bool { + switch self { + case .alias, .tag, .trigger: return true + case .email, .sms, .externalUserId: return false + } + } + + var keyPlaceholder: String { + switch self { + case .alias: return "Alias Label" + case .tag: return "Tag Key" + case .trigger: return "Trigger Key" + default: return "Key" + } + } + + var valuePlaceholder: String { + switch self { + case .alias: return "Alias ID" + case .email: return "email@example.com" + case .sms: return "+1234567890" + case .tag: return "Tag Value" + case .trigger: return "Trigger Value" + case .externalUserId: return "External User ID" + } + } + + var keyboardType: UIKeyboardType { + switch self { + case .email: return .emailAddress + case .sms: return .phonePad + default: return .default + } + } +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift new file mode 100644 index 000000000..78e49e4dd --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift @@ -0,0 +1,236 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation +import OneSignalFramework +import OneSignalInAppMessages +import OneSignalLocation + +/// Service layer that wraps OneSignal SDK calls +final class OneSignalService { + + // MARK: - Singleton + + static let shared = OneSignalService() + + private init() {} + + // MARK: - App ID + + private let appIdKey = "OneSignalAppId" + private let defaultAppId = "77e32082-ea27-42e3-a898-c72e141824ef" + + var appId: String { + get { + UserDefaults.standard.string(forKey: appIdKey) ?? defaultAppId + } + set { + UserDefaults.standard.set(newValue, forKey: appIdKey) + } + } + + // MARK: - Initialization + + func initialize(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { + OneSignal.Debug.setLogLevel(.LL_VERBOSE) + OneSignal.initialize(appId, withLaunchOptions: launchOptions) + } + + // MARK: - Consent + + var consentRequired: Bool { + get { OneSignal.privacyConsentRequired } + set { OneSignal.setConsentRequired(newValue) } + } + + var consentGiven: Bool { + get { OneSignal.privacyConsentGiven } + set { OneSignal.setConsentGiven(newValue) } + } + + func revokeConsent() { + OneSignal.setConsentGiven(false) + } + + // MARK: - User Management + + func login(externalId: String) { + OneSignal.login(externalId) + } + + func logout() { + OneSignal.logout() + } + + // MARK: - Aliases + + func addAlias(label: String, id: String) { + OneSignal.User.addAlias(label, id: id) + } + + func removeAlias(_ label: String) { + OneSignal.User.removeAlias(label) + } + + // MARK: - Push Subscription + + var pushSubscriptionId: String? { + OneSignal.User.pushSubscription.id + } + + var isPushEnabled: Bool { + OneSignal.User.pushSubscription.optedIn + } + + func optInPush() { + OneSignal.User.pushSubscription.optIn() + } + + func optOutPush() { + OneSignal.User.pushSubscription.optOut() + } + + func requestPushPermission(completion: @escaping (Bool) -> Void) { + OneSignal.Notifications.requestPermission({ accepted in + completion(accepted) + }, fallbackToSettings: true) + } + + // MARK: - Email + + func addEmail(_ email: String) { + OneSignal.User.addEmail(email) + } + + func removeEmail(_ email: String) { + OneSignal.User.removeEmail(email) + } + + // MARK: - SMS + + func addSms(_ number: String) { + OneSignal.User.addSms(number) + } + + func removeSms(_ number: String) { + OneSignal.User.removeSms(number) + } + + // MARK: - Tags + + func addTag(key: String, value: String) { + OneSignal.User.addTag(key: key, value: value) + } + + func removeTag(_ key: String) { + OneSignal.User.removeTag(key) + } + + func getTags() -> [String: String] { + OneSignal.User.getTags() + } + + // MARK: - Outcomes + + func sendOutcome(_ name: String) { + OneSignal.Session.addOutcome(name) + } + + func sendOutcome(_ name: String, value: NSNumber) { + OneSignal.Session.addOutcome(name, value: value) + } + + func sendUniqueOutcome(_ name: String) { + OneSignal.Session.addUniqueOutcome(name) + } + + // MARK: - In-App Messages + + var isInAppMessagesPaused: Bool { + get { OneSignal.InAppMessages.paused } + set { OneSignal.InAppMessages.paused = newValue } + } + + func addTrigger(key: String, value: String) { + OneSignal.InAppMessages.addTrigger(key, withValue: value) + } + + func removeTrigger(_ key: String) { + OneSignal.InAppMessages.removeTrigger(key) + } + + // MARK: - Location + + var isLocationShared: Bool { + get { OneSignal.Location.isShared } + set { OneSignal.Location.isShared = newValue } + } + + func requestLocationPermission() { + OneSignal.Location.requestPermission() + } + + // MARK: - Notifications + + func clearAllNotifications() { + OneSignal.Notifications.clearAll() + } + + var hasNotificationPermission: Bool { + OneSignal.Notifications.permission + } + + // MARK: - Observers + + func addPushSubscriptionObserver(_ observer: OSPushSubscriptionObserver) { + OneSignal.User.pushSubscription.addObserver(observer) + } + + func addUserObserver(_ observer: OSUserStateObserver) { + OneSignal.User.addObserver(observer) + } + + func addPermissionObserver(_ observer: OSNotificationPermissionObserver) { + OneSignal.Notifications.addPermissionObserver(observer) + } + + func addNotificationClickListener(_ listener: OSNotificationClickListener) { + OneSignal.Notifications.addClickListener(listener) + } + + func addNotificationLifecycleListener(_ listener: OSNotificationLifecycleListener) { + OneSignal.Notifications.addForegroundLifecycleListener(listener) + } + + func addInAppMessageClickListener(_ listener: OSInAppMessageClickListener) { + OneSignal.InAppMessages.addClickListener(listener) + } + + func addInAppMessageLifecycleListener(_ listener: OSInAppMessageLifecycleListener) { + OneSignal.InAppMessages.addLifecycleListener(listener) + } +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift new file mode 100644 index 000000000..9b8cc0227 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift @@ -0,0 +1,350 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation +import Combine +import OneSignalFramework +import OneSignalInAppMessages +import OneSignalLocation + +/// Main ViewModel managing all OneSignal SDK state and interactions +@MainActor +final class OneSignalViewModel: ObservableObject { + + // MARK: - Published Properties + + // App Info + @Published var appId: String + + // User + @Published var externalUserId: String? + @Published var aliases: [KeyValueItem] = [] + + // Push Subscription + @Published var pushSubscriptionId: String? + @Published var isPushEnabled: Bool = false + + // Email & SMS + @Published var emails: [String] = [] + @Published var smsNumbers: [String] = [] + + // Tags + @Published var tags: [KeyValueItem] = [] + + // In-App Messaging + @Published var isInAppMessagesPaused: Bool = true + @Published var triggers: [KeyValueItem] = [] + + // Location + @Published var isLocationShared: Bool = false + + // UI State + @Published var showingAddSheet: Bool = false + @Published var addItemType: AddItemType = .email + @Published var toastMessage: String? + + // MARK: - Private Properties + + private let service: OneSignalService + private var observers = Observers() + + // MARK: - Initialization + + init(service: OneSignalService = .shared) { + self.service = service + self.appId = service.appId + + // Initial state sync + refreshState() + + // Set up observers + setupObservers() + } + + // MARK: - State Management + + func refreshState() { + pushSubscriptionId = service.pushSubscriptionId + isPushEnabled = service.isPushEnabled + isInAppMessagesPaused = service.isInAppMessagesPaused + isLocationShared = service.isLocationShared + + // Sync tags from SDK + let sdkTags = service.getTags() + tags = sdkTags.map { KeyValueItem(key: $0.key, value: $0.value) } + } + + // MARK: - Consent + + func revokeConsent() { + service.revokeConsent() + showToast("Consent revoked") + } + + // MARK: - User Management + + func login(externalId: String) { + service.login(externalId: externalId) + externalUserId = externalId + showToast("Logged in as \(externalId)") + } + + func logout() { + service.logout() + externalUserId = nil + aliases.removeAll() + emails.removeAll() + smsNumbers.removeAll() + tags.removeAll() + triggers.removeAll() + showToast("Logged out") + } + + // MARK: - Aliases + + func addAlias(label: String, id: String) { + service.addAlias(label: label, id: id) + aliases.append(KeyValueItem(key: label, value: id)) + showToast("Alias added") + } + + func removeAlias(_ item: KeyValueItem) { + service.removeAlias(item.key) + aliases.removeAll { $0.id == item.id } + showToast("Alias removed") + } + + // MARK: - Push Subscription + + func togglePushEnabled() { + if isPushEnabled { + service.optOutPush() + isPushEnabled = false + showToast("Push disabled") + } else { + service.optInPush() + isPushEnabled = true + showToast("Push enabled") + } + } + + func requestPushPermission() { + service.requestPushPermission { [weak self] accepted in + Task { @MainActor in + self?.isPushEnabled = accepted + self?.showToast(accepted ? "Push permission granted" : "Push permission denied") + } + } + } + + // MARK: - Email + + func addEmail(_ email: String) { + service.addEmail(email) + emails.append(email) + showToast("Email added") + } + + func removeEmail(_ email: String) { + service.removeEmail(email) + emails.removeAll { $0 == email } + showToast("Email removed") + } + + // MARK: - SMS + + func addSms(_ number: String) { + service.addSms(number) + smsNumbers.append(number) + showToast("SMS added") + } + + func removeSms(_ number: String) { + service.removeSms(number) + smsNumbers.removeAll { $0 == number } + showToast("SMS removed") + } + + // MARK: - Tags + + func addTag(key: String, value: String) { + service.addTag(key: key, value: value) + // Remove existing tag with same key if present + tags.removeAll { $0.key == key } + tags.append(KeyValueItem(key: key, value: value)) + showToast("Tag added") + } + + func removeTag(_ item: KeyValueItem) { + service.removeTag(item.key) + tags.removeAll { $0.id == item.id } + showToast("Tag removed") + } + + // MARK: - Outcomes + + func sendOutcome(_ name: String) { + service.sendOutcome(name) + showToast("Outcome '\(name)' sent") + } + + func sendOutcome(_ name: String, value: Double) { + service.sendOutcome(name, value: NSNumber(value: value)) + showToast("Outcome '\(name)' with value \(value) sent") + } + + func sendUniqueOutcome(_ name: String) { + service.sendUniqueOutcome(name) + showToast("Unique outcome '\(name)' sent") + } + + // MARK: - In-App Messaging + + func toggleInAppMessagesPaused() { + isInAppMessagesPaused.toggle() + service.isInAppMessagesPaused = isInAppMessagesPaused + showToast(isInAppMessagesPaused ? "In-app messages paused" : "In-app messages resumed") + } + + func addTrigger(key: String, value: String) { + service.addTrigger(key: key, value: value) + // Remove existing trigger with same key if present + triggers.removeAll { $0.key == key } + triggers.append(KeyValueItem(key: key, value: value)) + showToast("Trigger added") + } + + func removeTrigger(_ item: KeyValueItem) { + service.removeTrigger(item.key) + triggers.removeAll { $0.id == item.id } + showToast("Trigger removed") + } + + // MARK: - Location + + func toggleLocationShared() { + isLocationShared.toggle() + service.isLocationShared = isLocationShared + showToast(isLocationShared ? "Location sharing enabled" : "Location sharing disabled") + } + + func promptLocation() { + service.requestLocationPermission() + showToast("Location permission requested") + } + + // MARK: - Notifications + + func clearAllNotifications() { + service.clearAllNotifications() + showToast("All notifications cleared") + } + + func sendTestNotification(_ type: NotificationType) { + // In a real app, this would trigger a notification via your backend + // For demo purposes, we just show a toast + showToast("Test '\(type.rawValue)' notification triggered") + } + + func sendTestInAppMessage(_ type: InAppMessageType) { + // In a real app, this would trigger an IAM via your backend + // For demo purposes, we just show a toast + showToast("Test '\(type.rawValue)' in-app message triggered") + } + + // MARK: - Add Sheet + + func showAddSheet(for type: AddItemType) { + addItemType = type + showingAddSheet = true + } + + func handleAddItem(key: String, value: String) { + switch addItemType { + case .alias: + addAlias(label: key, id: value) + case .email: + addEmail(value) + case .sms: + addSms(value) + case .tag: + addTag(key: key, value: value) + case .trigger: + addTrigger(key: key, value: value) + case .externalUserId: + login(externalId: value) + } + showingAddSheet = false + } + + // MARK: - Toast + + private func showToast(_ message: String) { + toastMessage = message + + // Auto-dismiss after 2 seconds + Task { + try? await Task.sleep(nanoseconds: 2_000_000_000) + toastMessage = nil + } + } + + // MARK: - Observers + + private func setupObservers() { + observers.viewModel = self + service.addPushSubscriptionObserver(observers) + service.addUserObserver(observers) + service.addPermissionObserver(observers) + } +} + +// MARK: - Observer Classes + +private class Observers: NSObject, OSPushSubscriptionObserver, OSUserStateObserver, OSNotificationPermissionObserver { + weak var viewModel: OneSignalViewModel? + + func onPushSubscriptionDidChange(state: OSPushSubscriptionChangedState) { + Task { @MainActor in + viewModel?.pushSubscriptionId = state.current.id + viewModel?.isPushEnabled = state.current.optedIn + } + } + + func onUserStateDidChange(state: OSUserChangedState) { + Task { @MainActor in + // User state changed - could refresh aliases, etc. + print("User state changed: \(state.jsonRepresentation())") + } + } + + func onNotificationPermissionDidChange(_ permission: Bool) { + Task { @MainActor in + viewModel?.isPushEnabled = permission && (viewModel?.isPushEnabled ?? false) + } + } +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift new file mode 100644 index 000000000..ada4a861a --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift @@ -0,0 +1,126 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A reusable sheet for adding items with one or two text fields +struct AddItemSheet: View { + let itemType: AddItemType + let onAdd: (String, String) -> Void + let onCancel: () -> Void + + @State private var keyText: String = "" + @State private var valueText: String = "" + @FocusState private var focusedField: Field? + + private enum Field { + case key, value + } + + var body: some View { + NavigationStack { + Form { + if itemType.requiresKeyValue { + Section { + TextField(itemType.keyPlaceholder, text: $keyText) + .focused($focusedField, equals: .key) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + + TextField(itemType.valuePlaceholder, text: $valueText) + .focused($focusedField, equals: .value) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .keyboardType(itemType.keyboardType) + } + } else { + Section { + TextField(itemType.valuePlaceholder, text: $valueText) + .focused($focusedField, equals: .value) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .keyboardType(itemType.keyboardType) + } + } + } + .navigationTitle(itemType.title) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { + onCancel() + } + } + + ToolbarItem(placement: .confirmationAction) { + Button(itemType == .externalUserId ? "Login" : "Add") { + onAdd(keyText, valueText) + } + .disabled(!isValid) + } + } + .onAppear { + focusedField = itemType.requiresKeyValue ? .key : .value + } + } + .presentationDetents([.medium]) + .presentationDragIndicator(.visible) + } + + private var isValid: Bool { + if itemType.requiresKeyValue { + return !keyText.trimmingCharacters(in: .whitespaces).isEmpty && + !valueText.trimmingCharacters(in: .whitespaces).isEmpty + } else { + return !valueText.trimmingCharacters(in: .whitespaces).isEmpty + } + } +} + +#Preview("Add Alias") { + AddItemSheet( + itemType: .alias, + onAdd: { key, value in print("Add: \(key) = \(value)") }, + onCancel: { print("Cancel") } + ) +} + +#Preview("Add Email") { + AddItemSheet( + itemType: .email, + onAdd: { _, value in print("Add: \(value)") }, + onCancel: { print("Cancel") } + ) +} + +#Preview("Login User") { + AddItemSheet( + itemType: .externalUserId, + onAdd: { _, value in print("Login: \(value)") }, + onCancel: { print("Cancel") } + ) +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift new file mode 100644 index 000000000..d8b149e53 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift @@ -0,0 +1,158 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A row displaying a key-value pair with optional delete action +struct KeyValueRow: View { + let item: KeyValueItem + let onDelete: (() -> Void)? + + init(item: KeyValueItem, onDelete: (() -> Void)? = nil) { + self.item = item + self.onDelete = onDelete + } + + var body: some View { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(item.key) + .font(.subheadline) + .foregroundColor(.secondary) + Text(item.value) + .font(.body) + } + + Spacer() + + if let onDelete = onDelete { + Button(action: onDelete) { + Image(systemName: "trash") + .foregroundColor(.red) + } + .buttonStyle(.borderless) + } + } + .contentShape(Rectangle()) + } +} + +/// A row displaying a single value with optional delete action +struct SingleValueRow: View { + let value: String + let onDelete: (() -> Void)? + + init(value: String, onDelete: (() -> Void)? = nil) { + self.value = value + self.onDelete = onDelete + } + + var body: some View { + HStack { + Text(value) + .font(.body) + + Spacer() + + if let onDelete = onDelete { + Button(action: onDelete) { + Image(systemName: "trash") + .foregroundColor(.red) + } + .buttonStyle(.borderless) + } + } + .contentShape(Rectangle()) + } +} + +/// A row displaying a label and value in a horizontal layout +struct InfoRow: View { + let label: String + let value: String + let isMonospaced: Bool + + init(label: String, value: String, isMonospaced: Bool = false) { + self.label = label + self.value = value + self.isMonospaced = isMonospaced + } + + var body: some View { + HStack { + Text(label) + .foregroundColor(.secondary) + Spacer() + Text(value) + .font(isMonospaced ? .system(.body, design: .monospaced) : .body) + .foregroundColor(.primary) + .lineLimit(1) + .truncationMode(.middle) + } + } +} + +/// A placeholder row for empty lists +struct EmptyListRow: View { + let message: String + + var body: some View { + Text(message) + .foregroundColor(.secondary) + .font(.subheadline) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.vertical, 8) + } +} + +#Preview { + List { + Section("Key-Value Items") { + KeyValueRow( + item: KeyValueItem(key: "external_id", value: "user_123"), + onDelete: {} + ) + KeyValueRow( + item: KeyValueItem(key: "subscription_tier", value: "premium") + ) + } + + Section("Single Values") { + SingleValueRow(value: "user@example.com", onDelete: {}) + SingleValueRow(value: "+1234567890") + } + + Section("Info Rows") { + InfoRow(label: "App ID", value: "77e32082-ea27-42e3-a898-c72e141824ef", isMonospaced: true) + InfoRow(label: "Status", value: "Active") + } + + Section("Empty") { + EmptyListRow(message: "No items added") + } + } +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift new file mode 100644 index 000000000..35dd290a1 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift @@ -0,0 +1,137 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A grid of notification type buttons +struct NotificationTypeGrid: View { + let onSelect: (NotificationType) -> Void + + private let columns = [ + GridItem(.flexible(), spacing: 12), + GridItem(.flexible(), spacing: 12) + ] + + var body: some View { + LazyVGrid(columns: columns, spacing: 12) { + ForEach(NotificationType.allCases) { type in + NotificationTypeButton(type: type) { + onSelect(type) + } + } + } + .padding(.vertical, 8) + } +} + +/// A grid of in-app message type buttons +struct InAppMessageTypeGrid: View { + let onSelect: (InAppMessageType) -> Void + + private let columns = [ + GridItem(.flexible(), spacing: 12), + GridItem(.flexible(), spacing: 12) + ] + + var body: some View { + LazyVGrid(columns: columns, spacing: 12) { + ForEach(InAppMessageType.allCases) { type in + InAppMessageTypeButton(type: type) { + onSelect(type) + } + } + } + .padding(.vertical, 8) + } +} + +/// A button for a notification type with icon and label +struct NotificationTypeButton: View { + let type: NotificationType + let action: () -> Void + + var body: some View { + Button(action: action) { + VStack(spacing: 8) { + Image(systemName: type.iconName) + .font(.title2) + Text(type.rawValue) + .font(.caption) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .background(Color.accentColor) + .foregroundColor(.white) + .cornerRadius(12) + } + .buttonStyle(.plain) + } +} + +/// A button for an in-app message type with icon and label +struct InAppMessageTypeButton: View { + let type: InAppMessageType + let action: () -> Void + + var body: some View { + Button(action: action) { + VStack(spacing: 8) { + Image(systemName: type.iconName) + .font(.title2) + Text(type.rawValue) + .font(.caption) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .background(Color.accentColor) + .foregroundColor(.white) + .cornerRadius(12) + } + .buttonStyle(.plain) + } +} + +#Preview { + ScrollView { + VStack(alignment: .leading, spacing: 20) { + Text("Send Push Notification") + .font(.headline) + NotificationTypeGrid(onSelect: { type in + print("Selected: \(type.rawValue)") + }) + + Text("Send In-App Message") + .font(.headline) + InAppMessageTypeGrid(onSelect: { type in + print("Selected: \(type.rawValue)") + }) + } + .padding() + } +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift new file mode 100644 index 000000000..a0e298b63 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift @@ -0,0 +1,80 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A toast notification view that appears at the bottom of the screen +struct ToastView: View { + let message: String + + var body: some View { + Text(message) + .font(.subheadline) + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 12) + .background(Color.black.opacity(0.8)) + .cornerRadius(8) + .shadow(radius: 4) + } +} + +/// A view modifier that overlays a toast message +struct ToastModifier: ViewModifier { + @Binding var message: String? + + func body(content: Content) -> some View { + ZStack { + content + + if let message = message { + VStack { + Spacer() + ToastView(message: message) + .padding(.bottom, 32) + .transition(.move(edge: .bottom).combined(with: .opacity)) + } + .animation(.easeInOut(duration: 0.3), value: message) + } + } + } +} + +extension View { + /// Adds a toast overlay to the view + func toast(message: Binding) -> some View { + modifier(ToastModifier(message: message)) + } +} + +#Preview { + VStack { + Text("Content") + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .toast(message: .constant("This is a toast message")) +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift new file mode 100644 index 000000000..1e383f694 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift @@ -0,0 +1,75 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Main content view composing all sections +struct ContentView: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + NavigationStack { + List { + AppInfoSection() + UserSection() + SubscriptionSection() + TagsSection() + MessagingSection() + LocationSection() + NotificationSection() + } + .listStyle(.insetGrouped) + .navigationTitle("OneSignal") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + viewModel.refreshState() + } label: { + Image(systemName: "arrow.clockwise") + } + } + } + .sheet(isPresented: $viewModel.showingAddSheet) { + AddItemSheet( + itemType: viewModel.addItemType, + onAdd: { key, value in + viewModel.handleAddItem(key: key, value: value) + }, + onCancel: { + viewModel.showingAddSheet = false + } + ) + } + } + .toast(message: $viewModel.toastMessage) + } +} + +#Preview { + ContentView() + .environmentObject(OneSignalViewModel()) +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift new file mode 100644 index 000000000..d16c71ffa --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift @@ -0,0 +1,69 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section displaying app information and consent management +struct AppInfoSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + Section { + // App ID + VStack(alignment: .leading, spacing: 4) { + Text("App ID") + .font(.caption) + .foregroundColor(.secondary) + Text(viewModel.appId) + .font(.system(.footnote, design: .monospaced)) + .textSelection(.enabled) + } + .padding(.vertical, 4) + + // Revoke Consent Button + Button(role: .destructive) { + viewModel.revokeConsent() + } label: { + HStack { + Spacer() + Text("Revoke Consent") + .fontWeight(.medium) + Spacer() + } + } + } header: { + Text("App") + } + } +} + +#Preview { + List { + AppInfoSection() + } + .environmentObject(OneSignalViewModel()) +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift new file mode 100644 index 000000000..a3eda8e0c --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift @@ -0,0 +1,69 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for location sharing and permissions +struct LocationSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + Section { + Toggle(isOn: Binding( + get: { viewModel.isLocationShared }, + set: { _ in viewModel.toggleLocationShared() } + )) { + VStack(alignment: .leading, spacing: 2) { + Text("Location Shared") + Text("Location will be shared from device") + .font(.caption) + .foregroundColor(.secondary) + } + } + + Button { + viewModel.promptLocation() + } label: { + HStack { + Spacer() + Text("Prompt Location") + .fontWeight(.medium) + Spacer() + } + } + } header: { + Text("Location") + } + } +} + +#Preview { + List { + LocationSection() + } + .environmentObject(OneSignalViewModel()) +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift new file mode 100644 index 000000000..1e38356ca --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift @@ -0,0 +1,179 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for outcomes, in-app messaging, and triggers +struct MessagingSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + @State private var showingOutcomeSheet = false + @State private var outcomeName = "" + @State private var outcomeValue = "" + + var body: some View { + // Outcome Events Section + Section { + Button { + showingOutcomeSheet = true + } label: { + HStack { + Spacer() + Text("Send Outcome") + .fontWeight(.medium) + Spacer() + } + } + } header: { + Text("Outcome Events") + } + + // In-App Messaging Section + Section { + Toggle(isOn: Binding( + get: { viewModel.isInAppMessagesPaused }, + set: { _ in viewModel.toggleInAppMessagesPaused() } + )) { + VStack(alignment: .leading, spacing: 2) { + Text("Pause In-App Messages") + Text("Toggle in-app messages") + .font(.caption) + .foregroundColor(.secondary) + } + } + } header: { + Text("In-App Messaging") + } + + // Triggers Section + Section { + if viewModel.triggers.isEmpty { + EmptyListRow(message: "No Triggers Added") + } else { + ForEach(viewModel.triggers) { trigger in + KeyValueRow(item: trigger) { + viewModel.removeTrigger(trigger) + } + } + } + + Button { + viewModel.showAddSheet(for: .trigger) + } label: { + HStack { + Spacer() + Label("Add Trigger", systemImage: "plus") + .fontWeight(.medium) + Spacer() + } + } + } header: { + Text("Triggers") + } + .sheet(isPresented: $showingOutcomeSheet) { + OutcomeSheet( + onSend: { name, value in + if let value = value { + viewModel.sendOutcome(name, value: value) + } else { + viewModel.sendOutcome(name) + } + showingOutcomeSheet = false + }, + onCancel: { + showingOutcomeSheet = false + } + ) + } + } +} + +/// Sheet for sending outcomes +struct OutcomeSheet: View { + let onSend: (String, Double?) -> Void + let onCancel: () -> Void + + @State private var outcomeName = "" + @State private var outcomeValue = "" + @State private var includeValue = false + @FocusState private var focusedField: Field? + + private enum Field { + case name, value + } + + var body: some View { + NavigationStack { + Form { + Section { + TextField("Outcome Name", text: $outcomeName) + .focused($focusedField, equals: .name) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + } + + Section { + Toggle("Include Value", isOn: $includeValue) + + if includeValue { + TextField("Value", text: $outcomeValue) + .focused($focusedField, equals: .value) + .keyboardType(.decimalPad) + } + } + } + .navigationTitle("Send Outcome") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { + onCancel() + } + } + + ToolbarItem(placement: .confirmationAction) { + Button("Send") { + let value = includeValue ? Double(outcomeValue) : nil + onSend(outcomeName, value) + } + .disabled(outcomeName.trimmingCharacters(in: .whitespaces).isEmpty) + } + } + .onAppear { + focusedField = .name + } + } + .presentationDetents([.medium]) + .presentationDragIndicator(.visible) + } +} + +#Preview { + List { + MessagingSection() + } + .environmentObject(OneSignalViewModel()) +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift new file mode 100644 index 000000000..f30b436a2 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift @@ -0,0 +1,60 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for sending test push notifications and in-app messages +struct NotificationSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + // Send Push Notification Section + Section { + NotificationTypeGrid { type in + viewModel.sendTestNotification(type) + } + } header: { + Text("Send Push Notification") + } + + // Send In-App Message Section + Section { + InAppMessageTypeGrid { type in + viewModel.sendTestInAppMessage(type) + } + } header: { + Text("Send In-App Message") + } + } +} + +#Preview { + List { + NotificationSection() + } + .environmentObject(OneSignalViewModel()) +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift new file mode 100644 index 000000000..675ad8390 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift @@ -0,0 +1,116 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for push subscription, email, and SMS management +struct SubscriptionSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + // Push Section + Section { + // Push ID + VStack(alignment: .leading, spacing: 4) { + Text("Push ID") + .font(.caption) + .foregroundColor(.secondary) + Text(viewModel.pushSubscriptionId ?? "Not available") + .font(.system(.footnote, design: .monospaced)) + .textSelection(.enabled) + } + .padding(.vertical, 4) + + // Enabled Toggle + Toggle("Enabled", isOn: Binding( + get: { viewModel.isPushEnabled }, + set: { _ in viewModel.togglePushEnabled() } + )) + } header: { + Text("Push") + } + + // Emails Section + Section { + if viewModel.emails.isEmpty { + EmptyListRow(message: "No Emails Added") + } else { + ForEach(viewModel.emails, id: \.self) { email in + SingleValueRow(value: email) { + viewModel.removeEmail(email) + } + } + } + + Button { + viewModel.showAddSheet(for: .email) + } label: { + HStack { + Spacer() + Label("Add Email", systemImage: "plus") + .fontWeight(.medium) + Spacer() + } + } + } header: { + Text("Emails") + } + + // SMS Section + Section { + if viewModel.smsNumbers.isEmpty { + EmptyListRow(message: "No SMSs Added") + } else { + ForEach(viewModel.smsNumbers, id: \.self) { sms in + SingleValueRow(value: sms) { + viewModel.removeSms(sms) + } + } + } + + Button { + viewModel.showAddSheet(for: .sms) + } label: { + HStack { + Spacer() + Label("Add SMS", systemImage: "plus") + .fontWeight(.medium) + Spacer() + } + } + } header: { + Text("SMSs") + } + } +} + +#Preview { + List { + SubscriptionSection() + } + .environmentObject(OneSignalViewModel()) +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift new file mode 100644 index 000000000..4de6de523 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift @@ -0,0 +1,67 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for managing user tags +struct TagsSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + Section { + if viewModel.tags.isEmpty { + EmptyListRow(message: "No Tags Added") + } else { + ForEach(viewModel.tags) { tag in + KeyValueRow(item: tag) { + viewModel.removeTag(tag) + } + } + } + + Button { + viewModel.showAddSheet(for: .tag) + } label: { + HStack { + Spacer() + Label("Add Tag", systemImage: "plus") + .fontWeight(.medium) + Spacer() + } + } + } header: { + Text("Tags") + } + } +} + +#Preview { + List { + TagsSection() + } + .environmentObject(OneSignalViewModel()) +} diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift new file mode 100644 index 000000000..c8fcaab88 --- /dev/null +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift @@ -0,0 +1,103 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for user login/logout and alias management +struct UserSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + // Login/Logout Section + Section { + // Login Button + Button { + viewModel.showAddSheet(for: .externalUserId) + } label: { + HStack { + Spacer() + Text("Login User") + .fontWeight(.medium) + Spacer() + } + } + + // Logout Button + Button(role: .destructive) { + viewModel.logout() + } label: { + HStack { + Spacer() + Text("Logout User") + .fontWeight(.medium) + Spacer() + } + } + .disabled(viewModel.externalUserId == nil) + + // Current User Info + if let userId = viewModel.externalUserId { + InfoRow(label: "External User ID", value: userId) + } + } header: { + Text("User") + } + + // Aliases Section + Section { + if viewModel.aliases.isEmpty { + EmptyListRow(message: "No Aliases Added") + } else { + ForEach(viewModel.aliases) { alias in + KeyValueRow(item: alias) { + viewModel.removeAlias(alias) + } + } + } + + Button { + viewModel.showAddSheet(for: .alias) + } label: { + HStack { + Spacer() + Label("Add Alias", systemImage: "plus") + .fontWeight(.medium) + Spacer() + } + } + } header: { + Text("Aliases") + } + } +} + +#Preview { + List { + UserSection() + } + .environmentObject(OneSignalViewModel()) +} diff --git a/OneSignalSwiftUIExample/README.md b/OneSignalSwiftUIExample/README.md new file mode 100644 index 000000000..f20899df8 --- /dev/null +++ b/OneSignalSwiftUIExample/README.md @@ -0,0 +1,136 @@ +# OneSignal SwiftUI Example App + +A modern SwiftUI example app demonstrating the OneSignal iOS SDK features using MVVM architecture. + +## Features + +This example app demonstrates all major OneSignal SDK capabilities: + +- **User Management**: Login/logout with external user ID +- **Aliases**: Add and remove user aliases +- **Push Subscriptions**: Enable/disable push notifications, view push ID +- **Email & SMS**: Add and remove email and SMS subscriptions +- **Tags**: Manage user tags for segmentation +- **Outcomes**: Track outcome events with optional values +- **In-App Messaging**: Pause/resume IAM, manage triggers +- **Location**: Toggle location sharing, request permissions +- **Test Notifications**: Grid of notification types for testing + +## Architecture + +The app follows the **MVVM (Model-View-ViewModel)** pattern: + +``` +OneSignalSwiftUIExample/ +├── App/ +│ └── OneSignalSwiftUIExampleApp.swift # App entry point, SDK init +├── Views/ +│ ├── ContentView.swift # Main view +│ ├── Sections/ # Feature sections +│ │ ├── AppInfoSection.swift +│ │ ├── UserSection.swift +│ │ ├── SubscriptionSection.swift +│ │ ├── TagsSection.swift +│ │ ├── MessagingSection.swift +│ │ ├── LocationSection.swift +│ │ └── NotificationSection.swift +│ └── Components/ # Reusable UI components +│ ├── KeyValueRow.swift +│ ├── AddItemSheet.swift +│ ├── NotificationGrid.swift +│ └── ToastView.swift +├── ViewModels/ +│ └── OneSignalViewModel.swift # Main ViewModel +├── Models/ +│ └── AppModels.swift # Data models +├── Services/ +│ └── OneSignalService.swift # SDK wrapper +└── Assets.xcassets/ # App assets +``` + +## Setup Instructions + +### 1. Create Xcode Project + +1. Open Xcode and create a new project +2. Select **iOS** → **App** +3. Configure the project: + - Product Name: `OneSignalSwiftUIExample` + - Team: Your development team + - Organization Identifier: `com.onesignal` + - Interface: **SwiftUI** + - Language: **Swift** + - Storage: None +4. Save the project in `iOS_SDK/OneSignalSwiftUIExample/` + +### 2. Add Source Files + +1. Delete the auto-generated `ContentView.swift` and `OneSignalSwiftUIExampleApp.swift` +2. Drag all the folders from `OneSignalSwiftUIExample/` into your Xcode project: + - `App/` + - `Views/` + - `ViewModels/` + - `Models/` + - `Services/` + - `Assets.xcassets/` +3. Make sure "Copy items if needed" is **unchecked** and "Create groups" is selected + +### 3. Add OneSignal SDK Dependencies + +#### Option A: Swift Package Manager (Recommended) + +1. In Xcode, go to **File** → **Add Package Dependencies...** +2. Enter the OneSignal SDK repository URL: `https://github.com/OneSignal/OneSignal-iOS-SDK` +3. Select version **5.0.0** or later +4. Add the following packages to your main target: + - `OneSignalFramework` + - `OneSignalInAppMessages` + - `OneSignalLocation` + +#### Option B: Local Development + +If you're developing the SDK locally: + +1. Drag the parent `OneSignal-iOS-SDK` folder into your project +2. Or add local package dependency pointing to the repo root + +### 4. Configure Capabilities + +1. Select your project in the navigator +2. Select your app target +3. Go to **Signing & Capabilities** +4. Add the following capabilities: + - **Push Notifications** + - **Background Modes** → Check "Remote notifications" + +### 5. Configure Info.plist + +The included `Info.plist` already has the required keys: +- `NSLocationWhenInUseUsageDescription` +- `NSLocationAlwaysAndWhenInUseUsageDescription` +- `UIBackgroundModes` with `remote-notification` + +### 6. Update App ID (Optional) + +The default OneSignal App ID is configured in `OneSignalService.swift`. To use your own: + +1. Open `Services/OneSignalService.swift` +2. Change the `defaultAppId` value to your OneSignal App ID + +## Running the App + +1. Select a simulator or device +2. Build and run (⌘R) +3. Grant notification permissions when prompted +4. Explore the various OneSignal features + +## Requirements + +- iOS 15.0+ +- Xcode 15.0+ +- Swift 5.9+ +- OneSignal iOS SDK 5.0+ + +## License + +Modified MIT License - See LICENSE file for details. diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements new file mode 100644 index 000000000..903def2af --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj index 83222dfb2..093135984 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj @@ -3,69 +3,264 @@ archiveVersion = 1; classes = { }; - objectVersion = 77; + objectVersion = 63; objects = { /* Begin PBXBuildFile section */ - 3C25C0552F33E031005E5E9A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3C25C0512F33E031005E5E9A /* Assets.xcassets */; }; - 3C25C0562F33E031005E5E9A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C25C0522F33E031005E5E9A /* ContentView.swift */; }; - 3C25C0572F33E031005E5E9A /* OneSignalSwiftUIExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C25C0532F33E031005E5E9A /* OneSignalSwiftUIExampleApp.swift */; }; + 104E342C46E0870F7E30FB29 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56C25916C718509D05DF03B2 /* CoreLocation.framework */; }; + 12A93EBB7D6C97B82814924A /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CD2DA9DC09F1EF7D989C291 /* UserNotifications.framework */; }; + 1D68D16D167B951BD57386ED /* OneSignalSwiftUIExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE9826D274F62E747F3A931B /* OneSignalSwiftUIExampleApp.swift */; }; + 228DE2E45BE2EEA72F9436F3 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 985DE7F1C6162433D11943B0 /* SystemConfiguration.framework */; }; + 23578DF36320EFEB17E12FFA /* OneSignalInAppMessages.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A6BA7282C93D0714AB7AE9D /* OneSignalInAppMessages.framework */; }; + 25D4FC203BE01E9A35ACA465 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FEE98DA6A075AEB7709CCBE2 /* Assets.xcassets */; }; + 2E6D6E17EEE5D42AE3CAB9EA /* AddItemSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42140C8B10F3D06BDE1C79E /* AddItemSheet.swift */; }; + 2F52BD9F2A3002390A7C8896 /* OneSignalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD249AC2E0DD1F282E33E3BF /* OneSignalViewModel.swift */; }; + 36AD6B646EA553AD54024FAC /* OneSignalLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5B39E8FA865B4030F3009B4 /* OneSignalLocation.framework */; }; + 39D261592E207BCB8F453E6E /* OneSignalOSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 501ED810C4C9B04785D4CCD4 /* OneSignalOSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3C25C0592F3409E9005E5E9A /* NotificationSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C25C0582F3409E9005E5E9A /* NotificationSender.swift */; }; + 4840204E727B4F405094C542 /* OneSignalExtension.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B441216A875FE941AED4964E /* OneSignalExtension.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 49E74AFBFBE23D06B7D310EC /* SubscriptionSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DF983228037C1B729C1419 /* SubscriptionSection.swift */; }; + 56FA425BC3C515132CF2098F /* OneSignalUser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7B9F08F02892844599AA8BDD /* OneSignalUser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 59BF581E9C89F1D09BB5130A /* OneSignalOutcomes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E815F87FE680572C74E9D51 /* OneSignalOutcomes.framework */; }; + 5EEA4B007D60765502F2A6E5 /* MessagingSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 962607A12EFC53D2F77E5948 /* MessagingSection.swift */; }; + 644520A37F05E25FD17F5CF9 /* AppModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45255F2C6EB0B13E199BDC65 /* AppModels.swift */; }; + 74920778F254B9DA27906AA3 /* AppInfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564304E2BA4613FD7649116D /* AppInfoSection.swift */; }; + 861FCEE8AFB371A2FA3BCC93 /* OneSignalOSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 501ED810C4C9B04785D4CCD4 /* OneSignalOSCore.framework */; }; + 8EF4836F9252E9C8180804AB /* OneSignalOutcomes.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4E815F87FE680572C74E9D51 /* OneSignalOutcomes.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 8F69162AE31452F3956160BE /* OneSignalUser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B9F08F02892844599AA8BDD /* OneSignalUser.framework */; }; + 9EF9D8D46AAAD6F93B32FDCB /* OneSignalLocation.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5B39E8FA865B4030F3009B4 /* OneSignalLocation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + A8954DAC09761A3125EF37D3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A084A32F6E0CA6A6354705C0 /* WebKit.framework */; }; + A92101FA384AC15596D4A659 /* OneSignalNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0805F3793886C9122DFB6E4 /* OneSignalNotifications.framework */; }; + AD19D28FDE8DAA7C8BC87939 /* LocationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D373C8C00DE06FC5CD01C5 /* LocationSection.swift */; }; + B13E9460BDD0553FF05542A0 /* NotificationGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 414EE3451468D29D4A9C87AC /* NotificationGrid.swift */; }; + B5C4A4AC0A2D2AF73C9A7DD3 /* KeyValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A4F866BFA1EE429ED1CECC /* KeyValueRow.swift */; }; + C9884D12CEDE50ACAE38DF0D /* NotificationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E237FD3F84CE3831CAB8A6DA /* NotificationSection.swift */; }; + C994C923037F602FA48A1490 /* OneSignalFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB1399100ED5DD83ED6CD889 /* OneSignalFramework.framework */; }; + CB5F82751F156695A7A03338 /* OneSignalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FF4424D702D8686CC819B8 /* OneSignalService.swift */; }; + D13A25FE5762A23125227A84 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8029F22CDBDB03B2CBA14212 /* ToastView.swift */; }; + D9E5ED5BA2BCFF6D9233AC66 /* OneSignalCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 533347B40563BF22FB6EAC9C /* OneSignalCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + DAA6DDE0CF432A7BE50481EC /* OneSignalNotifications.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0805F3793886C9122DFB6E4 /* OneSignalNotifications.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + DB2BA39FE32DB3D52154F48C /* TagsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49D0DCDB581563C015CEAFF /* TagsSection.swift */; }; + DD9942D5EB98C5F70FA8D3D8 /* OneSignalInAppMessages.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7A6BA7282C93D0714AB7AE9D /* OneSignalInAppMessages.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + E1BEF5E30555F0ADCBF62B7D /* UserSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D22580635AB6E6BE86F1396 /* UserSection.swift */; }; + E20A2AFD25AD52384A15794A /* OneSignalFramework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FB1399100ED5DD83ED6CD889 /* OneSignalFramework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + E26208CEB7DC4D217EAAE4E6 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BF17A9E51F5A187FFAC1AC /* ContentView.swift */; }; + EFE6A330EF362418C68B3F6E /* OneSignalCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 533347B40563BF22FB6EAC9C /* OneSignalCore.framework */; }; + F8CF0C2C1A1F8842BCF86784 /* OneSignalExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B441216A875FE941AED4964E /* OneSignalExtension.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + 510E0294CE200E84088E8CC1 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + E20A2AFD25AD52384A15794A /* OneSignalFramework.framework in Embed Frameworks */, + D9E5ED5BA2BCFF6D9233AC66 /* OneSignalCore.framework in Embed Frameworks */, + 4840204E727B4F405094C542 /* OneSignalExtension.framework in Embed Frameworks */, + 8EF4836F9252E9C8180804AB /* OneSignalOutcomes.framework in Embed Frameworks */, + 39D261592E207BCB8F453E6E /* OneSignalOSCore.framework in Embed Frameworks */, + 56FA425BC3C515132CF2098F /* OneSignalUser.framework in Embed Frameworks */, + DAA6DDE0CF432A7BE50481EC /* OneSignalNotifications.framework in Embed Frameworks */, + DD9942D5EB98C5F70FA8D3D8 /* OneSignalInAppMessages.framework in Embed Frameworks */, + 9EF9D8D46AAAD6F93B32FDCB /* OneSignalLocation.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - 3C25C0432F33E016005E5E9A /* OneSignalSwiftUIExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OneSignalSwiftUIExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 3C25C0512F33E031005E5E9A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 3C25C0522F33E031005E5E9A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 3C25C0532F33E031005E5E9A /* OneSignalSwiftUIExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalSwiftUIExampleApp.swift; sourceTree = ""; }; + 09DF983228037C1B729C1419 /* SubscriptionSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionSection.swift; sourceTree = ""; }; + 3C25C0582F3409E9005E5E9A /* NotificationSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSender.swift; sourceTree = ""; }; + 414EE3451468D29D4A9C87AC /* NotificationGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationGrid.swift; sourceTree = ""; }; + 41A4F866BFA1EE429ED1CECC /* KeyValueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueRow.swift; sourceTree = ""; }; + 45255F2C6EB0B13E199BDC65 /* AppModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppModels.swift; sourceTree = ""; }; + 4D22580635AB6E6BE86F1396 /* UserSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSection.swift; sourceTree = ""; }; + 4E815F87FE680572C74E9D51 /* OneSignalOutcomes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalOutcomes.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 501ED810C4C9B04785D4CCD4 /* OneSignalOSCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalOSCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 533347B40563BF22FB6EAC9C /* OneSignalCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 564304E2BA4613FD7649116D /* AppInfoSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoSection.swift; sourceTree = ""; }; + 56C25916C718509D05DF03B2 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; + 60D373C8C00DE06FC5CD01C5 /* LocationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSection.swift; sourceTree = ""; }; + 6CD2DA9DC09F1EF7D989C291 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; + 7A6BA7282C93D0714AB7AE9D /* OneSignalInAppMessages.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalInAppMessages.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7B9F08F02892844599AA8BDD /* OneSignalUser.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalUser.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8029F22CDBDB03B2CBA14212 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; + 85FF4424D702D8686CC819B8 /* OneSignalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalService.swift; sourceTree = ""; }; + 962607A12EFC53D2F77E5948 /* MessagingSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingSection.swift; sourceTree = ""; }; + 985DE7F1C6162433D11943B0 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + A0506934E0F27B864F3AC273 /* OneSignalSwiftUIExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OneSignalSwiftUIExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A084A32F6E0CA6A6354705C0 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + A3BF17A9E51F5A187FFAC1AC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + B441216A875FE941AED4964E /* OneSignalExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B5B39E8FA865B4030F3009B4 /* OneSignalLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalLocation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C49D0DCDB581563C015CEAFF /* TagsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsSection.swift; sourceTree = ""; }; + E0805F3793886C9122DFB6E4 /* OneSignalNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalNotifications.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E237FD3F84CE3831CAB8A6DA /* NotificationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSection.swift; sourceTree = ""; }; + F42140C8B10F3D06BDE1C79E /* AddItemSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddItemSheet.swift; sourceTree = ""; }; + FB1399100ED5DD83ED6CD889 /* OneSignalFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FD249AC2E0DD1F282E33E3BF /* OneSignalViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalViewModel.swift; sourceTree = ""; }; + FDEE7A98A0EBB6BF767A041D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + FE9826D274F62E747F3A931B /* OneSignalSwiftUIExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalSwiftUIExampleApp.swift; sourceTree = ""; }; + FEE98DA6A075AEB7709CCBE2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 3C25C0402F33E016005E5E9A /* Frameworks */ = { + D1959F94F086AC39994A96BD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C994C923037F602FA48A1490 /* OneSignalFramework.framework in Frameworks */, + EFE6A330EF362418C68B3F6E /* OneSignalCore.framework in Frameworks */, + F8CF0C2C1A1F8842BCF86784 /* OneSignalExtension.framework in Frameworks */, + 59BF581E9C89F1D09BB5130A /* OneSignalOutcomes.framework in Frameworks */, + 861FCEE8AFB371A2FA3BCC93 /* OneSignalOSCore.framework in Frameworks */, + 8F69162AE31452F3956160BE /* OneSignalUser.framework in Frameworks */, + A92101FA384AC15596D4A659 /* OneSignalNotifications.framework in Frameworks */, + 23578DF36320EFEB17E12FFA /* OneSignalInAppMessages.framework in Frameworks */, + 36AD6B646EA553AD54024FAC /* OneSignalLocation.framework in Frameworks */, + 104E342C46E0870F7E30FB29 /* CoreLocation.framework in Frameworks */, + 228DE2E45BE2EEA72F9436F3 /* SystemConfiguration.framework in Frameworks */, + 12A93EBB7D6C97B82814924A /* UserNotifications.framework in Frameworks */, + A8954DAC09761A3125EF37D3 /* WebKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 3C25C03A2F33E016005E5E9A = { + 3D3976C085CF6E7D61D7F075 /* Products */ = { isa = PBXGroup; children = ( - 3C25C0542F33E031005E5E9A /* OneSignalSwiftUIExample */, - 3C25C0442F33E016005E5E9A /* Products */, + A0506934E0F27B864F3AC273 /* OneSignalSwiftUIExample.app */, ); + name = Products; sourceTree = ""; }; - 3C25C0442F33E016005E5E9A /* Products */ = { + 4320E847C9D875EA2342DB5F /* Models */ = { isa = PBXGroup; children = ( - 3C25C0432F33E016005E5E9A /* OneSignalSwiftUIExample.app */, + 45255F2C6EB0B13E199BDC65 /* AppModels.swift */, ); - name = Products; + path = Models; + sourceTree = ""; + }; + 676D7DC4DABFC37256C328C5 /* Components */ = { + isa = PBXGroup; + children = ( + F42140C8B10F3D06BDE1C79E /* AddItemSheet.swift */, + 41A4F866BFA1EE429ED1CECC /* KeyValueRow.swift */, + 414EE3451468D29D4A9C87AC /* NotificationGrid.swift */, + 8029F22CDBDB03B2CBA14212 /* ToastView.swift */, + ); + path = Components; + sourceTree = ""; + }; + 82619F0C016CC5B46729A454 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 56C25916C718509D05DF03B2 /* CoreLocation.framework */, + 533347B40563BF22FB6EAC9C /* OneSignalCore.framework */, + B441216A875FE941AED4964E /* OneSignalExtension.framework */, + FB1399100ED5DD83ED6CD889 /* OneSignalFramework.framework */, + 7A6BA7282C93D0714AB7AE9D /* OneSignalInAppMessages.framework */, + B5B39E8FA865B4030F3009B4 /* OneSignalLocation.framework */, + E0805F3793886C9122DFB6E4 /* OneSignalNotifications.framework */, + 501ED810C4C9B04785D4CCD4 /* OneSignalOSCore.framework */, + 4E815F87FE680572C74E9D51 /* OneSignalOutcomes.framework */, + 7B9F08F02892844599AA8BDD /* OneSignalUser.framework */, + 985DE7F1C6162433D11943B0 /* SystemConfiguration.framework */, + 6CD2DA9DC09F1EF7D989C291 /* UserNotifications.framework */, + A084A32F6E0CA6A6354705C0 /* WebKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 8DB2942ED773E3D98EEC5396 = { + isa = PBXGroup; + children = ( + A21AEF40FA55F829CF1DA4B4 /* OneSignalSwiftUIExample */, + 82619F0C016CC5B46729A454 /* Frameworks */, + 3D3976C085CF6E7D61D7F075 /* Products */, + ); + sourceTree = ""; + }; + 92778F3DF1B0939012E946A7 /* Sections */ = { + isa = PBXGroup; + children = ( + 564304E2BA4613FD7649116D /* AppInfoSection.swift */, + 60D373C8C00DE06FC5CD01C5 /* LocationSection.swift */, + 962607A12EFC53D2F77E5948 /* MessagingSection.swift */, + E237FD3F84CE3831CAB8A6DA /* NotificationSection.swift */, + 09DF983228037C1B729C1419 /* SubscriptionSection.swift */, + C49D0DCDB581563C015CEAFF /* TagsSection.swift */, + 4D22580635AB6E6BE86F1396 /* UserSection.swift */, + ); + path = Sections; + sourceTree = ""; + }; + 9FC97545EE1DE4A1DD157B26 /* App */ = { + isa = PBXGroup; + children = ( + FE9826D274F62E747F3A931B /* OneSignalSwiftUIExampleApp.swift */, + ); + path = App; sourceTree = ""; }; - 3C25C0542F33E031005E5E9A /* OneSignalSwiftUIExample */ = { + A21AEF40FA55F829CF1DA4B4 /* OneSignalSwiftUIExample */ = { isa = PBXGroup; children = ( - 3C25C0512F33E031005E5E9A /* Assets.xcassets */, - 3C25C0522F33E031005E5E9A /* ContentView.swift */, - 3C25C0532F33E031005E5E9A /* OneSignalSwiftUIExampleApp.swift */, + 9FC97545EE1DE4A1DD157B26 /* App */, + 4320E847C9D875EA2342DB5F /* Models */, + E905BAC55971B3066478F04B /* Services */, + BAB41B49B9BD40FB4375BA33 /* ViewModels */, + DE2A74C6876FFA7E42683810 /* Views */, + FEE98DA6A075AEB7709CCBE2 /* Assets.xcassets */, + FDEE7A98A0EBB6BF767A041D /* Info.plist */, ); path = OneSignalSwiftUIExample; sourceTree = ""; }; + BAB41B49B9BD40FB4375BA33 /* ViewModels */ = { + isa = PBXGroup; + children = ( + FD249AC2E0DD1F282E33E3BF /* OneSignalViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + DE2A74C6876FFA7E42683810 /* Views */ = { + isa = PBXGroup; + children = ( + 676D7DC4DABFC37256C328C5 /* Components */, + 92778F3DF1B0939012E946A7 /* Sections */, + A3BF17A9E51F5A187FFAC1AC /* ContentView.swift */, + ); + path = Views; + sourceTree = ""; + }; + E905BAC55971B3066478F04B /* Services */ = { + isa = PBXGroup; + children = ( + 85FF4424D702D8686CC819B8 /* OneSignalService.swift */, + 3C25C0582F3409E9005E5E9A /* NotificationSender.swift */, + ); + path = Services; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 3C25C0422F33E016005E5E9A /* OneSignalSwiftUIExample */ = { + F27E45A0AADC4454C26D8C07 /* OneSignalSwiftUIExample */ = { isa = PBXNativeTarget; - buildConfigurationList = 3C25C04E2F33E019005E5E9A /* Build configuration list for PBXNativeTarget "OneSignalSwiftUIExample" */; + buildConfigurationList = 94A2EFFFED81A4B2280CE916 /* Build configuration list for PBXNativeTarget "OneSignalSwiftUIExample" */; buildPhases = ( - 3C25C03F2F33E016005E5E9A /* Sources */, - 3C25C0402F33E016005E5E9A /* Frameworks */, - 3C25C0412F33E016005E5E9A /* Resources */, + 1916C53ACCF1871776E6A261 /* Sources */, + 8B945C9AF93265E68BBE57A8 /* Resources */, + D1959F94F086AC39994A96BD /* Frameworks */, + 510E0294CE200E84088E8CC1 /* Embed Frameworks */, ); buildRules = ( ); @@ -75,75 +270,88 @@ packageProductDependencies = ( ); productName = OneSignalSwiftUIExample; - productReference = 3C25C0432F33E016005E5E9A /* OneSignalSwiftUIExample.app */; + productReference = A0506934E0F27B864F3AC273 /* OneSignalSwiftUIExample.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - 3C25C03B2F33E016005E5E9A /* Project object */ = { + 7B7016AA7DF5AB54CD96701C /* Project object */ = { isa = PBXProject; attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1640; - LastUpgradeCheck = 1640; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1300; TargetAttributes = { - 3C25C0422F33E016005E5E9A = { - CreatedOnToolsVersion = 16.4; + F27E45A0AADC4454C26D8C07 = { + ProvisioningStyle = Automatic; }; }; }; - buildConfigurationList = 3C25C03E2F33E016005E5E9A /* Build configuration list for PBXProject "OneSignalSwiftUIExample" */; + buildConfigurationList = 74CB95FE4C8E95D018B0A01A /* Build configuration list for PBXProject "OneSignalSwiftUIExample" */; + compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - en, Base, + en, ); - mainGroup = 3C25C03A2F33E016005E5E9A; + mainGroup = 8DB2942ED773E3D98EEC5396; minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; - productRefGroup = 3C25C0442F33E016005E5E9A /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 3C25C0422F33E016005E5E9A /* OneSignalSwiftUIExample */, + F27E45A0AADC4454C26D8C07 /* OneSignalSwiftUIExample */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 3C25C0412F33E016005E5E9A /* Resources */ = { + 8B945C9AF93265E68BBE57A8 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3C25C0552F33E031005E5E9A /* Assets.xcassets in Resources */, + 25D4FC203BE01E9A35ACA465 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 3C25C03F2F33E016005E5E9A /* Sources */ = { + 1916C53ACCF1871776E6A261 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3C25C0562F33E031005E5E9A /* ContentView.swift in Sources */, - 3C25C0572F33E031005E5E9A /* OneSignalSwiftUIExampleApp.swift in Sources */, + 2E6D6E17EEE5D42AE3CAB9EA /* AddItemSheet.swift in Sources */, + 74920778F254B9DA27906AA3 /* AppInfoSection.swift in Sources */, + 644520A37F05E25FD17F5CF9 /* AppModels.swift in Sources */, + E26208CEB7DC4D217EAAE4E6 /* ContentView.swift in Sources */, + B5C4A4AC0A2D2AF73C9A7DD3 /* KeyValueRow.swift in Sources */, + AD19D28FDE8DAA7C8BC87939 /* LocationSection.swift in Sources */, + 5EEA4B007D60765502F2A6E5 /* MessagingSection.swift in Sources */, + B13E9460BDD0553FF05542A0 /* NotificationGrid.swift in Sources */, + C9884D12CEDE50ACAE38DF0D /* NotificationSection.swift in Sources */, + CB5F82751F156695A7A03338 /* OneSignalService.swift in Sources */, + 1D68D16D167B951BD57386ED /* OneSignalSwiftUIExampleApp.swift in Sources */, + 2F52BD9F2A3002390A7C8896 /* OneSignalViewModel.swift in Sources */, + 3C25C0592F3409E9005E5E9A /* NotificationSender.swift in Sources */, + 49E74AFBFBE23D06B7D310EC /* SubscriptionSection.swift in Sources */, + DB2BA39FE32DB3D52154F48C /* TagsSection.swift in Sources */, + D13A25FE5762A23125227A84 /* ToastView.swift in Sources */, + E1BEF5E30555F0ADCBF62B7D /* UserSection.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - 3C25C04C2F33E019005E5E9A /* Debug */ = { + 09FF5B5EE6F70E991CEAE373 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; @@ -169,19 +377,20 @@ 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; - DEVELOPMENT_TEAM = 99SW8E36CT; + DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; + 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)", + "DEBUG=1", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -189,25 +398,49 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.5; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; - 3C25C04D2F33E019005E5E9A /* Release */ = { + 70CAD4886778AACB94FDDB37 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = OneSignalSwiftUIExample.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = 99SW8E36CT; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = OneSignalSwiftUIExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example; + SDKROOT = iphoneos; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 81971FC2118996AD12380AFF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; @@ -233,13 +466,14 @@ 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"; - DEVELOPMENT_TEAM = 99SW8E36CT; + DEVELOPMENT_TEAM = ""; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; + 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; @@ -247,94 +481,62 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.5; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - VALIDATE_PRODUCT = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; }; name = Release; }; - 3C25C04F2F33E019005E5E9A /* Debug */ = { + 8BDA36EF0097197153C97356 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CODE_SIGN_ENTITLEMENTS = OneSignalSwiftUIExample.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_TEAM = 99SW8E36CT; 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"; + INFOPLIST_FILE = OneSignalSwiftUIExample/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.OneSignalSwiftUIExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example; + SDKROOT = iphoneos; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - 3C25C0502F33E019005E5E9A /* 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_TEAM = 99SW8E36CT; - 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.onesignal.OneSignalSwiftUIExample; - 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 */ - 3C25C03E2F33E016005E5E9A /* Build configuration list for PBXProject "OneSignalSwiftUIExample" */ = { + 74CB95FE4C8E95D018B0A01A /* Build configuration list for PBXProject "OneSignalSwiftUIExample" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3C25C04C2F33E019005E5E9A /* Debug */, - 3C25C04D2F33E019005E5E9A /* Release */, + 09FF5B5EE6F70E991CEAE373 /* Debug */, + 81971FC2118996AD12380AFF /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; - 3C25C04E2F33E019005E5E9A /* Build configuration list for PBXNativeTarget "OneSignalSwiftUIExample" */ = { + 94A2EFFFED81A4B2280CE916 /* Build configuration list for PBXNativeTarget "OneSignalSwiftUIExample" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3C25C04F2F33E019005E5E9A /* Debug */, - 3C25C0502F33E019005E5E9A /* Release */, + 8BDA36EF0097197153C97356 /* Debug */, + 70CAD4886778AACB94FDDB37 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ }; - rootObject = 3C25C03B2F33E016005E5E9A /* Project object */; + rootObject = 7B7016AA7DF5AB54CD96701C /* Project object */; } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/xcshareddata/xcschemes/OneSignalSwiftUIExample.xcscheme b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/xcshareddata/xcschemes/OneSignalSwiftUIExample.xcscheme index 8dcded243..c1bea564d 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/xcshareddata/xcschemes/OneSignalSwiftUIExample.xcscheme +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/xcshareddata/xcschemes/OneSignalSwiftUIExample.xcscheme @@ -1,11 +1,11 @@ + runPostActionsOnFailure = "NO"> @@ -28,7 +28,18 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" - shouldAutocreateTestPlan = "YES"> + onlyGenerateCoverageForSpecifiedTargets = "NO"> + + + + + + + + + + diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift new file mode 100644 index 000000000..daefaa0ef --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift @@ -0,0 +1,135 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI +import OneSignalFramework + +@main +struct OneSignalSwiftUIExampleApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @StateObject private var viewModel = OneSignalViewModel() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(viewModel) + } + } +} + +// MARK: - App Delegate + +class AppDelegate: NSObject, UIApplicationDelegate { + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + // Initialize OneSignal + OneSignalService.shared.initialize(launchOptions: launchOptions) + + // Set up notification lifecycle listeners + setupNotificationListeners() + + // Set up in-app message listeners + setupInAppMessageListeners() + + return true + } + + private func setupNotificationListeners() { + // Foreground notification display + OneSignal.Notifications.addForegroundLifecycleListener(NotificationLifecycleHandler.shared) + + // Notification click handling + OneSignal.Notifications.addClickListener(NotificationClickHandler.shared) + } + + private func setupInAppMessageListeners() { + // In-app message lifecycle + OneSignal.InAppMessages.addLifecycleListener(InAppMessageLifecycleHandler.shared) + + // In-app message click handling + OneSignal.InAppMessages.addClickListener(InAppMessageClickHandler.shared) + + // Start with IAM paused + OneSignal.InAppMessages.paused = true + } +} + +// MARK: - Notification Handlers + +class NotificationLifecycleHandler: NSObject, OSNotificationLifecycleListener { + static let shared = NotificationLifecycleHandler() + + func onWillDisplay(event: OSNotificationWillDisplayEvent) { + print("[OneSignal] Notification will display: \(event.notification.title ?? "No title")") + // Optionally modify display behavior + // event.preventDefault() // Prevent automatic display + // event.notification.display() // Manually display later + } +} + +class NotificationClickHandler: NSObject, OSNotificationClickListener { + static let shared = NotificationClickHandler() + + func onClick(event: OSNotificationClickEvent) { + print("[OneSignal] Notification clicked: \(event.notification.title ?? "No title")") + // Handle notification click - navigate to specific screen, etc. + } +} + +// MARK: - In-App Message Handlers + +class InAppMessageLifecycleHandler: NSObject, OSInAppMessageLifecycleListener { + static let shared = InAppMessageLifecycleHandler() + + func onWillDisplay(event: OSInAppMessageWillDisplayEvent) { + print("[OneSignal] IAM will display: \(event.message.messageId)") + } + + func onDidDisplay(event: OSInAppMessageDidDisplayEvent) { + print("[OneSignal] IAM did display: \(event.message.messageId)") + } + + func onWillDismiss(event: OSInAppMessageWillDismissEvent) { + print("[OneSignal] IAM will dismiss: \(event.message.messageId)") + } + + func onDidDismiss(event: OSInAppMessageDidDismissEvent) { + print("[OneSignal] IAM did dismiss: \(event.message.messageId)") + } +} + +class InAppMessageClickHandler: NSObject, OSInAppMessageClickListener { + static let shared = InAppMessageClickHandler() + + func onClick(event: OSInAppMessageClickEvent) { + print("[OneSignal] IAM clicked: \(event.result.actionId ?? "No action ID")") + // Handle IAM click - navigate, track event, etc. + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json index eb8789700..2c54006ed 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,33 @@ { "colors" : [ { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4D", + "green" : "0x4B", + "red" : "0xE5" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x6D", + "green" : "0x6B", + "red" : "0xF5" + } + }, "idiom" : "universal" } ], diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json index 230588010..13613e3ee 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -4,28 +4,6 @@ "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" } ], "info" : { diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ContentView.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ContentView.swift deleted file mode 100644 index 7be85958c..000000000 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ContentView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ContentView.swift -// OneSignalSwiftUIExample -// -// Created by Nan Li on 2/4/26. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - } -} - -#Preview { - ContentView() -} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist new file mode 100644 index 000000000..994a1f9a2 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist @@ -0,0 +1,56 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSLocationAlwaysAndWhenInUseUsageDescription + This app uses your location to provide location-based notifications and services. + NSLocationWhenInUseUsageDescription + This app uses your location to provide location-based notifications. + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UIBackgroundModes + + remote-notification + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift new file mode 100644 index 000000000..b9a802baf --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift @@ -0,0 +1,146 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation +import UIKit + +// MARK: - Key-Value Item + +/// A generic key-value pair used for aliases, tags, and triggers +struct KeyValueItem: Identifiable, Equatable { + let id = UUID() + let key: String + let value: String +} + +// MARK: - Notification Type + +/// Types of test push notifications that can be sent +enum NotificationType: String, CaseIterable, Identifiable { + case general = "General" + case greetings = "Greetings" + case promotions = "Promotions" + case breakingNews = "Breaking News" + case abandonedCart = "Abandoned Cart" + case newPost = "New Post" + case reEngagement = "Re-Engagement" + case rating = "Rating" + + var id: String { rawValue } + + var iconName: String { + switch self { + case .general: return "bell.fill" + case .greetings: return "hand.wave.fill" + case .promotions: return "tag.fill" + case .breakingNews: return "newspaper.fill" + case .abandonedCart: return "cart.fill" + case .newPost: return "photo.fill" + case .reEngagement: return "hand.tap.fill" + case .rating: return "star.fill" + } + } +} + +// MARK: - In-App Message Type + +/// Types of in-app messages that can be displayed +enum InAppMessageType: String, CaseIterable, Identifiable { + case topBanner = "Top Banner" + case bottomBanner = "Bottom Banner" + case centerModal = "Center Modal" + case fullScreen = "Full Screen" + + var id: String { rawValue } + + var iconName: String { + switch self { + case .topBanner: return "rectangle.topthird.inset.filled" + case .bottomBanner: return "rectangle.bottomthird.inset.filled" + case .centerModal: return "rectangle.center.inset.filled" + case .fullScreen: return "rectangle.inset.filled" + } + } +} + +// MARK: - Add Item Type + +/// Types of items that can be added via the add sheet +enum AddItemType { + case alias + case email + case sms + case tag + case trigger + case externalUserId + + var title: String { + switch self { + case .alias: return "Add Alias" + case .email: return "Add Email" + case .sms: return "Add SMS" + case .tag: return "Add Tag" + case .trigger: return "Add Trigger" + case .externalUserId: return "Login User" + } + } + + var requiresKeyValue: Bool { + switch self { + case .alias, .tag, .trigger: return true + case .email, .sms, .externalUserId: return false + } + } + + var keyPlaceholder: String { + switch self { + case .alias: return "Alias Label" + case .tag: return "Tag Key" + case .trigger: return "Trigger Key" + default: return "Key" + } + } + + var valuePlaceholder: String { + switch self { + case .alias: return "Alias ID" + case .email: return "email@example.com" + case .sms: return "+1234567890" + case .tag: return "Tag Value" + case .trigger: return "Trigger Value" + case .externalUserId: return "External User ID" + } + } + + var keyboardType: UIKeyboardType { + switch self { + case .email: return .emailAddress + case .sms: return .phonePad + default: return .default + } + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/OneSignalSwiftUIExampleApp.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/OneSignalSwiftUIExampleApp.swift deleted file mode 100644 index f2c416246..000000000 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/OneSignalSwiftUIExampleApp.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// OneSignalSwiftUIExampleApp.swift -// OneSignalSwiftUIExample -// -// Created by Nan Li on 2/4/26. -// - -import SwiftUI - -@main -struct OneSignalSwiftUIExampleApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/NotificationSender.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/NotificationSender.swift new file mode 100644 index 000000000..2a3c96cc4 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/NotificationSender.swift @@ -0,0 +1,368 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation +import OneSignalFramework + +/// Service for sending push notifications via OneSignal API +/// Note: This is for demo purposes only. In production, API calls should be made from your backend. +final class NotificationSender { + + static let shared = NotificationSender() + + private let apiURL = URL(string: "https://onesignal.com/api/v1/notifications")! + + private init() {} + + /// Send a notification to this device through the OneSignal Platform. + /// Note: This form of API should not be used in production as it is not safe. + /// The device should make an API call to its own backend, which handles the OneSignal API call. + func sendNotification( + type: NotificationType, + appId: String, + completion: @escaping (Result) -> Void + ) { + guard let subscriptionId = OneSignal.User.pushSubscription.id else { + completion(.failure(NotificationError.noSubscriptionId)) + return + } + + guard OneSignal.User.pushSubscription.optedIn else { + completion(.failure(NotificationError.notOptedIn)) + return + } + + let templateData = type.getNextTemplate() + + var payload: [String: Any] = [ + "app_id": appId, + "include_subscription_ids": [subscriptionId], + "contents": ["en": templateData.message], + "ios_sound": "nil" + ] + + // Add title if present + if !templateData.title.isEmpty { + payload["headings"] = ["en": templateData.title] + } + + // Add large icon if present + if !templateData.largeIconUrl.isEmpty { + payload["ios_attachments"] = ["icon": templateData.largeIconUrl] + } + + // Add big picture if present + if !templateData.bigPictureUrl.isEmpty { + payload["big_picture"] = templateData.bigPictureUrl + } + + // Add buttons for Breaking News + if type == .breakingNews { + payload["buttons"] = [ + ["id": "view", "text": "View"], + ["id": "save", "text": "Save"], + ["id": "share", "text": "Share"] + ] + } + + // Add thread/group ID for notification grouping + payload["thread_id"] = type.rawValue + + sendRequest(payload: payload, completion: completion) + } + + private func sendRequest( + payload: [String: Any], + completion: @escaping (Result) -> Void + ) { + var request = URLRequest(url: apiURL) + request.httpMethod = "POST" + request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type") + request.setValue("application/vnd.onesignal.v1+json", forHTTPHeaderField: "Accept") + request.timeoutInterval = 30 + + do { + request.httpBody = try JSONSerialization.data(withJSONObject: payload) + } catch { + completion(.failure(error)) + return + } + + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + print("💛 [OneSignal] Failed to send notification: \(error.localizedDescription)") + completion(.failure(error)) + return + } + + if let httpResponse = response as? HTTPURLResponse { + if httpResponse.statusCode == 200 || httpResponse.statusCode == 202 { + if let data = data, let responseStr = String(data: data, encoding: .utf8) { + print("💛 [OneSignal] Success sending notification: \(responseStr)") + } + completion(.success(())) + } else { + if let data = data, let responseStr = String(data: data, encoding: .utf8) { + print("💛 [OneSignal] Failed to send notification (\(httpResponse.statusCode)): \(responseStr)") + } + completion(.failure(NotificationError.apiError(statusCode: httpResponse.statusCode))) + } + } + }.resume() + } +} + +// MARK: - Errors + +enum NotificationError: LocalizedError { + case noSubscriptionId + case notOptedIn + case apiError(statusCode: Int) + + var errorDescription: String? { + switch self { + case .noSubscriptionId: + return "No push subscription ID available" + case .notOptedIn: + return "Push notifications not opted in" + case .apiError(let statusCode): + return "API error with status code: \(statusCode)" + } + } +} + +// MARK: - Notification Template Data + +struct NotificationTemplateData { + let title: String + let message: String + let largeIconUrl: String + let bigPictureUrl: String +} + +// MARK: - Notification Data (matching Android NotificationData.java) + +extension NotificationType { + + private static var templatePositions: [NotificationType: Int] = [:] + + /// Get the next template data, cycling through available templates + func getNextTemplate() -> NotificationTemplateData { + let templates = self.templates + var pos = NotificationType.templatePositions[self] ?? 0 + + let template = templates[pos] + + pos += 1 + if pos >= templates.count { + pos = 0 + } + NotificationType.templatePositions[self] = pos + + return template + } + + private var templates: [NotificationTemplateData] { + switch self { + case .general: + return [ + NotificationTemplateData( + title: "Liked post", + message: "Michael DiCioccio liked your post!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fbell_red.png?alt=media&token=c80c4e76-1fd7-4912-93f4-f1aee1d98b20", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "Birthdays", + message: "Say happy birthday to Rodrigo and 5 others!", + largeIconUrl: "https://images.vexels.com/media/users/3/147226/isolated/preview/068af50eededd7a739aac52d8e509ab5-three-candles-birthday-cake-icon-by-vexels.png", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "New Post", + message: "Neil just posted for the first time in a while, check it out!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fbell_red.png?alt=media&token=c80c4e76-1fd7-4912-93f4-f1aee1d98b20", + bigPictureUrl: "" + ) + ] + + case .greetings: + return [ + NotificationTemplateData( + title: "", + message: "Welcome to Nike!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fhuman-greeting-red.png?alt=media&token=cb9f3418-db61-443c-955a-57e664d30271", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "Welcome to Adidas!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fhuman-greeting-red.png?alt=media&token=cb9f3418-db61-443c-955a-57e664d30271", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "Welcome to Sandra's cooking blog!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fhuman-greeting-red.png?alt=media&token=cb9f3418-db61-443c-955a-57e664d30271", + bigPictureUrl: "" + ) + ] + + case .promotions: + return [ + NotificationTemplateData( + title: "", + message: "Get 20% off site-wide!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fbrightness-percent-red.png?alt=media&token=9e43c45e-8bcc-413e-8a42-612020c406ba", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "Half-off all shoes today only!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fbrightness-percent-red.png?alt=media&token=9e43c45e-8bcc-413e-8a42-612020c406ba", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "3 hour flash sale!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fbrightness-percent-red.png?alt=media&token=9e43c45e-8bcc-413e-8a42-612020c406ba", + bigPictureUrl: "" + ) + ] + + case .breakingNews: + return [ + NotificationTemplateData( + title: "The rap game won't be the same", + message: "Nipsey Hussle shot dead in his own hometown!", + largeIconUrl: "https://pbs.twimg.com/profile_images/719602655337656321/kQUzR2Es_400x400.jpg", + bigPictureUrl: "https://lab.fm/wp-content/uploads/2019/04/nipsey-hussle-cipriani-diamond-ball-2018-nyc-credit-jstone-shutterstock@1800x1013.jpg" + ), + NotificationTemplateData( + title: "CNN being bought by Fox?", + message: "Fox has shown an increasing interest in purchasing CNN and because of some other deals this year it could actually happen!", + largeIconUrl: "https://www.thewrap.com/sites/default/wp-content/uploads/files/2013/Jul/08/101771/gallupinside.png", + bigPictureUrl: "https://i.ytimg.com/vi/C8YBKBuX43Q/maxresdefault.jpg" + ), + NotificationTemplateData( + title: "Tesla's next venture!", + message: "Tesla releasing fully autonomous driving service!", + largeIconUrl: "https://i.etsystatic.com/13567406/r/il/6657a5/1083941709/il_794xN.1083941709_k3vi.jpg", + bigPictureUrl: "https://electrek.co/wp-content/uploads/sites/3/2018/01/screen-shot-2018-01-04-at-12-59-25-pm.jpg?quality=82&strip=all&w=1600" + ) + ] + + case .abandonedCart: + return [ + NotificationTemplateData( + title: "", + message: "You have some shoes left in your cart!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fcart-red.png?alt=media&token=3e9ca206-540c-4275-8f21-1840e9cba930", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "Still want to buy the dress you saw?", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fcart-red.png?alt=media&token=3e9ca206-540c-4275-8f21-1840e9cba930", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "20% off the shoes you saw today.", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fcart-red.png?alt=media&token=3e9ca206-540c-4275-8f21-1840e9cba930", + bigPictureUrl: "" + ) + ] + + case .newPost: + return [ + NotificationTemplateData( + title: "", + message: "I just published a new blog post!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fimage-red.png?alt=media&token=3f44fd3d-27a5-4d05-9544-423edf2f6284", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "Come check out my new blog post on aliens!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fimage-red.png?alt=media&token=3f44fd3d-27a5-4d05-9544-423edf2f6284", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "10 places you have to see before you die.", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fimage-red.png?alt=media&token=3f44fd3d-27a5-4d05-9544-423edf2f6284", + bigPictureUrl: "" + ) + ] + + case .reEngagement: + return [ + NotificationTemplateData( + title: "", + message: "Your friend George just joined Facebook", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fgesture-tap-red.png?alt=media&token=8ea7f6db-18e4-4fdd-aabf-ac97f04522fd", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "Can you beat level 23?", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fgesture-tap-red.png?alt=media&token=8ea7f6db-18e4-4fdd-aabf-ac97f04522fd", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "Check out our Fall collection!", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fgesture-tap-red.png?alt=media&token=8ea7f6db-18e4-4fdd-aabf-ac97f04522fd", + bigPictureUrl: "" + ) + ] + + case .rating: + return [ + NotificationTemplateData( + title: "", + message: "How was your food/experience at Chipotle?", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fstar-red.png?alt=media&token=e18e99ce-96ad-4ee5-b0b9-40c7f90613d1", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "Rate your experience with Amazon.", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fstar-red.png?alt=media&token=e18e99ce-96ad-4ee5-b0b9-40c7f90613d1", + bigPictureUrl: "" + ), + NotificationTemplateData( + title: "", + message: "Let your Lyft driver know how the ride was.", + largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fstar-red.png?alt=media&token=e18e99ce-96ad-4ee5-b0b9-40c7f90613d1", + bigPictureUrl: "" + ) + ] + } + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift new file mode 100644 index 000000000..2a451ec44 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift @@ -0,0 +1,232 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation +import OneSignalFramework + +/// Service layer that wraps OneSignal SDK calls +final class OneSignalService { + + // MARK: - Singleton + + static let shared = OneSignalService() + + private init() {} + + // MARK: - App ID + + private let appIdKey = "OneSignalAppId" + private let defaultAppId = "77e32082-ea27-42e3-a898-c72e141824ef" + + var appId: String { + get { + UserDefaults.standard.string(forKey: appIdKey) ?? defaultAppId + } + set { + UserDefaults.standard.set(newValue, forKey: appIdKey) + } + } + + // MARK: - Initialization + + func initialize(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { + OneSignal.Debug.setLogLevel(.LL_VERBOSE) + OneSignal.initialize(appId, withLaunchOptions: launchOptions) + } + + // MARK: - Consent + + func setConsentRequired(_ required: Bool) { + OneSignal.setConsentRequired(required) + } + + func setConsentGiven(_ granted: Bool) { + OneSignal.setConsentGiven(granted) + } + + func revokeConsent() { + OneSignal.setConsentGiven(false) + } + + // MARK: - User Management + + func login(externalId: String) { + OneSignal.login(externalId) + } + + func logout() { + OneSignal.logout() + } + + // MARK: - Aliases + + func addAlias(label: String, id: String) { + OneSignal.User.addAlias(label: label, id: id) + } + + func removeAlias(_ label: String) { + OneSignal.User.removeAlias(label) + } + + // MARK: - Push Subscription + + var pushSubscriptionId: String? { + OneSignal.User.pushSubscription.id + } + + var isPushEnabled: Bool { + OneSignal.User.pushSubscription.optedIn + } + + func optInPush() { + OneSignal.User.pushSubscription.optIn() + } + + func optOutPush() { + OneSignal.User.pushSubscription.optOut() + } + + func requestPushPermission(completion: @escaping (Bool) -> Void) { + OneSignal.Notifications.requestPermission({ accepted in + completion(accepted) + }, fallbackToSettings: true) + } + + // MARK: - Email + + func addEmail(_ email: String) { + OneSignal.User.addEmail(email) + } + + func removeEmail(_ email: String) { + OneSignal.User.removeEmail(email) + } + + // MARK: - SMS + + func addSms(_ number: String) { + OneSignal.User.addSms(number) + } + + func removeSms(_ number: String) { + OneSignal.User.removeSms(number) + } + + // MARK: - Tags + + func addTag(key: String, value: String) { + OneSignal.User.addTag(key: key, value: value) + } + + func removeTag(_ key: String) { + OneSignal.User.removeTag(key) + } + + func getTags() -> [String: String] { + OneSignal.User.getTags() + } + + // MARK: - Outcomes + + func sendOutcome(_ name: String) { + OneSignal.Session.addOutcome(name) + } + + func sendOutcome(_ name: String, value: NSNumber) { + OneSignal.Session.addOutcome(name, value) + } + + func sendUniqueOutcome(_ name: String) { + OneSignal.Session.addUniqueOutcome(name) + } + + // MARK: - In-App Messages + + var isInAppMessagesPaused: Bool { + get { OneSignal.InAppMessages.paused } + set { OneSignal.InAppMessages.paused = newValue } + } + + func addTrigger(key: String, value: String) { + OneSignal.InAppMessages.addTrigger(key, withValue: value) + } + + func removeTrigger(_ key: String) { + OneSignal.InAppMessages.removeTrigger(key) + } + + // MARK: - Location + + var isLocationShared: Bool { + get { OneSignal.Location.isShared } + set { OneSignal.Location.isShared = newValue } + } + + func requestLocationPermission() { + OneSignal.Location.requestPermission() + } + + // MARK: - Notifications + + func clearAllNotifications() { + OneSignal.Notifications.clearAll() + } + + var hasNotificationPermission: Bool { + OneSignal.Notifications.permission + } + + // MARK: - Observers + + func addPushSubscriptionObserver(_ observer: OSPushSubscriptionObserver) { + OneSignal.User.pushSubscription.addObserver(observer) + } + + func addUserObserver(_ observer: OSUserStateObserver) { + OneSignal.User.addObserver(observer) + } + + func addPermissionObserver(_ observer: OSNotificationPermissionObserver) { + OneSignal.Notifications.addPermissionObserver(observer) + } + + func addNotificationClickListener(_ listener: OSNotificationClickListener) { + OneSignal.Notifications.addClickListener(listener) + } + + func addNotificationLifecycleListener(_ listener: OSNotificationLifecycleListener) { + OneSignal.Notifications.addForegroundLifecycleListener(listener) + } + + func addInAppMessageClickListener(_ listener: OSInAppMessageClickListener) { + OneSignal.InAppMessages.addClickListener(listener) + } + + func addInAppMessageLifecycleListener(_ listener: OSInAppMessageLifecycleListener) { + OneSignal.InAppMessages.addLifecycleListener(listener) + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift new file mode 100644 index 000000000..7c1d3719e --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift @@ -0,0 +1,357 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation +import Combine +import OneSignalFramework + +/// Main ViewModel managing all OneSignal SDK state and interactions +@MainActor +final class OneSignalViewModel: ObservableObject { + + // MARK: - Published Properties + + // App Info + @Published var appId: String + + // User + @Published var externalUserId: String? + @Published var aliases: [KeyValueItem] = [] + + // Push Subscription + @Published var pushSubscriptionId: String? + @Published var isPushEnabled: Bool = false + + // Email & SMS + @Published var emails: [String] = [] + @Published var smsNumbers: [String] = [] + + // Tags + @Published var tags: [KeyValueItem] = [] + + // In-App Messaging + @Published var isInAppMessagesPaused: Bool = true + @Published var triggers: [KeyValueItem] = [] + + // Location + @Published var isLocationShared: Bool = false + + // UI State + @Published var showingAddSheet: Bool = false + @Published var addItemType: AddItemType = .email + @Published var toastMessage: String? + + // MARK: - Private Properties + + private let service: OneSignalService + private var observers = Observers() + + // MARK: - Initialization + + init(service: OneSignalService = .shared) { + self.service = service + self.appId = service.appId + + // Initial state sync + refreshState() + + // Set up observers + setupObservers() + } + + // MARK: - State Management + + func refreshState() { + pushSubscriptionId = service.pushSubscriptionId + isPushEnabled = service.isPushEnabled + isInAppMessagesPaused = service.isInAppMessagesPaused + isLocationShared = service.isLocationShared + + // Sync tags from SDK + let sdkTags = service.getTags() + tags = sdkTags.map { KeyValueItem(key: $0.key, value: $0.value) } + } + + // MARK: - Consent + + func revokeConsent() { + service.revokeConsent() + showToast("Consent revoked") + } + + // MARK: - User Management + + func login(externalId: String) { + service.login(externalId: externalId) + externalUserId = externalId + showToast("Logged in as \(externalId)") + } + + func logout() { + service.logout() + externalUserId = nil + aliases.removeAll() + emails.removeAll() + smsNumbers.removeAll() + tags.removeAll() + triggers.removeAll() + showToast("Logged out") + } + + // MARK: - Aliases + + func addAlias(label: String, id: String) { + service.addAlias(label: label, id: id) + aliases.append(KeyValueItem(key: label, value: id)) + showToast("Alias added") + } + + func removeAlias(_ item: KeyValueItem) { + service.removeAlias(item.key) + aliases.removeAll { $0.id == item.id } + showToast("Alias removed") + } + + // MARK: - Push Subscription + + func togglePushEnabled() { + if isPushEnabled { + service.optOutPush() + isPushEnabled = false + showToast("Push disabled") + } else { + service.optInPush() + isPushEnabled = true + showToast("Push enabled") + } + } + + func requestPushPermission() { + service.requestPushPermission { [weak self] accepted in + Task { @MainActor in + self?.isPushEnabled = accepted + self?.showToast(accepted ? "Push permission granted" : "Push permission denied") + } + } + } + + // MARK: - Email + + func addEmail(_ email: String) { + service.addEmail(email) + emails.append(email) + showToast("Email added") + } + + func removeEmail(_ email: String) { + service.removeEmail(email) + emails.removeAll { $0 == email } + showToast("Email removed") + } + + // MARK: - SMS + + func addSms(_ number: String) { + service.addSms(number) + smsNumbers.append(number) + showToast("SMS added") + } + + func removeSms(_ number: String) { + service.removeSms(number) + smsNumbers.removeAll { $0 == number } + showToast("SMS removed") + } + + // MARK: - Tags + + func addTag(key: String, value: String) { + service.addTag(key: key, value: value) + // Remove existing tag with same key if present + tags.removeAll { $0.key == key } + tags.append(KeyValueItem(key: key, value: value)) + showToast("Tag added") + } + + func removeTag(_ item: KeyValueItem) { + service.removeTag(item.key) + tags.removeAll { $0.id == item.id } + showToast("Tag removed") + } + + // MARK: - Outcomes + + func sendOutcome(_ name: String) { + service.sendOutcome(name) + showToast("Outcome '\(name)' sent") + } + + func sendOutcome(_ name: String, value: Double) { + service.sendOutcome(name, value: NSNumber(value: value)) + showToast("Outcome '\(name)' with value \(value) sent") + } + + func sendUniqueOutcome(_ name: String) { + service.sendUniqueOutcome(name) + showToast("Unique outcome '\(name)' sent") + } + + // MARK: - In-App Messaging + + func toggleInAppMessagesPaused() { + isInAppMessagesPaused.toggle() + service.isInAppMessagesPaused = isInAppMessagesPaused + showToast(isInAppMessagesPaused ? "In-app messages paused" : "In-app messages resumed") + } + + func addTrigger(key: String, value: String) { + service.addTrigger(key: key, value: value) + // Remove existing trigger with same key if present + triggers.removeAll { $0.key == key } + triggers.append(KeyValueItem(key: key, value: value)) + showToast("Trigger added") + } + + func removeTrigger(_ item: KeyValueItem) { + service.removeTrigger(item.key) + triggers.removeAll { $0.id == item.id } + showToast("Trigger removed") + } + + // MARK: - Location + + func toggleLocationShared() { + isLocationShared.toggle() + service.isLocationShared = isLocationShared + showToast(isLocationShared ? "Location sharing enabled" : "Location sharing disabled") + } + + func promptLocation() { + service.requestLocationPermission() + showToast("Location permission requested") + } + + // MARK: - Notifications + + func clearAllNotifications() { + service.clearAllNotifications() + showToast("All notifications cleared") + } + + func sendTestNotification(_ type: NotificationType) { + showToast("Sending '\(type.rawValue)' notification...") + + NotificationSender.shared.sendNotification(type: type, appId: appId) { [weak self] result in + Task { @MainActor in + switch result { + case .success: + self?.showToast("'\(type.rawValue)' notification sent!") + case .failure(let error): + self?.showToast("Failed: \(error.localizedDescription)") + } + } + } + } + + func sendTestInAppMessage(_ type: InAppMessageType) { + // In a real app, this would trigger an IAM via your backend + // For demo purposes, we just show a toast + showToast("Test '\(type.rawValue)' in-app message triggered") + } + + // MARK: - Add Sheet + + func showAddSheet(for type: AddItemType) { + addItemType = type + showingAddSheet = true + } + + func handleAddItem(key: String, value: String) { + switch addItemType { + case .alias: + addAlias(label: key, id: value) + case .email: + addEmail(value) + case .sms: + addSms(value) + case .tag: + addTag(key: key, value: value) + case .trigger: + addTrigger(key: key, value: value) + case .externalUserId: + login(externalId: value) + } + showingAddSheet = false + } + + // MARK: - Toast + + private func showToast(_ message: String) { + toastMessage = message + + // Auto-dismiss after 2 seconds + Task { + try? await Task.sleep(nanoseconds: 2_000_000_000) + toastMessage = nil + } + } + + // MARK: - Observers + + private func setupObservers() { + observers.viewModel = self + service.addPushSubscriptionObserver(observers) + service.addUserObserver(observers) + service.addPermissionObserver(observers) + } +} + +// MARK: - Observer Classes + +private class Observers: NSObject, OSPushSubscriptionObserver, OSUserStateObserver, OSNotificationPermissionObserver { + weak var viewModel: OneSignalViewModel? + + func onPushSubscriptionDidChange(state: OSPushSubscriptionChangedState) { + Task { @MainActor in + viewModel?.pushSubscriptionId = state.current.id + viewModel?.isPushEnabled = state.current.optedIn + } + } + + func onUserStateDidChange(state: OSUserChangedState) { + Task { @MainActor in + // User state changed - could refresh aliases, etc. + print("User state changed: \(state.jsonRepresentation())") + } + } + + func onNotificationPermissionDidChange(_ permission: Bool) { + Task { @MainActor in + viewModel?.isPushEnabled = permission && (viewModel?.isPushEnabled ?? false) + } + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift new file mode 100644 index 000000000..1df25d143 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift @@ -0,0 +1,175 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A sheet for adding items with one or two text fields (dialog style matching screenshots) +struct AddItemSheet: View { + let itemType: AddItemType + let onAdd: (String, String) -> Void + let onCancel: () -> Void + + @State private var keyText: String = "" + @State private var valueText: String = "" + @FocusState private var focusedField: Field? + + private enum Field { + case key, value + } + + var body: some View { + NavigationStack { + VStack(spacing: 24) { + // Title + Text(itemType.title) + .font(.title2) + .fontWeight(.semibold) + .frame(maxWidth: .infinity, alignment: .leading) + + // Input Fields + if itemType.requiresKeyValue { + VStack(alignment: .leading, spacing: 8) { + Text("Key") + .font(.caption) + .foregroundColor(.secondary) + TextField(itemType.keyPlaceholder, text: $keyText) + .textFieldStyle(UnderlineTextFieldStyle()) + .focused($focusedField, equals: .key) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + } + + VStack(alignment: .leading, spacing: 8) { + Text("Value") + .font(.caption) + .foregroundColor(.secondary) + TextField(itemType.valuePlaceholder, text: $valueText) + .textFieldStyle(UnderlineTextFieldStyle()) + .focused($focusedField, equals: .value) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .keyboardType(itemType.keyboardType) + } + } else { + VStack(alignment: .leading, spacing: 8) { + Text(singleFieldLabel) + .font(.caption) + .foregroundColor(.secondary) + TextField(itemType.valuePlaceholder, text: $valueText) + .textFieldStyle(UnderlineTextFieldStyle()) + .focused($focusedField, equals: .value) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .keyboardType(itemType.keyboardType) + } + } + + Spacer() + + // Action Buttons + HStack(spacing: 24) { + Spacer() + + Button("CANCEL") { + onCancel() + } + .foregroundColor(.accentColor) + + Button(itemType == .externalUserId ? "LOGIN" : "ADD") { + onAdd(keyText, valueText) + } + .foregroundColor(isValid ? .accentColor : .gray) + .disabled(!isValid) + } + .font(.system(size: 16, weight: .semibold)) + } + .padding(24) + .onAppear { + focusedField = itemType.requiresKeyValue ? .key : .value + } + } + .presentationDetents([.medium]) + .presentationDragIndicator(.visible) + } + + private var singleFieldLabel: String { + switch itemType { + case .email: return "New Email" + case .sms: return "New SMS" + case .externalUserId: return "External User Id" + default: return "Value" + } + } + + private var isValid: Bool { + if itemType.requiresKeyValue { + return !keyText.trimmingCharacters(in: .whitespaces).isEmpty && + !valueText.trimmingCharacters(in: .whitespaces).isEmpty + } else { + return !valueText.trimmingCharacters(in: .whitespaces).isEmpty + } + } +} + +/// A text field style with an underline instead of a border +struct UnderlineTextFieldStyle: TextFieldStyle { + func _body(configuration: TextField) -> some View { + VStack(spacing: 0) { + configuration + .font(.system(size: 17)) + .padding(.vertical, 8) + + Rectangle() + .fill(Color(.separator)) + .frame(height: 1) + } + } +} + +#Preview("Add Alias") { + AddItemSheet( + itemType: .alias, + onAdd: { key, value in print("Add: \(key) = \(value)") }, + onCancel: { print("Cancel") } + ) +} + +#Preview("Add Email") { + AddItemSheet( + itemType: .email, + onAdd: { _, value in print("Add: \(value)") }, + onCancel: { print("Cancel") } + ) +} + +#Preview("Login User") { + AddItemSheet( + itemType: .externalUserId, + onAdd: { _, value in print("Login: \(value)") }, + onCancel: { print("Cancel") } + ) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift new file mode 100644 index 000000000..ae9f25966 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift @@ -0,0 +1,291 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +// MARK: - Action Button Style + +/// A full-width red button with white uppercase text +struct ActionButtonStyle: ButtonStyle { + var isDestructive: Bool = false + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .textCase(.uppercase) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(Color.accentColor.opacity(configuration.isPressed ? 0.8 : 1.0)) + .cornerRadius(8) + } +} + +/// A full-width action button matching the screenshot style +struct ActionButton: View { + let title: String + let action: () -> Void + + var body: some View { + Button(action: action) { + Text(title) + } + .buttonStyle(ActionButtonStyle()) + } +} + +// MARK: - Card Container + +/// A white card container with rounded corners +struct CardContainer: View { + let content: Content + + init(@ViewBuilder content: () -> Content) { + self.content = content() + } + + var body: some View { + VStack(spacing: 0) { + content + } + .background(Color(.systemBackground)) + .cornerRadius(12) + } +} + +// MARK: - Section Header + +/// A small gray section header +struct SectionHeader: View { + let title: String + + var body: some View { + Text(title) + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 4) + .padding(.top, 16) + .padding(.bottom, 8) + } +} + +// MARK: - Key-Value Row + +/// A row displaying a key-value pair with optional delete action +struct KeyValueRow: View { + let item: KeyValueItem + let onDelete: (() -> Void)? + + init(item: KeyValueItem, onDelete: (() -> Void)? = nil) { + self.item = item + self.onDelete = onDelete + } + + var body: some View { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(item.key) + .font(.subheadline) + .foregroundColor(.secondary) + Text(item.value) + .font(.body) + } + + Spacer() + + if let onDelete = onDelete { + Button(action: onDelete) { + Image(systemName: "trash") + .foregroundColor(.red) + } + .buttonStyle(.borderless) + } + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .contentShape(Rectangle()) + } +} + +// MARK: - Single Value Row + +/// A row displaying a single value with optional delete action +struct SingleValueRow: View { + let value: String + let onDelete: (() -> Void)? + + init(value: String, onDelete: (() -> Void)? = nil) { + self.value = value + self.onDelete = onDelete + } + + var body: some View { + HStack { + Text(value) + .font(.body) + + Spacer() + + if let onDelete = onDelete { + Button(action: onDelete) { + Image(systemName: "trash") + .foregroundColor(.red) + } + .buttonStyle(.borderless) + } + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .contentShape(Rectangle()) + } +} + +// MARK: - Info Row + +/// A row displaying a label and value (like "Push-Id: xxx") +struct InfoRow: View { + let label: String + let value: String + let isMonospaced: Bool + + init(label: String, value: String, isMonospaced: Bool = false) { + self.label = label + self.value = value + self.isMonospaced = isMonospaced + } + + var body: some View { + HStack(alignment: .top, spacing: 0) { + Text(label) + .font(.system(size: 15, weight: .medium)) + Text(value) + .font(isMonospaced ? .system(size: 15, design: .monospaced) : .system(size: 15)) + .foregroundColor(.primary) + .lineLimit(1) + .truncationMode(.middle) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + } +} + +// MARK: - Toggle Row + +/// A toggle row with title and optional subtitle +struct ToggleRow: View { + let title: String + let subtitle: String? + @Binding var isOn: Bool + + init(title: String, subtitle: String? = nil, isOn: Binding) { + self.title = title + self.subtitle = subtitle + self._isOn = isOn + } + + var body: some View { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.system(size: 15, weight: .medium)) + if let subtitle = subtitle { + Text(subtitle) + .font(.caption) + .foregroundColor(.secondary) + } + } + + Spacer() + + Toggle("", isOn: $isOn) + .labelsHidden() + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + } +} + +// MARK: - Empty List Row + +/// A placeholder row for empty lists +struct EmptyListRow: View { + let message: String + + var body: some View { + Text(message) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.primary) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.vertical, 16) + } +} + +// MARK: - Divider Line + +/// A subtle divider for card sections +struct CardDivider: View { + var body: some View { + Rectangle() + .fill(Color(.separator)) + .frame(height: 0.5) + } +} + +#Preview { + ScrollView { + VStack(spacing: 16) { + SectionHeader(title: "Key-Value Items") + CardContainer { + KeyValueRow( + item: KeyValueItem(key: "external_id", value: "user_123"), + onDelete: {} + ) + CardDivider() + KeyValueRow( + item: KeyValueItem(key: "subscription_tier", value: "premium") + ) + } + + SectionHeader(title: "Info Rows") + CardContainer { + InfoRow(label: "Push-Id:", value: "77e32082-ea27-42e3-a898-c72e141824ef", isMonospaced: true) + CardDivider() + ToggleRow(title: "Enabled", isOn: .constant(true)) + } + + SectionHeader(title: "Empty") + CardContainer { + EmptyListRow(message: "No Items Added") + } + + ActionButton(title: "Add Item") {} + } + .padding() + } + .background(Color(.systemGroupedBackground)) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift new file mode 100644 index 000000000..07a26704d --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift @@ -0,0 +1,138 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A grid of notification type buttons +struct NotificationTypeGrid: View { + let onSelect: (NotificationType) -> Void + + private let columns = [ + GridItem(.flexible(), spacing: 12), + GridItem(.flexible(), spacing: 12) + ] + + var body: some View { + LazyVGrid(columns: columns, spacing: 12) { + ForEach(NotificationType.allCases) { type in + NotificationTypeButton(type: type) { + onSelect(type) + } + } + } + } +} + +/// A grid of in-app message type buttons +struct InAppMessageTypeGrid: View { + let onSelect: (InAppMessageType) -> Void + + private let columns = [ + GridItem(.flexible(), spacing: 12), + GridItem(.flexible(), spacing: 12) + ] + + var body: some View { + LazyVGrid(columns: columns, spacing: 12) { + ForEach(InAppMessageType.allCases) { type in + InAppMessageTypeButton(type: type) { + onSelect(type) + } + } + } + } +} + +/// A button for a notification type with icon and label +struct NotificationTypeButton: View { + let type: NotificationType + let action: () -> Void + + var body: some View { + Button(action: action) { + VStack(spacing: 8) { + Image(systemName: type.iconName) + .font(.system(size: 32)) + Text(type.rawValue) + .font(.system(size: 14, weight: .medium)) + .multilineTextAlignment(.center) + } + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .frame(height: 100) + .background(Color.accentColor) + .cornerRadius(12) + } + .buttonStyle(.plain) + } +} + +/// A button for an in-app message type with icon and label +struct InAppMessageTypeButton: View { + let type: InAppMessageType + let action: () -> Void + + var body: some View { + Button(action: action) { + VStack(spacing: 8) { + Image(systemName: type.iconName) + .font(.system(size: 32)) + Text(type.rawValue) + .font(.system(size: 14, weight: .medium)) + .multilineTextAlignment(.center) + } + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .frame(height: 100) + .background(Color.accentColor) + .cornerRadius(12) + } + .buttonStyle(.plain) + } +} + +#Preview { + ScrollView { + VStack(alignment: .leading, spacing: 20) { + Text("Send Push Notification") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.secondary) + NotificationTypeGrid(onSelect: { type in + print("Selected: \(type.rawValue)") + }) + + Text("Send In-App Message") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.secondary) + InAppMessageTypeGrid(onSelect: { type in + print("Selected: \(type.rawValue)") + }) + } + .padding() + } + .background(Color(.systemGroupedBackground)) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift new file mode 100644 index 000000000..a0e298b63 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift @@ -0,0 +1,80 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A toast notification view that appears at the bottom of the screen +struct ToastView: View { + let message: String + + var body: some View { + Text(message) + .font(.subheadline) + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 12) + .background(Color.black.opacity(0.8)) + .cornerRadius(8) + .shadow(radius: 4) + } +} + +/// A view modifier that overlays a toast message +struct ToastModifier: ViewModifier { + @Binding var message: String? + + func body(content: Content) -> some View { + ZStack { + content + + if let message = message { + VStack { + Spacer() + ToastView(message: message) + .padding(.bottom, 32) + .transition(.move(edge: .bottom).combined(with: .opacity)) + } + .animation(.easeInOut(duration: 0.3), value: message) + } + } + } +} + +extension View { + /// Adds a toast overlay to the view + func toast(message: Binding) -> some View { + modifier(ToastModifier(message: message)) + } +} + +#Preview { + VStack { + Text("Content") + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .toast(message: .constant("This is a toast message")) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift new file mode 100644 index 000000000..fdef572c3 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift @@ -0,0 +1,98 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Main content view composing all sections +struct ContentView: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + NavigationStack { + ScrollView { + VStack(spacing: 0) { + AppInfoSection() + UserSection() + AliasesSection() + PushSection() + EmailsSection() + SMSSection() + TagsSection() + OutcomeEventsSection() + InAppMessagingSection() + TriggersSection() + LocationSection() + NotificationSection() + } + .padding(.horizontal, 16) + .padding(.bottom, 32) + } + .background(Color(.systemGroupedBackground)) + .safeAreaInset(edge: .top) { + // Compact header bar + HStack { + HStack(spacing: 8) { + Image(systemName: "bell.circle.fill") + .font(.title3) + Text("OneSignal") + .font(.headline) + } + + Spacer() + + Button { + viewModel.refreshState() + } label: { + Image(systemName: "arrow.clockwise") + } + } + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 12) + .background(Color.accentColor) + } + .navigationBarHidden(true) + .sheet(isPresented: $viewModel.showingAddSheet) { + AddItemSheet( + itemType: viewModel.addItemType, + onAdd: { key, value in + viewModel.handleAddItem(key: key, value: value) + }, + onCancel: { + viewModel.showingAddSheet = false + } + ) + } + } + .toast(message: $viewModel.toastMessage) + } +} + +#Preview { + ContentView() + .environmentObject(OneSignalViewModel()) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift new file mode 100644 index 000000000..861c57241 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift @@ -0,0 +1,55 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section displaying app information and consent management +struct AppInfoSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "App") + + CardContainer { + InfoRow(label: "App-Id:", value: viewModel.appId, isMonospaced: true) + } + + ActionButton(title: "Revoke Consent") { + viewModel.revokeConsent() + } + .padding(.top, 12) + } + } +} + +#Preview { + AppInfoSection() + .padding() + .background(Color(.systemGroupedBackground)) + .environmentObject(OneSignalViewModel()) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift new file mode 100644 index 000000000..c6414917d --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift @@ -0,0 +1,62 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for location sharing and permissions +struct LocationSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "Location") + + CardContainer { + ToggleRow( + title: "Location Shared:", + subtitle: "Location will be shared from device", + isOn: Binding( + get: { viewModel.isLocationShared }, + set: { _ in viewModel.toggleLocationShared() } + ) + ) + } + + ActionButton(title: "Prompt Location") { + viewModel.promptLocation() + } + .padding(.top, 12) + } + } +} + +#Preview { + LocationSection() + .padding() + .background(Color(.systemGroupedBackground)) + .environmentObject(OneSignalViewModel()) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift new file mode 100644 index 000000000..f92de093d --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift @@ -0,0 +1,199 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for outcome events +struct OutcomeEventsSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + @State private var showingOutcomeSheet = false + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "Outcome Events") + + ActionButton(title: "Send Outcome") { + showingOutcomeSheet = true + } + } + .sheet(isPresented: $showingOutcomeSheet) { + OutcomeSheet( + onSend: { name, value in + if let value = value { + viewModel.sendOutcome(name, value: value) + } else { + viewModel.sendOutcome(name) + } + showingOutcomeSheet = false + }, + onCancel: { + showingOutcomeSheet = false + } + ) + } + } +} + +/// Section for in-app messaging controls +struct InAppMessagingSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "In-App Messaging") + + CardContainer { + ToggleRow( + title: "Pause In-App Messages:", + subtitle: "Toggle in-app messages", + isOn: Binding( + get: { viewModel.isInAppMessagesPaused }, + set: { _ in viewModel.toggleInAppMessagesPaused() } + ) + ) + } + } + } +} + +/// Section for trigger management +struct TriggersSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "Triggers") + + CardContainer { + if viewModel.triggers.isEmpty { + EmptyListRow(message: "No Triggers Added") + } else { + ForEach(Array(viewModel.triggers.enumerated()), id: \.element.id) { index, trigger in + if index > 0 { + CardDivider() + } + KeyValueRow(item: trigger) { + viewModel.removeTrigger(trigger) + } + } + } + } + + ActionButton(title: "Add Trigger") { + viewModel.showAddSheet(for: .trigger) + } + .padding(.top, 12) + } + } +} + +/// Sheet for sending outcomes +struct OutcomeSheet: View { + let onSend: (String, Double?) -> Void + let onCancel: () -> Void + + @State private var outcomeName = "" + @State private var outcomeValue = "" + @State private var includeValue = false + @FocusState private var focusedField: Field? + + private enum Field { + case name, value + } + + var body: some View { + NavigationStack { + VStack(spacing: 24) { + VStack(alignment: .leading, spacing: 8) { + Text("Outcome Name") + .font(.caption) + .foregroundColor(.secondary) + TextField("Enter outcome name", text: $outcomeName) + .textFieldStyle(.roundedBorder) + .focused($focusedField, equals: .name) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + } + + Toggle("Include Value", isOn: $includeValue) + + if includeValue { + VStack(alignment: .leading, spacing: 8) { + Text("Value") + .font(.caption) + .foregroundColor(.secondary) + TextField("Enter value", text: $outcomeValue) + .textFieldStyle(.roundedBorder) + .focused($focusedField, equals: .value) + .keyboardType(.decimalPad) + } + } + + Spacer() + + HStack(spacing: 16) { + Button("Cancel") { + onCancel() + } + .foregroundColor(.accentColor) + + Spacer() + + Button("Send") { + let value = includeValue ? Double(outcomeValue) : nil + onSend(outcomeName, value) + } + .foregroundColor(.accentColor) + .disabled(outcomeName.trimmingCharacters(in: .whitespaces).isEmpty) + } + .textCase(.uppercase) + .font(.system(size: 16, weight: .semibold)) + } + .padding(24) + .navigationTitle("Send Outcome") + .navigationBarTitleDisplayMode(.inline) + .onAppear { + focusedField = .name + } + } + .presentationDetents([.medium]) + .presentationDragIndicator(.visible) + } +} + +#Preview { + ScrollView { + VStack { + OutcomeEventsSection() + InAppMessagingSection() + TriggersSection() + } + .padding() + } + .background(Color(.systemGroupedBackground)) + .environmentObject(OneSignalViewModel()) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift new file mode 100644 index 000000000..6c70e1203 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift @@ -0,0 +1,58 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for sending test push notifications and in-app messages +struct NotificationSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + // Push Notification Grid + SectionHeader(title: "Send Push Notification") + NotificationTypeGrid { type in + viewModel.sendTestNotification(type) + } + + // In-App Message Grid + SectionHeader(title: "Send In-App Message") + InAppMessageTypeGrid { type in + viewModel.sendTestInAppMessage(type) + } + } + } +} + +#Preview { + ScrollView { + NotificationSection() + .padding() + } + .background(Color(.systemGroupedBackground)) + .environmentObject(OneSignalViewModel()) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift new file mode 100644 index 000000000..06f21990f --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift @@ -0,0 +1,130 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for push subscription management +struct PushSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "Push") + + CardContainer { + InfoRow( + label: "Push-Id:", + value: viewModel.pushSubscriptionId ?? "Not available", + isMonospaced: true + ) + CardDivider() + ToggleRow( + title: "Enabled", + isOn: Binding( + get: { viewModel.isPushEnabled }, + set: { _ in viewModel.togglePushEnabled() } + ) + ) + } + } + } +} + +/// Section for email subscription management +struct EmailsSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "Emails") + + CardContainer { + if viewModel.emails.isEmpty { + EmptyListRow(message: "No Emails Added") + } else { + ForEach(Array(viewModel.emails.enumerated()), id: \.element) { index, email in + if index > 0 { + CardDivider() + } + SingleValueRow(value: email) { + viewModel.removeEmail(email) + } + } + } + } + + ActionButton(title: "Add Email") { + viewModel.showAddSheet(for: .email) + } + .padding(.top, 12) + } + } +} + +/// Section for SMS subscription management +struct SMSSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "SMSs") + + CardContainer { + if viewModel.smsNumbers.isEmpty { + EmptyListRow(message: "No SMSs Added") + } else { + ForEach(Array(viewModel.smsNumbers.enumerated()), id: \.element) { index, sms in + if index > 0 { + CardDivider() + } + SingleValueRow(value: sms) { + viewModel.removeSms(sms) + } + } + } + } + + ActionButton(title: "Add SMS") { + viewModel.showAddSheet(for: .sms) + } + .padding(.top, 12) + } + } +} + +#Preview { + ScrollView { + VStack { + PushSection() + EmailsSection() + SMSSection() + } + .padding() + } + .background(Color(.systemGroupedBackground)) + .environmentObject(OneSignalViewModel()) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift new file mode 100644 index 000000000..5177e9591 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift @@ -0,0 +1,66 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for managing user tags +struct TagsSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "Tags") + + CardContainer { + if viewModel.tags.isEmpty { + EmptyListRow(message: "No Tags Added") + } else { + ForEach(Array(viewModel.tags.enumerated()), id: \.element.id) { index, tag in + if index > 0 { + CardDivider() + } + KeyValueRow(item: tag) { + viewModel.removeTag(tag) + } + } + } + } + + ActionButton(title: "Add Tag") { + viewModel.showAddSheet(for: .tag) + } + .padding(.top, 12) + } + } +} + +#Preview { + TagsSection() + .padding() + .background(Color(.systemGroupedBackground)) + .environmentObject(OneSignalViewModel()) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift new file mode 100644 index 000000000..c9c762d69 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift @@ -0,0 +1,89 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for user login/logout +struct UserSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 12) { + ActionButton(title: "Login User") { + viewModel.showAddSheet(for: .externalUserId) + } + + ActionButton(title: "Logout User") { + viewModel.logout() + } + } + .padding(.top, 12) + } +} + +/// Section for alias management +struct AliasesSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "Aliases") + + CardContainer { + if viewModel.aliases.isEmpty { + EmptyListRow(message: "No Aliases Added") + } else { + ForEach(Array(viewModel.aliases.enumerated()), id: \.element.id) { index, alias in + if index > 0 { + CardDivider() + } + KeyValueRow(item: alias) { + viewModel.removeAlias(alias) + } + } + } + } + + ActionButton(title: "Add Alias") { + viewModel.showAddSheet(for: .alias) + } + .padding(.top, 12) + } + } +} + +#Preview { + ScrollView { + VStack { + UserSection() + AliasesSection() + } + .padding() + } + .background(Color(.systemGroupedBackground)) + .environmentObject(OneSignalViewModel()) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/README.md b/iOS_SDK/OneSignalSwiftUIExample/README.md new file mode 100644 index 000000000..1587befd4 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/README.md @@ -0,0 +1,153 @@ +# OneSignal SwiftUI Example App + +A modern SwiftUI example app demonstrating the OneSignal iOS SDK features using MVVM architecture. + +## Features + +This example app demonstrates all major OneSignal SDK capabilities: + +- **User Management**: Login/logout with external user ID +- **Aliases**: Add and remove user aliases +- **Push Subscriptions**: Enable/disable push notifications, view push ID +- **Email & SMS**: Add and remove email and SMS subscriptions +- **Tags**: Manage user tags for segmentation +- **Outcomes**: Track outcome events with optional values +- **In-App Messaging**: Pause/resume IAM, manage triggers +- **Location**: Toggle location sharing, request permissions +- **Test Notifications**: Grid of notification types for testing + +## Architecture + +The app follows the **MVVM (Model-View-ViewModel)** pattern with a service layer: + +``` +OneSignalSwiftUIExample/ +├── App/ +│ └── OneSignalSwiftUIExampleApp.swift # App entry point, AppDelegate, SDK initialization +├── Models/ +│ └── AppModels.swift # Data models (KeyValueItem, NotificationType, etc.) +├── Services/ +│ └── OneSignalService.swift # Singleton service wrapping all OneSignal SDK calls +├── ViewModels/ +│ └── OneSignalViewModel.swift # Main ViewModel with state management & observers +└── Views/ + ├── ContentView.swift # Root view composing all sections + ├── Components/ # Reusable UI components + │ ├── AddItemSheet.swift # Sheet for adding items (aliases, tags, etc.) + │ ├── KeyValueRow.swift # Row components for displaying data + │ ├── NotificationGrid.swift # Grid buttons for notification types + │ └── ToastView.swift # Toast notification overlay + └── Sections/ # Feature-specific sections + ├── AppInfoSection.swift # App ID display and consent management + ├── UserSection.swift # Login/logout and alias management + ├── SubscriptionSection.swift # Push, email, and SMS subscriptions + ├── TagsSection.swift # User tag management + ├── MessagingSection.swift # Outcomes, IAM controls, and triggers + ├── LocationSection.swift # Location sharing controls + └── NotificationSection.swift # Test notification buttons +``` + +## Running the App + +This project is part of the `OneSignalSDK.xcworkspace` and is configured to work with the local OneSignal SDK frameworks. + +### Quick Start + +1. Open `iOS_SDK/OneSignalSDK.xcworkspace` in Xcode +2. Select the **OneSignalSwiftUIExample** scheme +3. Select a simulator or physical device +4. Build and run (⌘R) +5. Grant notification permissions when prompted +6. Explore the various OneSignal features + +### Using Your Own App ID + +The default OneSignal App ID is configured in `OneSignalService.swift`. To use your own: + +1. Open `OneSignalSwiftUIExample/Services/OneSignalService.swift` +2. Change the `defaultAppId` value to your OneSignal App ID + +```swift +private let defaultAppId = "your-onesignal-app-id" +``` + +## Project Configuration + +### Required Capabilities + +The app requires the following capabilities (already configured): + +- **Push Notifications** +- **Background Modes** → Remote notifications + +### Info.plist Keys + +The following keys are configured for location and background notifications: + +- `NSLocationWhenInUseUsageDescription` +- `NSLocationAlwaysAndWhenInUseUsageDescription` +- `UIBackgroundModes` with `remote-notification` + +### Framework Dependencies + +The project links against the following OneSignal frameworks (built from the workspace): + +- `OneSignalFramework` +- `OneSignalInAppMessages` +- `OneSignalLocation` +- `OneSignalUser` +- `OneSignalNotifications` +- `OneSignalExtension` +- `OneSignalOutcomes` +- `OneSignalOSCore` + +## Key Implementation Details + +### SDK Initialization + +The OneSignal SDK is initialized in `AppDelegate` via `OneSignalService.shared.initialize()`: + +```swift +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + OneSignalService.shared.initialize(launchOptions: launchOptions) + // Set up notification and IAM listeners... + return true + } +} +``` + +### Service Layer Pattern + +All OneSignal SDK calls are encapsulated in `OneSignalService`, providing: + +- Centralized SDK access +- Easy mocking for testing +- Clean separation from UI code + +### Observer Pattern + +The ViewModel sets up observers for SDK state changes: + +- `OSPushSubscriptionObserver` - Push subscription state changes +- `OSUserStateObserver` - User state changes +- `OSNotificationPermissionObserver` - Permission changes + +### SwiftUI Best Practices + +- `@StateObject` for ViewModel ownership +- `@EnvironmentObject` for dependency injection to child views +- `@MainActor` for thread-safe UI updates +- Reusable components for consistent UI + +## Requirements + +- iOS 16.0+ +- Xcode 15.0+ +- Swift 5.9+ +- OneSignal iOS SDK 5.0+ + +## License + +Modified MIT License - See LICENSE file for details. From c70092b5272ac231dcb48163d277b1d6a2507499 Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 5 Feb 2026 16:19:16 -0800 Subject: [PATCH 3/8] make consent a toggle --- .../Services/OneSignalService.swift | 2 ++ .../ViewModels/OneSignalViewModel.swift | 14 +++++++++++--- .../Views/Sections/AppInfoSection.swift | 14 +++++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift index 2a451ec44..c435d32ed 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift @@ -69,6 +69,8 @@ final class OneSignalService { } func revokeConsent() { + // Must set consent as required first, then revoke it + OneSignal.setConsentRequired(true) OneSignal.setConsentGiven(false) } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift index 7c1d3719e..4c181c46d 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift @@ -60,6 +60,9 @@ final class OneSignalViewModel: ObservableObject { // Location @Published var isLocationShared: Bool = false + // Consent + @Published var consentGiven: Bool = true + // UI State @Published var showingAddSheet: Bool = false @Published var addItemType: AddItemType = .email @@ -98,9 +101,14 @@ final class OneSignalViewModel: ObservableObject { // MARK: - Consent - func revokeConsent() { - service.revokeConsent() - showToast("Consent revoked") + func toggleConsent() { + consentGiven.toggle() + if consentGiven { + service.setConsentGiven(true) + } else { + service.revokeConsent() + } + showToast(consentGiven ? "Consent given" : "Consent revoked") } // MARK: - User Management diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift index 861c57241..5ad56be35 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift @@ -37,12 +37,16 @@ struct AppInfoSection: View { CardContainer { InfoRow(label: "App-Id:", value: viewModel.appId, isMonospaced: true) + CardDivider() + ToggleRow( + title: "Privacy Consent", + subtitle: "Grant or revoke privacy consent", + isOn: Binding( + get: { viewModel.consentGiven }, + set: { _ in viewModel.toggleConsent() } + ) + ) } - - ActionButton(title: "Revoke Consent") { - viewModel.revokeConsent() - } - .padding(.top, 12) } } } From 373f0bed13e3927b0811ebbf66079e54accd5b2d Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 5 Feb 2026 16:43:09 -0800 Subject: [PATCH 4/8] add app icon assets --- .../AppIcon.appiconset/100.png | Bin 0 -> 8004 bytes .../AppIcon.appiconset/1024.png | Bin 0 -> 83781 bytes .../AppIcon.appiconset/114.png | Bin 0 -> 9253 bytes .../AppIcon.appiconset/120.png | Bin 0 -> 9675 bytes .../AppIcon.appiconset/128.png | Bin 0 -> 10472 bytes .../AppIcon.appiconset/144.png | Bin 0 -> 11630 bytes .../AppIcon.appiconset/152.png | Bin 0 -> 12562 bytes .../Assets.xcassets/AppIcon.appiconset/16.png | Bin 0 -> 834 bytes .../AppIcon.appiconset/167.png | Bin 0 -> 13971 bytes .../AppIcon.appiconset/172.png | Bin 0 -> 14348 bytes .../AppIcon.appiconset/180.png | Bin 0 -> 15136 bytes .../AppIcon.appiconset/196.png | Bin 0 -> 16813 bytes .../Assets.xcassets/AppIcon.appiconset/20.png | Bin 0 -> 1174 bytes .../AppIcon.appiconset/216.png | Bin 0 -> 18650 bytes .../AppIcon.appiconset/256.png | Bin 0 -> 23108 bytes .../Assets.xcassets/AppIcon.appiconset/29.png | Bin 0 -> 1890 bytes .../Assets.xcassets/AppIcon.appiconset/32.png | Bin 0 -> 2109 bytes .../Assets.xcassets/AppIcon.appiconset/40.png | Bin 0 -> 2788 bytes .../Assets.xcassets/AppIcon.appiconset/48.png | Bin 0 -> 3490 bytes .../Assets.xcassets/AppIcon.appiconset/50.png | Bin 0 -> 3735 bytes .../AppIcon.appiconset/512.png | Bin 0 -> 54163 bytes .../Assets.xcassets/AppIcon.appiconset/55.png | Bin 0 -> 4158 bytes .../Assets.xcassets/AppIcon.appiconset/57.png | Bin 0 -> 4325 bytes .../Assets.xcassets/AppIcon.appiconset/58.png | Bin 0 -> 4350 bytes .../Assets.xcassets/AppIcon.appiconset/60.png | Bin 0 -> 4545 bytes .../Assets.xcassets/AppIcon.appiconset/64.png | Bin 0 -> 4873 bytes .../Assets.xcassets/AppIcon.appiconset/66.png | Bin 0 -> 5048 bytes .../Assets.xcassets/AppIcon.appiconset/72.png | Bin 0 -> 5116 bytes .../Assets.xcassets/AppIcon.appiconset/76.png | Bin 0 -> 5938 bytes .../Assets.xcassets/AppIcon.appiconset/80.png | Bin 0 -> 6258 bytes .../Assets.xcassets/AppIcon.appiconset/87.png | Bin 0 -> 6838 bytes .../Assets.xcassets/AppIcon.appiconset/88.png | Bin 0 -> 6940 bytes .../Assets.xcassets/AppIcon.appiconset/92.png | Bin 0 -> 7294 bytes .../AppIcon.appiconset/Contents.json | 337 +++++++++++++++++- 34 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/100.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/1024.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/114.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/120.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/128.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/144.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/152.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/16.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/167.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/172.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/180.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/196.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/20.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/216.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/256.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/29.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/32.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/40.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/48.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/50.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/512.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/55.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/57.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/58.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/60.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/64.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/66.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/72.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/76.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/80.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/87.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/88.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/92.png diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/100.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000000000000000000000000000000000000..57855f96d6876fe1b0250e043862c6399c3a079d GIT binary patch literal 8004 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

HRi%`V7;%VAa+}%Ob#}sZ!UqoG*ui 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 B1m^$% literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/114.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000000000000000000000000000000000000..61d5f896258b5be83a34754769f01e84e618bf91 GIT binary patch literal 9253 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 Hu0mjfX6msh literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/120.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000000000000000000000000000000000000..5ba28fc5b75001823f2565301fd5a4d90a79d971 GIT binary patch literal 9675 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 literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/16.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 0000000000000000000000000000000000000000..4e4b3be949d6aa43f30d3e728109ea9743ef2829 GIT binary patch literal 834 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�NOI`8T*HPet_C-?VIPnyhrJ!%PAvu^X`%V%62K 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 literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/172.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/172.png new file mode 100644 index 0000000000000000000000000000000000000000..772e8878bb555ba52ed973b8da025e1b5e4f999b GIT binary patch literal 14348 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

2M;B27Wp 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 literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/20.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000000000000000000000000000000000000..db15cd57cc02481fc4eb717662aefdd560251d6e GIT binary patch literal 1174 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 KMNUMnLSTYP`>ksL literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/256.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c1cfe00aaba6c383fbb57c02d7ac659ae0a55 GIT binary patch literal 23108 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 literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/32.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 0000000000000000000000000000000000000000..2285ee76698166d268b646e7c0d9361e5eb2e345 GIT binary patch literal 2109 zcmV-D2*US?P)Px+_en%SR9HuiS8Y&K*BO4!?%hvd*FY!I{eL5J;R2%9KZh0wh=>@C&^w)wW=w~%pa7=%^8meGK)nT! zi2=mJ2_W>*M+kKN0m_-P(7aw~vJ6Cd=2Ei9L$B9^S*?)r@?c%H64r-SgBc7r1u;H= z7;06PG4%Y42)}+5EEEzsHkqKGdK;uKegQgX4wykg)aQrV*AMmDHAKBb&@>T#!>pOG zm8^xSpkP7}V*$iA54%0^*3?2fcMi}?U=}mXMTM~3{|y*s&xS55E1eCh3g!BBgirS% z_~u(sE`A~isn@}_t{55LdIV6_WNKn>rvr$EUG{k3*}Mhp>QxG|vEVM)il3&9$Bc1u ztcAk}ynYn^LoWlejA%GaATl5O?idhB0DN;zgQ_SP*t`Wee;yiRus-|{Z0k$FVx&_0 z`!Ra58}j-8KphwW6a_3J1Jd;AFfaT%OkZ07Fd;*^_d^lIp&{apy=p5g%a*34BMBgO z5>Mwr$S?m6kR(_iUXARHrL>r)DDeOOFoJ*l6I71}Os5AV&U8i#YN|pY4R_6l?WaG1 zan2liFLL=)43<|w^ZUUvGvI1#g+4b|1d=fb0I|(OmoCG*z7#rHhBR+3TzmHrnWBLJ zygPOxe6kz5EE`~u1R(`90fOgS(}Xk)671z=uqvU>R1pTAN9#{NPNBMm3H;;u(aycpcE+56h|tVc$|gFZvI^f{~U3(DATEB8kwY z*>H?M2{_yVWf{z7gR8w2dWQp%D_7xJUyA6+2r}3F0Ja}LMTBIUqG;Se{BQpQBfILr zBni&CT9_6rfa>+a{d6(3U`WU(ZchA!;c)W!1_PMUm>wBPLUeEtSx-KJtfziNa|fTR z#OR54NL8+ueU#n=0B_3ygbp8pG$RkL11+TUz_CALxbc_J+3i9;JkN?kzYSlWodBeK z_8F9`{}rwm=LVlEN1(fXDstOf!OUj(yI#Xca|?7iImqQ?oO&CWrm7l8;(oRg^6?We z6)izdLmeUZ);D1E&A)-!vH(@Mo8}L|RJaJv9orKT2z0%MVZN}#A%MmQW6&ZIxSD@O z9un?3gTY@sM^BxN4KUsP)tCwZ43us{p=4DF}xpWDh(lSJSJ~%mm zrA0{bZ&lN1)SB}1ZgU_BHaBh8A!4bVL(_0EM`o3;UIwBR0c zPfvLTal=tp3-iKzlN=bT-;LnWV^q&`_BW9lMxK8G{@)x*4=pDbmybOgxty$a`@{ga z!p$bQI@+Mmbs_Zj-{IX^2c5-2dl>TbQ)KvRm(aW24%hxBNYkc4?(L zZo8O2ZUpptXg(iIg^Lq}9d6!_z>zMh8@ZkBv~EHLk&72aeu6*{V42Z8;MWcQ%3ggqq1SI-Udt2l9&^6 z0I^1a=V)kGJsD2slRre(n(vEZaKfr(X0x!_i2(50=Jmp~co8w3J0xelySM~u zUmv9WJK<{GPaI2DLn>;b<#ul>N2IrxUUclLC9~ioeD-||?`@(*_%oK3na+WZPUvhl zXu%*Xs~<#m$yzXzS*WRMJHp-nq7yr|SCRhXzA~mCh&vbH4gJI5B zkki-zW-^JI61&ulZAxQ&;HZc9H76#T7T$}jC)QAN;p^yxls9cGfG^zvXJb9WXUemk|2rabh=TFE6{yVW*OsNI_c%byB?0 z@aNLZd}KcUI4sMG1RuuP{N?~sK=7~1|E>{;a8D1J32#WJ9PFf8;5+n^NXyK87-r3a zWoZ$~kUD98kd&M!*ctnB>$_pR-AcJ=a)R+RaKqve;UVX@BYs;VpNNl1Tu^TfAih}q nY97CI-guPMO^Gm*yQ2Rez-BqgP%HeH00000NkvXXu0mjfCq>~Y literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/40.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000000000000000000000000000000000000..302c40febc612b90c245f3efb615b5d44d2fd26b GIT binary patch literal 2788 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 B5Xt}m literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/512.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 0000000000000000000000000000000000000000..a2d9aeee61f2af35318dae8bb51405adb8418976 GIT binary patch literal 54163 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 bb;frDxvH^Vy_Y&MAYhz4euhwP?iT$oI5Ez^ literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/55.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/55.png new file mode 100644 index 0000000000000000000000000000000000000000..1f3d4c2d5d6e48a815bfb4f3a06ed54e93dfc52e GIT binary patch literal 4158 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 IM6N<$g3(CrS^xk5 literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/57.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000000000000000000000000000000000000..03585df4f6153d0530408a5bd29728ee44c187f1 GIT binary patch literal 4325 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 literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/60.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000000000000000000000000000000000000..6211b6bbff88171cdc651f79a236d748cb070cd7 GIT binary patch literal 4545 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 f+p(Za{gr3400000NkvXXu0mjfEjho{ literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/64.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 0000000000000000000000000000000000000000..3d0afeaf72039a1184547e1e7f8bd1198b949ac0 GIT binary patch literal 4873 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 literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/66.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/66.png new file mode 100644 index 0000000000000000000000000000000000000000..50bfed1bca8a5bedfe7762bf23193ec674273446 GIT binary patch literal 5048 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 literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/88.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/88.png new file mode 100644 index 0000000000000000000000000000000000000000..d1e764b6f175ee6c075ffba0f81dba48ebc6308d GIT binary patch literal 6940 zcmV+%8{_1OP)Py4)=5M`RCr$PT?v#F)z$v0dY$Q>eFlvS;zEpqMp2_7`6KR;xFl)}m}p3jz_7|T zEQ$mH0f!xgVR4Ot7}Q@)KtUr$lNb$A5L_5QP*f0@nV#PF?*H6Z@6A+IS55cK^nm$~ zeGUhPsjjZ~z5DL{?sxBdEv3`xG`@Lt83NKb0CyR-?8e^!+_m`r7XYmHVB%*r$I4RB5*903@|DBrov&QLm{Lh z5y;7;G69l=v_*zwcR+GFA-mm>j3$GwrF+xwjuY6&2Vh}sDo~SYAb#*5;(PZZzJEVb zpMHkq(W8LZ#{f>ppr)m|l5o0oDyN7K_qyDF95$^0n%cDL=!-Nm<7@w-8lF} zrOC3BNwB}kyEvV&bn6D&Ip2Zfl8fQ!b0I9{S-eO8w;E#xI7eOWb7_L&aipVBkeiV!E8ytU2L;z$4aY@& zAPILt*L5+_T?;UQ!+jvxmhZ!l5PtE`NdN0?$YiMwhXN`MGMxfadCYlOkR+Hk!A`2zrO(5 z@x4n>H27}V&pNYpqVmW%@(M7cTtuxT2~F>9N4TyIw%vOmxr>mtTFikJpe7QE-;Bo@ zSY+w4+XcC#giVg6$PF;dPa_=;A>sD}z5r4_&NN~%NU{Zz!w%T(iZ3@?B?4%F04Q+6 z{mX$U8aSZ!J$drY`2(0+Z6uvW<5O#awd-Ido?B1=q>`+6v^^cB*H1Z4Vr z0^7{TlUqn8k@ERrzw{E6P8bL42`A*uW#k874zAzlL-YJau)gsIYjzB~y3%vo^ksz zS*!EPekkmJJ?v+l)jC|3z9m~TuWTu=n#R$;bt!hz=R^3-zazZyCB%2tL$VRjPGfgP zAV;Ij7nh75i^6NK=`_fl0$70CxNko~)273I=n&FwxB0^d0)WGgf*)T8*KN1LcG5`- zup*%4E2ULiJNKpJE<#&gNAT%CA@RvRw!&CY)UPmM1i>JRM~#5{mY;PB3o|)f^f@krlk#OI3ONT-35WtKrND@2;55YfiG8~5w8-q(rE*t@@GTejiLecHF zw{lK1YgN;%_VChNgqG#?Va{B=0 z4<3bt!!sX*=%3&Wz(iALW^0Z#DEoPEkHpjwsR+8;i2i#kl82A5K`;TxW>W?u1|)o~ct1T;A_2tWU~$3ig6*8MnXprE z`Q@#Wr4cA9YYx8pDtxsIn8wUB47Ii?qk^$4ze8qs&&K{^sutVj$XTiUDV#58D1 zTp|$$I$3q$EkA{;e}6^#G+RY9K~^h5TehHi_FTx$LPexggKV`T)zrlFi<05hIlGFS z02V25C=x}(*l}=tvX5tIyu3pLrCAOHVC~%t6}599JDkSg@i%5eB=tu;%-n~#X+52#y{D zn=EMq8!eeo2)175qGHhkrFf%{uH141&pZR)vK7j*(+f9U39A0y1hUu5>UHI{b6{~1 z|K%EnxVB}ry#nu?hnQSyWX0~^{u;%De%)rkYukcTDWu4H=P!a~OPA=Zft^U;O!_GJ}lD;#q;m_7>%RHvHGE&8`C6}jJ*f0pWbY0 z#pVEW>pT3%^+>H+1<4>|BW1C2)pFQQI$2vdODlo|32j|v6WZ}()(iB+oGWx+@zmPp|U`QE*Ikalvh znsZ+AW2V;2(^KDLk0J2HQ;>08Lm6;K*`{{O<6_5_!`9D-@z&{ExwpmKyx!(cML$u&>@Cy!q`O! z_wf5aAob|u+QdRIhu*zVxoANqMd7pVnKKvR&3|QWO>I_@yAr%y?@b0`wV-l!9jx6> z)Upb}C^l_^ckVnbqu>@yDS74cWr{SQdl>_qP9rHvI6STf@{S#V!K@XH9)Y4;Z`IBk z2RHuNXa616E5Hzz_^W{^89Z1!CLZjQ9uJP*JroFswY-?7Bbh|yie<2!etO0V$OM?* zFx=RLrXkg^@{Sly1Wl(=QTH%x-MedV$Ycsc9ECoTh2NL-ZeSu@(ZYoA9D!!dSR&ydlF)WCF}@=H2=q1Sj7Ii>r{yfSSnJ z^DI<7ywp$=O+}+<7+Q_gp(B9Zmc8r72t^XP$x?E7u}X118bt)7n0`N#IW_6NNS2;| zo~8L3jedWK;XnWlgNGvB>{U99#P5lM78RlDkyWsW zY?T9T=!2>C>Bxjh!1nFh0LPMupu4HW;2|Qm1fJu6-9*m-a0}TV6?~z9lcxr7%prg#TWXdGCuDe$AKCK0F zz@m*!XdF@vSq`GdoHUNo~a@Q_2-7`)b1afr`Y8OFeC$)=cqvI!UK3HYJg+IF) zrT2_B26+852(aL;lm2RiFoT$XM+#H-siihm{)5EaOCZ`;koyIEfLQFq?Sv_tuQxK1^2H8vec8R z!{pbf!Nf>&%`3n(M!z2g*Ib3td#4xz9NN4I&2w}qv#{V%BUsO!nnN1`96xdt4MT@P zB7P|}TmkR}=VwNE;UeCB7flnVbc{g=lv!4lL@FF&pG_5$@?}d>1=Gq0B`2lIeqejA|>tizpxTy&ZLxw>LQ8kv=qe$8dM_}uD7NaAYAeK$mYmXJv zwW0-;16($DwrQC>zW`HK=7aO5pP+2qSVjBgy==lNea}9Jz_R7WE@I3*aQ#dxz8eCZ zWQ4jJX&sB8?iuOCZc3R%WHX}Gh$PA{zCh!!k?p5vW)mO)uAK{Kp9>B3)_em@@?+86 zzd_NRcNzvc|C3L`|JakpEMj5}T>XFChKmqnn|Sb$VMu-XWh)QnT{9>sK-KCxtxx3a zBNYlV#VPGk(Yy9quloAq0Wi_6Ni}d?uLVlf8$I*pBeHQ5B()4n&lw0P4T9glEK8!n z!D^T=3Gr>)mEHwav5&ZL#luUKu5@*glP47{)7kk!$C{v2Xty0?bw$P|LjvV3>&3Q zvP1%QT6a{gT%j}>s$QmQTI8L7!!zYREvFU922r*7Ix$8lPClQaIQjgoY4(4E1v4(| zMRCnIxNrE0;pz--HfTiQyOSJOU5WCk_vWAnbD&u3(2!wD^Kr|52-QJlyPh-D%8@Xj4*99z?I(Op>lu>zQn$=UzMD4$qkC{t46 z!x&OylU6AdrTaw-;OLcEx^J_pY^WS@U*-6pAFw8C!wy9N4K))H-}ZjzvbtjhuxJ4z zcGOeL0+^-BGQw~F3!eLQu|=fz-MX=gG&=x9C$Sm>EG%)yPBe|L$*dO8O)Rpa3xDfd z+MDvg7kTeJG}TN}Iw?CE?2ZM%%zAxrUV9bF_4)&?BvJ63F$>Yx-%#3FbrqfhlX@IW zO4~X&jkw*>>fuOzp-MP}tk>KO_iye(@t{GbeO*m6W-1S*Z+Xo%=yf%~WP!A?Tn1wR zY-6NR6V1VcOy|}gxI&gqyH?9)&qBdvmugdMKE|xQX}-rEN8rh)v~*3)8CS0~ZaES? zw$;OdXe^_bASaBw8erZ4SUh?-+&}-hX>S^B^$M%&YQ@1VTi|_QR;P-fWnG~~Sgc6|smIV4kDXgWu1&UgWMGW6S$EG z8%xE)TG&oM!_+lI-hUsS@e@tP?M1&Z93R3V`=DvI1p`Eux64@3??okg)2j*c9K zHKg`n_wtf#_D&YarBtXf+lnJ2}mVHsH}S!mhuWyZk$FM z-o?uhdj5sh?OSb8xO@T31bn5%N$!3M%BD|){fsl(+_2NUbQwa=y^twd@z_&%+pQ=a zHKI-H&CP;UR@}UJsRE9E{}|u+O~)0K&z%j42#vnuTl7ds5(3XZ5C57++4k3#y$?n` z6!`#{vdnk_sem7p9F{B-3sX-AlOMPpi4V9nZX zRR*J3&0>?}jvY!}(PA~+vrK#BN~TOy1bLIdroL}>7tz89l=@Wo?|+Bq{^^-pn=}cC zYI~DWPXiwe`LW=J4G2E-98#pQs+;QQejNpv9eqU(DGg`8%NdQdiX=kMbm(XPJn>IH zK_j({(cU=f3{_h%SIz7{=!=S3GjakqcMI0huI9x{5c=O2jQ3tp(DV7={_P-T+bZ8u zkkRR*ntduYHBFs{_>K=D6{_}vbQE9~tWqeQF&)l}E>fP}>{tU1Zfx&fCePC8Q*1|( z8eDn~+RIVoW_{6ylvEeo#E-dKusXuV<7gN=9trAdG&-Y)K=yj!8t`+J3>#*?Ea^8S zhuUoLE?k7*iyO2-r6T}yeohA;RXzS_YnG`$-+|l}R{|s3wxM~(EXIu)V^Hs8<;g`V z6h`^n2jMhnA27kU11Df}%S#+OgvL>0ST4?%ww?`epX}`SBNkX#z~IVMM#*22&|EtY zp_gCL68g>nObO7cI#{ZzG80<+#N2)F0)pm;Q$-J{zwVTP*e|J!H5 z;*Fvq`NNMuJ0Y0}BF=_Te<*KirUT|^!=B^(^D~z#zZU~oFVc4w;MFT(Q5OYNK{gc( zz_)TWLK|LE>TgCTP|*$~wpo$Rh%nf!*ku5#0s|{8O`ERNqD(+$+W}+=g}2>;qB{mc zE_89AmGgcn`g^s}81hshiR;@u||EK2Ldl|SM6y9_LN^AH|R+G(!ZQGaq z2e8T~r~H02FIyokVKPbe-!Y_c{Ic0?zok+pXc z;Fiu%GeL=4Ey9Q1`;9$j3Me}HwtkA4il@g@Rm*C9@)VbeLj z;`=SKRh#n(umCn52OTL!o74;X_lJAH?XZY=(E%Ek4eYK1SXeWsK2)I&{q;3=PGo%d zZkC)di&lRVfIc907GMEtI>ipXQ;r_1M8TC;Fa~2i`Q$FKS~cih3$XYk)v}|Fe#D!@ zn_oq2`}<6@kgRm>x$H5wYES*fHRA*nRTw zSL^^dI_Io>;e0qR_^x39Iyzz$61(@na^i_>`CGf6%D#+*z9)lDVby$zl#$q!D^eZ2 z?%249w!jwdL$@=OTnT|o{M35tDcbXaRrgDLc@XJTl8HL(U{U=`r$clQrw+gE_yVkc zY+>2VkJ1rf8I4NW2dTM7JHL-E``tTq#n%U5qsLdti>9L!*K~2P>JYkF8&5iV*Kgj} ig230?g8v^()c*k~w{!SO>jaYk0000Py6I7vi7RCr$PT?d#{)!BaUow}XfokdL$L6O+OE^4evB%0{&Z$pU+QOW`Wf&@aB zqV$d;7>PBp#1;e_ir6bgY*3~1nZ(bpE<-^28SpN{nqBB;z`GXjp8@Y;ynn)glW7ru*j|*{{%BERRwxN1 z=r7zPWqN;CqpZ5#Pe)P++(beFRhA7QAvh%gt^hR?fLVTTSFOQ;1za##16({FN3@{< zu^)d#e9sSv@86GPQzH@{FOs1UM-P|}>k0G0 z2SY9`O~o=EZ+B5U6Z``MxB!|nIRPNt(17sHPZ9a}W5mDu2Fdz*!0!iQF$OGSy(}vr z)4#=wsYrs!Pm;i-@0MgpMRv%Ql`!}21?zFg!ggYRSdKgrlF6ieQgaWT39|aTyB2WO zLlRCQj|YLb-$CT{H<0-Jb4X4X2ujIf0q75bBfwRM3);{@yhptdBu}vzlHmxF@fak# z9p+w#!FKv-u%CMltVbNth6BAfgNzk&F}enD70|?H-=6;>u;qEgx4#alt`0IW(r!o6 zLhO?iEiXhorZ$|31d`z}TL7t~7}owLp!kxDVL$UsWe0T;<2xU4&K?5V``;hnd*WZf zw%1_tcpw!OA!)U?J!~OB&O}EcO=JUtK~RuwIp!FYT=l;wKKESZlfrPTcS`{&-g$tl z7~}JJ;d%5iBsM(hj3hxKAOr(RD!BhG zNitwIDK>zvL+@h?PZDKtLH=DypC2&ED8BR$C>uHia!HADpTfu%h~ z0rHmTVKSMJwA+mttp+(9Mj{+$V3A8_I;cBLJ$?yOO%40ITwc!pNCYT(CV>E=Za3g^ zAzD|DM8kf>_wGf)>0&Mw(7?C*kqH+`Bi*$K3+^iTw*MF@*)3gWa_2 zD+HD-gZ1mLA(fXaH5TH(Iq?@B4@x!4O-V(xEq^?7Bg1Ze#&;y>Ngh5{Sz(_P_cX z;wx6d5(pq!Tx={Kv{fS!B!WT6)m5+!I0HrJp9kx4$3eE)QlZd#Eh&DuN-GS)`B+^Y zLT|r~;49k@{c;!OGnl*?0x#I^cEf(r?@)Q$1W0sG1#lk=19&2Vm@K2=Urz$-*28SE zv>8P$i|AqE2_$?z$Oj*U;!6gic;H1acRwiAFQ$KG6T0iMRGo)W={uRHo$%awfSE+O0Bx!S~;X@4<%={bVQP;u0ojy0T|BBk6X- zd}JR~-8mno>go=G-Z8*Av+Evw80PynG9#xX?ldj|u`Hpm+IGfiC?7E#mfpP;h{zLA zdmWh=tM}dF{n=&*Rem;kKE9`)f&YOA!H7V47d;P(K=*iHI=nZk7cYdVvNCVb^9MW; zM_e|c?#ZViuU-R_5!k{AnN29YX$Z=$xFS2CRc>I;VYf(7W!fz&J&ykH1KcZCA@ae8 zkV!VwX<@OTjOiYjk2#8gPRTMwlT>+~ER1?yfU``UETeAw8%WHZ2eZwlFCzrD*9X%t z4?)FWC&PBkF>N_(y`X|^{Ujjs+&6!KL@1nh)Acp(e^h}=h8{mKRam5GMlSQWKF4)gG8&z{=^F&$dy|#yW&VUQB zo4(nN;BAv&rTm2Oan)BdnUHY1Vd-}Qs%FiAsj4b-AQ4Znz(c$uyLTh};YW!4=Rb(; z`7aX9EdX6hWCYdMqAa4-!oWBE;t*K+_J!@_lVI!DuPr?*+)_Q@RWjq-vj$G^_{}*7M>zpbR-QJHFAzZUgh{aP-i;(}Ev=8u!M2@w1}bOH zfJ}jhQPif|+T%|8vhF@Z{5s&8^WPc@^4HTc$m$Hk|aiNEC@skOe#T zvj(7K&^563>C*FUUo{R0* zT+g!%in0ToZz;b&faYd%b9?9gQS;N|gFl8&Ur3?0fz+xLvQMUBFp{NbFv7H_T5w zkcyHlwE&)Mxun? zeHSfLrz?7{-i`D<@i>!2$G!I``IZbU&j>hUw4C_ z1~Tq(qxsf}tnEQ6<;nf7X14Zv40IU@mlNd^$D{PJ%k)9#(&D^x0YWdnoGvY5hYufy zvccD8WO-(WbM3C%_yDB)?}wyy1NntY23?Jc+L7rO;x1hhNhTxVciw~C?EuM$(c*Hw zrBYkv!uhbDd`dcLcwiK3Xh745T7XdieW%dknbS~wfsPh)fSbmRX9+e|lvZUT$wjuFaXPB!&&`41 z9Kiau>yUi>@l@}GMOQIM)s?8(aE}sQsV%Z#_x9~@&6>kn8Z!2w6bh(9eGjob5QOE} zqv2SwEL|#FL;iojd(Vc{-OH0&6p_~4dk^HYvNX^Qjb<#F#Qt#;Ab;@%OZv2~B9{~U zMZZPml*u|erxXyHr_Vs-t#=ilJKwNfB%iVYvygcha`VDrRIgeF>rqFiJ|lMqB42%l z=3B-q*+jLhumuaCYVktWv8&A|r2{V9!4@Z4hL41q+8xxPJl}E^Qzj{S2(_H>&oX~? z_{emBH5=emk)YCi8P>19w0;-)x=FURS;ti&JR#xPEyKeDGwpB_TTEYt6)7`6IpWb{ad!cy>N-H z70(KDX1s!|A*kqFO7Qcto8E`XUO+-%^A^Ou`#zmgUYE>vS7FfV>dsK;G)H;psBNC8`dc~Wwn0u8*wgJ zh|mi!K`JjxbtSWXcw&juiSm&nP%1i~+WD+|`EvN5-lRmbnR*wqy$9BOkYs_7rDsoF zJLUbyKjB@sK2^unHeluQw5z2voCDtc_XiQ#xDk>%56qnV04yih~7H_OjzKtqftr?J!%g{Y_qrD zfoqy(o8N?aQpCT!Gz1l)j3e9jKpOoicvWPW+rkppDB*c9_G!7d9xZG{+&~*O< zcqoMG)vHn|!@(z)wQ=}J#OwDftsKIez5_q<0l~g>1X`MQXXNS(h6jVN^gR-ewW~FIlpoKH zMVX|;_U=oSPyzUog|PNd4Soz6-gN72h#4__`cxGC=0bgjzw{!U^A~oE;bL1-;6qJ4 z+(qlY@m{3~RNW2G$qcvrsxM1qrMv{gBM}zh#p>z}0AIuo7PKoVq(O~;nS$7dAGP&Q zw`!{!6%)syfA)9JY1k4DOkJDvI(zH~9HCmi1n;G8i{GiM`e;H8w_JQU^HqAUMgXy;BiCrnb# z)f6Jk=HN5H?^@lpfO~zgUvM5OXJ`r%!b@>EnF~y@2|Iixo|lYfB!a4y%V0a|sO$iD zE?k1p^UtS7i`-}yUw#?7jT@_Z&vI<#&E(8QatENbgQk^cY(kA z4$Y&-DyQnSFG+&q?zJ%Y_+>kOI5&QtjrYU%_xsbsR-Yf%0jHsQ{=8J<#aEq9pfwF2 zfkZ=NdJjjg?xE`a2Ly1p8x@l#qU4f`bpa3U*a7FHDXETyNYPf5E9q-Ziqjy$zPsiNMse2Wr;cr5xQ;zeG}J=4^zua?}B^h0*L%N#U|ky?O<#efy@`4UwdK z=2^Ixt&J z(srVkR!WrD=FUdZS!d~M+qd7KdGxr#<)=mFuB(hD<&4}O*e~WeBb}yHDmgaYG7hn? zzD{)zMC;q1hoJn%^gNJZ#fN?2s9O-*y*srgDU$QoT#1TN8e^jDeU{d28ap1bU0sUog#79k5z^~L<-FU|328XhjbFgEd?fEawuuBhS2Y!Lr`vz$8Wc{ zuzBiq);QmKL|WWHGzzJ@8Z{fJc~R4d&UG#B@vvM2<&|5fIsB9vPAAk_T2OY&D5cL` zYb-^K^6(?@t=BmfAzaCoRP9fVFk7<~hNDnh3&PLCy--uzROeMDPejSZn!td5&Tp0# zlou@SFv~lg;kplbHa^rj|ZU+6fwETd)e6huDU z!OthD`s7&}zzzHF2cmNFq>NxQ2QRCE?V_V<$ua~tJ(sTK{7|fkl09ozD=A5}|Ejuq zVs`M|cj3NsA(CEy)~DGD2Dq5r;_;y9l7Z+pae{I@T_;EpJJb7!PNyt;J~7N$g5#dG zZB6(Yj(}$exG;W=P0FDnYOvHft4U2l*0ax4wkI)H&o$yqkM7w6*P_LU?)*%VDSlGe z$Z_c~;Jg!;9g3ydE!6XJ&DF}~GJs7Zo19apvt#LL6)dLzPBsUIC8~6iT-xW!4sewL z^*!?}JWH3eLAttw4X9hmC;}B%=Qoy&M&NzmLHM6|iY+1wt_;d@9Ri#do)0v$9Cs|r zN7wS%Eqr{owlXD1@%{H`9zRhbn07=7?dnhnrbBz6X6@>>AqeeMxv~dbfG$aBo;($i z4?a)|#aay}ZFIR&I&wJ5uN`FErlw*;~B=kuUfB4^nov33|qewvSN4k8m?g`wFNYd8Uutv`XM_{ zQI?Gyu54SnS#{JVzK_^Uf4Jrawryiksa~IVUVw}KmQznb#k46fm2%tC4%o4VdN?Of zMeN7D%C6Irio22|OXWjv%-s8^Ax>@#S4m6g&9~4peWqd{wW}|VReL-rz41>dA39XI zY^DdLMRGZ@f9TEZNQ<@rofqJ|?YV5|4Je~~pkfMnMtU#K?8vv@!Z~v`oA9X%Hg9OP zo_Y$Z7Tm$?W-jCYatB=HS9>3NSQ%|mrK9I;5V*|7Fctm&B2c;M)E~)$XIAH10)9*cl&oSSn2}X=-N>a`*0VtX=_AWjCce zIEU_p`~X*h?pm@8foJ~>ndYPDO@`pD6_%rqLd9*j!g@p>MMhLo(walBuR|lXSQHxY z!GjdQwZ~)l0#J2}jJJwbrU;vGIa&u;<}va)Z^{BYaJpV?P$w?>y*uCE(&!O z)38WpB-M+T1<>Lx`2#NeS2ma?2It)Q2yJ`K7<4wHB#s1~V?5zFln%KO_LEL(({{=L zG}NPU}+REE#x?Cu| z{yLP?JOEl$mkYrcUWEUdXA$392bqqvt9P6F8Oqj$Zdmf6)SuRGoF-Y(+ky0c)hKJ*S!bf#6gy5YeZVt_%(%-kN(Ntx@*A$t z96#h>EDO(Y^~JdbPlUqo{{4P7ubXt4876wz9E%Ui2I|CiWm<@6lo@n#Us~gd zw=kUtxJK9MpMjUR!uR+SNA z;x2XGZ?5WGz;)P8K6`N6b_6zULiDrG6~juv)8SH~^E%+?4Y){}(SNCWLvt5nF*g3% ze%`t4*T`6U^ib|pZ@6W9l+E$yT?4od@btgJpL~Mg%da5({s*9RC^3t+bL%K+a$Q73 zOBzn2`i#!_ELozQM5eR$!bZ@4>30A{B1m!@V)@mtP;}lou>bmOWzxN1v|ctdM=-N_ zd)ESP06a~;jC`__0Z*)t?W;pF;8zxa3>u9u1=vw)i?&G1x4&z_0{H*E1u_|CI{ij7 z7p+#N&6YlW*e}koopLfPy?eFcVw5({2Dv=nb6@~h15bZf)c|QYLTv9|M8EkK(eJ)P z{KvhB*VVCKKU2N%PDN+c8q5FgUcVl}mtSRonhrS_=0guveh<)LJ=>;b3cDgcpF@@} zPkhrpc3=S)wS7Y}x^o3d?QW_KW+wrd>(~A?0oQv9y~?`wec8MxSA+`u{{KIK7x=*s)YttCco!r8 Ye-Lne`+I#QCjbBd07*qoM6N<$f_v39`v3p{ literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json index 13613e3ee..8e70699a7 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,8 +1,341 @@ { "images" : [ { - "idiom" : "universal", - "platform" : "ios", + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + }, + { + "filename" : "48.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "24x24", + "subtype" : "38mm" + }, + { + "filename" : "55.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "27.5x27.5", + "subtype" : "42mm" + }, + { + "filename" : "58.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "66.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "33x33", + "subtype" : "45mm" + }, + { + "filename" : "80.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "40x40", + "subtype" : "38mm" + }, + { + "filename" : "88.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "44x44", + "subtype" : "40mm" + }, + { + "filename" : "92.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "46x46", + "subtype" : "41mm" + }, + { + "filename" : "100.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "50x50", + "subtype" : "44mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "51x51", + "subtype" : "45mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "54x54", + "subtype" : "49mm" + }, + { + "filename" : "172.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "86x86", + "subtype" : "38mm" + }, + { + "filename" : "196.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "98x98", + "subtype" : "42mm" + }, + { + "filename" : "216.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "108x108", + "subtype" : "44mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "117x117", + "subtype" : "45mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "129x129", + "subtype" : "49mm" + }, + { + "filename" : "1024.png", + "idiom" : "watch-marketing", + "scale" : "1x", "size" : "1024x1024" } ], From 8fddfc544128841b89686e764876b1c005b3360e Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 12 Feb 2026 00:34:19 -0800 Subject: [PATCH 5/8] Update new example app --- .../Info.plist | 31 ++ .../NotificationService.swift | 37 ++ ...lNotificationServiceExtension.entitlements | 10 + .../OneSignalSwiftUIExample.entitlements | 4 + .../project.pbxproj | 250 ++++++++++- .../App/OneSignalSwiftUIExampleApp.swift | 127 ++++-- .../OneSignalLogo.imageset/Contents.json | 24 ++ .../onesignal_rectangle.png | Bin 0 -> 18638 bytes .../Models/AppModels.swift | 123 ++++-- .../Services/LogManager.swift | 92 ++++ .../Services/NotificationSender.swift | 348 ++++----------- .../Services/OneSignalService.swift | 152 ++++--- .../Services/TooltipService.swift | 101 +++++ .../Services/UserFetchService.swift | 125 ++++++ .../ViewModels/OneSignalViewModel.swift | 400 ++++++++++++++---- .../Views/Components/AddMultiItemSheet.swift | 140 ++++++ .../Components/CustomNotificationSheet.swift | 113 +++++ .../Views/Components/GuidanceBanner.swift | 54 +++ .../Views/Components/KeyValueRow.swift | 204 ++++++--- .../Views/Components/LogView.swift | 130 ++++++ .../Views/Components/NotificationGrid.swift | 104 ++--- .../Views/Components/RemoveMultiSheet.swift | 118 ++++++ .../Views/Components/TrackEventSheet.swift | 150 +++++++ .../Views/ContentView.swift | 172 ++++++-- .../Views/Sections/AppInfoSection.swift | 51 ++- .../Views/Sections/LocationSection.swift | 12 +- .../Views/Sections/MessagingSection.swift | 155 +++++-- .../Views/Sections/NextScreenSection.swift | 81 ++++ .../Views/Sections/NotificationSection.swift | 48 ++- .../Views/Sections/SubscriptionSection.swift | 88 +++- .../Views/Sections/TagsSection.swift | 27 +- .../Views/Sections/TrackEventSection.swift | 50 +++ .../Views/Sections/UserSection.swift | 79 +++- 33 files changed, 2809 insertions(+), 791 deletions(-) create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/Info.plist create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/NotificationService.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/OneSignalLogo.imageset/Contents.json create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/OneSignalLogo.imageset/onesignal_rectangle.png create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/LogManager.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/TooltipService.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/UserFetchService.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddMultiItemSheet.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/CustomNotificationSheet.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/GuidanceBanner.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/LogView.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/RemoveMultiSheet.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/TrackEventSheet.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NextScreenSection.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TrackEventSection.swift diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/Info.plist b/iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/Info.plist new file mode 100644 index 000000000..9c03e92f1 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + OneSignalNotificationServiceExtension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/NotificationService.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/NotificationService.swift new file mode 100644 index 000000000..d536bdaff --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/NotificationService.swift @@ -0,0 +1,37 @@ +import UserNotifications +import OneSignalExtension + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var receivedRequest: UNNotificationRequest? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive( + _ request: UNNotificationRequest, + withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void + ) { + self.receivedRequest = request + self.contentHandler = contentHandler + self.bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent + + if let bestAttemptContent = bestAttemptContent { + OneSignalExtension.didReceiveNotificationExtensionRequest( + request, + with: bestAttemptContent, + withContentHandler: contentHandler + ) + } + } + + override func serviceExtensionTimeWillExpire() { + if let contentHandler = contentHandler, + let bestAttemptContent = bestAttemptContent { + OneSignalExtension.serviceExtensionTimeWillExpireRequest( + receivedRequest!, + with: bestAttemptContent + ) + contentHandler(bestAttemptContent) + } + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements b/iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements new file mode 100644 index 000000000..c70461e82 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.onesignal.example.onesignal + + + diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements index 903def2af..344636495 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.entitlements @@ -4,5 +4,9 @@ aps-environment development + com.apple.security.application-groups + + group.com.onesignal.example.onesignal + diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj index 093135984..4d59a6aa0 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj @@ -7,9 +7,14 @@ objects = { /* Begin PBXBuildFile section */ + 076B2F5C2E9B739160493063 /* UserFetchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FD3768669ADB5E8E927B7F /* UserFetchService.swift */; }; + 0D666446CA5476DB5AC260EA /* OneSignalLiveActivities.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A265470C544D20273EEB62B0 /* OneSignalLiveActivities.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 104E342C46E0870F7E30FB29 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56C25916C718509D05DF03B2 /* CoreLocation.framework */; }; 12A93EBB7D6C97B82814924A /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CD2DA9DC09F1EF7D989C291 /* UserNotifications.framework */; }; + 14AB26AE3A2FF05C9F62CCD2 /* CustomNotificationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF680E9246E2AE4D179CD3E /* CustomNotificationSheet.swift */; }; + 19FA9DF58988997FD6F5BD2A /* OneSignalLiveActivities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A265470C544D20273EEB62B0 /* OneSignalLiveActivities.framework */; }; 1D68D16D167B951BD57386ED /* OneSignalSwiftUIExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE9826D274F62E747F3A931B /* OneSignalSwiftUIExampleApp.swift */; }; + 1FC9DF3B1B444569E585CA2B /* OneSignalNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 50B6C9352575073F1873F639 /* OneSignalNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 228DE2E45BE2EEA72F9436F3 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 985DE7F1C6162433D11943B0 /* SystemConfiguration.framework */; }; 23578DF36320EFEB17E12FFA /* OneSignalInAppMessages.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A6BA7282C93D0714AB7AE9D /* OneSignalInAppMessages.framework */; }; 25D4FC203BE01E9A35ACA465 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FEE98DA6A075AEB7709CCBE2 /* Assets.xcassets */; }; @@ -18,26 +23,38 @@ 36AD6B646EA553AD54024FAC /* OneSignalLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5B39E8FA865B4030F3009B4 /* OneSignalLocation.framework */; }; 39D261592E207BCB8F453E6E /* OneSignalOSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 501ED810C4C9B04785D4CCD4 /* OneSignalOSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C25C0592F3409E9005E5E9A /* NotificationSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C25C0582F3409E9005E5E9A /* NotificationSender.swift */; }; + 44345BEEC3249B530635D91C /* TrackEventSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BBEDE187CB941C13F384CF0 /* TrackEventSection.swift */; }; 4840204E727B4F405094C542 /* OneSignalExtension.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B441216A875FE941AED4964E /* OneSignalExtension.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 49E74AFBFBE23D06B7D310EC /* SubscriptionSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DF983228037C1B729C1419 /* SubscriptionSection.swift */; }; 56FA425BC3C515132CF2098F /* OneSignalUser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7B9F08F02892844599AA8BDD /* OneSignalUser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 59BF581E9C89F1D09BB5130A /* OneSignalOutcomes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E815F87FE680572C74E9D51 /* OneSignalOutcomes.framework */; }; 5EEA4B007D60765502F2A6E5 /* MessagingSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 962607A12EFC53D2F77E5948 /* MessagingSection.swift */; }; 644520A37F05E25FD17F5CF9 /* AppModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45255F2C6EB0B13E199BDC65 /* AppModels.swift */; }; + 6486DBFD33978F37842B8326 /* TooltipService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B2BF7C4E1DDEA6AD4AF40B /* TooltipService.swift */; }; + 6C7913B91320573B2AE46212 /* RemoveMultiSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E77A427C94B9932B08E72DA9 /* RemoveMultiSheet.swift */; }; 74920778F254B9DA27906AA3 /* AppInfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564304E2BA4613FD7649116D /* AppInfoSection.swift */; }; + 7C93699D746B0B5807AD13BB /* NextScreenSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F91FF35087706DBF9AD3BA /* NextScreenSection.swift */; }; + 7F7A92F525620CDF81EC46BE /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A7E57EF192E9C9385A1484 /* NotificationService.swift */; }; + 85BAC3A9462800CB3A23C362 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D676BEDC8A5CB48BBF8FFAF9 /* LogManager.swift */; }; 861FCEE8AFB371A2FA3BCC93 /* OneSignalOSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 501ED810C4C9B04785D4CCD4 /* OneSignalOSCore.framework */; }; + 8B3A2DC19BD16993EC4B617B /* GuidanceBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B06024858612EFCB3F42CC /* GuidanceBanner.swift */; }; 8EF4836F9252E9C8180804AB /* OneSignalOutcomes.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4E815F87FE680572C74E9D51 /* OneSignalOutcomes.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 8F3FE40D8D603A8879C6A111 /* TrackEventSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C56E78CB208A9F1F53318D8 /* TrackEventSheet.swift */; }; 8F69162AE31452F3956160BE /* OneSignalUser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B9F08F02892844599AA8BDD /* OneSignalUser.framework */; }; + 959F9DFCC1031992FB28E771 /* OneSignalCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 533347B40563BF22FB6EAC9C /* OneSignalCore.framework */; }; 9EF9D8D46AAAD6F93B32FDCB /* OneSignalLocation.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5B39E8FA865B4030F3009B4 /* OneSignalLocation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A8954DAC09761A3125EF37D3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A084A32F6E0CA6A6354705C0 /* WebKit.framework */; }; A92101FA384AC15596D4A659 /* OneSignalNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0805F3793886C9122DFB6E4 /* OneSignalNotifications.framework */; }; AD19D28FDE8DAA7C8BC87939 /* LocationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D373C8C00DE06FC5CD01C5 /* LocationSection.swift */; }; B13E9460BDD0553FF05542A0 /* NotificationGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 414EE3451468D29D4A9C87AC /* NotificationGrid.swift */; }; B5C4A4AC0A2D2AF73C9A7DD3 /* KeyValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A4F866BFA1EE429ED1CECC /* KeyValueRow.swift */; }; + B86C1BD47DFEC7567B004FE8 /* OneSignalExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B441216A875FE941AED4964E /* OneSignalExtension.framework */; }; + BAE6133523D7D07386BD3172 /* AddMultiItemSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5ACF918029F336B9C36BD1C /* AddMultiItemSheet.swift */; }; C9884D12CEDE50ACAE38DF0D /* NotificationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E237FD3F84CE3831CAB8A6DA /* NotificationSection.swift */; }; C994C923037F602FA48A1490 /* OneSignalFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB1399100ED5DD83ED6CD889 /* OneSignalFramework.framework */; }; CB5F82751F156695A7A03338 /* OneSignalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FF4424D702D8686CC819B8 /* OneSignalService.swift */; }; D13A25FE5762A23125227A84 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8029F22CDBDB03B2CBA14212 /* ToastView.swift */; }; + D6A555B7B420B014299B9E50 /* OneSignalOutcomes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E815F87FE680572C74E9D51 /* OneSignalOutcomes.framework */; }; D9E5ED5BA2BCFF6D9233AC66 /* OneSignalCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 533347B40563BF22FB6EAC9C /* OneSignalCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DAA6DDE0CF432A7BE50481EC /* OneSignalNotifications.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0805F3793886C9122DFB6E4 /* OneSignalNotifications.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DB2BA39FE32DB3D52154F48C /* TagsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49D0DCDB581563C015CEAFF /* TagsSection.swift */; }; @@ -47,8 +64,19 @@ E26208CEB7DC4D217EAAE4E6 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BF17A9E51F5A187FFAC1AC /* ContentView.swift */; }; EFE6A330EF362418C68B3F6E /* OneSignalCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 533347B40563BF22FB6EAC9C /* OneSignalCore.framework */; }; F8CF0C2C1A1F8842BCF86784 /* OneSignalExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B441216A875FE941AED4964E /* OneSignalExtension.framework */; }; + FEEC78AA5DE04832192D8F92 /* LogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B8D8F4DF8E47F8BA53423A /* LogView.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + DC39EC3039BB65E2D21F753F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7B7016AA7DF5AB54CD96701C /* Project object */; + proxyType = 1; + remoteGlobalIDString = D99FA6E45A25EB4DB04DA0CE; + remoteInfo = OneSignalNotificationServiceExtension; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 510E0294CE200E84088E8CC1 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -65,41 +93,69 @@ DAA6DDE0CF432A7BE50481EC /* OneSignalNotifications.framework in Embed Frameworks */, DD9942D5EB98C5F70FA8D3D8 /* OneSignalInAppMessages.framework in Embed Frameworks */, 9EF9D8D46AAAD6F93B32FDCB /* OneSignalLocation.framework in Embed Frameworks */, + 0D666446CA5476DB5AC260EA /* OneSignalLiveActivities.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + D6AA72CEF78258953FC1C74B /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 1FC9DF3B1B444569E585CA2B /* OneSignalNotificationServiceExtension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 09DF983228037C1B729C1419 /* SubscriptionSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionSection.swift; sourceTree = ""; }; + 22B8D8F4DF8E47F8BA53423A /* LogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LogView.swift; path = Views/Components/LogView.swift; sourceTree = ""; }; + 2C56E78CB208A9F1F53318D8 /* TrackEventSheet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TrackEventSheet.swift; path = Views/Components/TrackEventSheet.swift; sourceTree = ""; }; 3C25C0582F3409E9005E5E9A /* NotificationSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSender.swift; sourceTree = ""; }; + 3CCBA2632F44DD4B009AFA72 /* OneSignalNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OneSignalNotificationServiceExtension.entitlements; sourceTree = ""; }; 414EE3451468D29D4A9C87AC /* NotificationGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationGrid.swift; sourceTree = ""; }; 41A4F866BFA1EE429ED1CECC /* KeyValueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueRow.swift; sourceTree = ""; }; 45255F2C6EB0B13E199BDC65 /* AppModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppModels.swift; sourceTree = ""; }; 4D22580635AB6E6BE86F1396 /* UserSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSection.swift; sourceTree = ""; }; 4E815F87FE680572C74E9D51 /* OneSignalOutcomes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalOutcomes.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 501ED810C4C9B04785D4CCD4 /* OneSignalOSCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalOSCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 50B6C9352575073F1873F639 /* OneSignalNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OneSignalNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 52B06024858612EFCB3F42CC /* GuidanceBanner.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GuidanceBanner.swift; path = Views/Components/GuidanceBanner.swift; sourceTree = ""; }; 533347B40563BF22FB6EAC9C /* OneSignalCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 55F91FF35087706DBF9AD3BA /* NextScreenSection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NextScreenSection.swift; path = Views/Sections/NextScreenSection.swift; sourceTree = ""; }; 564304E2BA4613FD7649116D /* AppInfoSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoSection.swift; sourceTree = ""; }; 56C25916C718509D05DF03B2 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; + 59FD3768669ADB5E8E927B7F /* UserFetchService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UserFetchService.swift; path = Services/UserFetchService.swift; sourceTree = ""; }; 60D373C8C00DE06FC5CD01C5 /* LocationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSection.swift; sourceTree = ""; }; + 6BBEDE187CB941C13F384CF0 /* TrackEventSection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TrackEventSection.swift; path = Views/Sections/TrackEventSection.swift; sourceTree = ""; }; 6CD2DA9DC09F1EF7D989C291 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; 7A6BA7282C93D0714AB7AE9D /* OneSignalInAppMessages.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalInAppMessages.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7B9F08F02892844599AA8BDD /* OneSignalUser.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalUser.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7DD2010F08A0E2483F5FDDF2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8029F22CDBDB03B2CBA14212 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; 85FF4424D702D8686CC819B8 /* OneSignalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalService.swift; sourceTree = ""; }; + 93A7E57EF192E9C9385A1484 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 962607A12EFC53D2F77E5948 /* MessagingSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingSection.swift; sourceTree = ""; }; 985DE7F1C6162433D11943B0 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; A0506934E0F27B864F3AC273 /* OneSignalSwiftUIExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OneSignalSwiftUIExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; A084A32F6E0CA6A6354705C0 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + A265470C544D20273EEB62B0 /* OneSignalLiveActivities.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalLiveActivities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A3BF17A9E51F5A187FFAC1AC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + A5ACF918029F336B9C36BD1C /* AddMultiItemSheet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AddMultiItemSheet.swift; path = Views/Components/AddMultiItemSheet.swift; sourceTree = ""; }; B441216A875FE941AED4964E /* OneSignalExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B5B39E8FA865B4030F3009B4 /* OneSignalLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalLocation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C49D0DCDB581563C015CEAFF /* TagsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsSection.swift; sourceTree = ""; }; + D676BEDC8A5CB48BBF8FFAF9 /* LogManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogManager.swift; sourceTree = ""; }; + DBF680E9246E2AE4D179CD3E /* CustomNotificationSheet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CustomNotificationSheet.swift; path = Views/Components/CustomNotificationSheet.swift; sourceTree = ""; }; E0805F3793886C9122DFB6E4 /* OneSignalNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalNotifications.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E237FD3F84CE3831CAB8A6DA /* NotificationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSection.swift; sourceTree = ""; }; + E77A427C94B9932B08E72DA9 /* RemoveMultiSheet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RemoveMultiSheet.swift; path = Views/Components/RemoveMultiSheet.swift; sourceTree = ""; }; F42140C8B10F3D06BDE1C79E /* AddItemSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddItemSheet.swift; sourceTree = ""; }; + F6B2BF7C4E1DDEA6AD4AF40B /* TooltipService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TooltipService.swift; path = Services/TooltipService.swift; sourceTree = ""; }; FB1399100ED5DD83ED6CD889 /* OneSignalFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OneSignalFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FD249AC2E0DD1F282E33E3BF /* OneSignalViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalViewModel.swift; sourceTree = ""; }; FDEE7A98A0EBB6BF767A041D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; @@ -108,6 +164,16 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 70893964E2D04EE9C32A8152 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B86C1BD47DFEC7567B004FE8 /* OneSignalExtension.framework in Frameworks */, + 959F9DFCC1031992FB28E771 /* OneSignalCore.framework in Frameworks */, + D6A555B7B420B014299B9E50 /* OneSignalOutcomes.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D1959F94F086AC39994A96BD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -121,6 +187,7 @@ A92101FA384AC15596D4A659 /* OneSignalNotifications.framework in Frameworks */, 23578DF36320EFEB17E12FFA /* OneSignalInAppMessages.framework in Frameworks */, 36AD6B646EA553AD54024FAC /* OneSignalLocation.framework in Frameworks */, + 19FA9DF58988997FD6F5BD2A /* OneSignalLiveActivities.framework in Frameworks */, 104E342C46E0870F7E30FB29 /* CoreLocation.framework in Frameworks */, 228DE2E45BE2EEA72F9436F3 /* SystemConfiguration.framework in Frameworks */, 12A93EBB7D6C97B82814924A /* UserNotifications.framework in Frameworks */, @@ -131,10 +198,30 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 130E67036ECE13331B1CFFFA /* Services */ = { + isa = PBXGroup; + children = ( + F6B2BF7C4E1DDEA6AD4AF40B /* TooltipService.swift */, + 59FD3768669ADB5E8E927B7F /* UserFetchService.swift */, + ); + name = Services; + sourceTree = ""; + }; + 1E3DC3EAE97EBB641F71FE7D /* OneSignalNotificationServiceExtension */ = { + isa = PBXGroup; + children = ( + 3CCBA2632F44DD4B009AFA72 /* OneSignalNotificationServiceExtension.entitlements */, + 93A7E57EF192E9C9385A1484 /* NotificationService.swift */, + 7DD2010F08A0E2483F5FDDF2 /* Info.plist */, + ); + path = OneSignalNotificationServiceExtension; + sourceTree = ""; + }; 3D3976C085CF6E7D61D7F075 /* Products */ = { isa = PBXGroup; children = ( A0506934E0F27B864F3AC273 /* OneSignalSwiftUIExample.app */, + 50B6C9352575073F1873F639 /* OneSignalNotificationServiceExtension.appex */, ); name = Products; sourceTree = ""; @@ -147,6 +234,15 @@ path = Models; sourceTree = ""; }; + 5ECE11F9FF76FF6082340576 /* Sections */ = { + isa = PBXGroup; + children = ( + 6BBEDE187CB941C13F384CF0 /* TrackEventSection.swift */, + 55F91FF35087706DBF9AD3BA /* NextScreenSection.swift */, + ); + name = Sections; + sourceTree = ""; + }; 676D7DC4DABFC37256C328C5 /* Components */ = { isa = PBXGroup; children = ( @@ -158,6 +254,15 @@ path = Components; sourceTree = ""; }; + 6CC0A9DDAC1CB43170E00043 /* Views */ = { + isa = PBXGroup; + children = ( + C5DE3E27078ADECB8D1EDA06 /* Components */, + 5ECE11F9FF76FF6082340576 /* Sections */, + ); + name = Views; + sourceTree = ""; + }; 82619F0C016CC5B46729A454 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -166,6 +271,7 @@ B441216A875FE941AED4964E /* OneSignalExtension.framework */, FB1399100ED5DD83ED6CD889 /* OneSignalFramework.framework */, 7A6BA7282C93D0714AB7AE9D /* OneSignalInAppMessages.framework */, + A265470C544D20273EEB62B0 /* OneSignalLiveActivities.framework */, B5B39E8FA865B4030F3009B4 /* OneSignalLocation.framework */, E0805F3793886C9122DFB6E4 /* OneSignalNotifications.framework */, 501ED810C4C9B04785D4CCD4 /* OneSignalOSCore.framework */, @@ -182,6 +288,7 @@ isa = PBXGroup; children = ( A21AEF40FA55F829CF1DA4B4 /* OneSignalSwiftUIExample */, + 1E3DC3EAE97EBB641F71FE7D /* OneSignalNotificationServiceExtension */, 82619F0C016CC5B46729A454 /* Frameworks */, 3D3976C085CF6E7D61D7F075 /* Products */, ); @@ -219,6 +326,8 @@ DE2A74C6876FFA7E42683810 /* Views */, FEE98DA6A075AEB7709CCBE2 /* Assets.xcassets */, FDEE7A98A0EBB6BF767A041D /* Info.plist */, + 130E67036ECE13331B1CFFFA /* Services */, + 6CC0A9DDAC1CB43170E00043 /* Views */, ); path = OneSignalSwiftUIExample; sourceTree = ""; @@ -231,6 +340,19 @@ path = ViewModels; sourceTree = ""; }; + C5DE3E27078ADECB8D1EDA06 /* Components */ = { + isa = PBXGroup; + children = ( + 52B06024858612EFCB3F42CC /* GuidanceBanner.swift */, + 22B8D8F4DF8E47F8BA53423A /* LogView.swift */, + A5ACF918029F336B9C36BD1C /* AddMultiItemSheet.swift */, + E77A427C94B9932B08E72DA9 /* RemoveMultiSheet.swift */, + DBF680E9246E2AE4D179CD3E /* CustomNotificationSheet.swift */, + 2C56E78CB208A9F1F53318D8 /* TrackEventSheet.swift */, + ); + name = Components; + sourceTree = ""; + }; DE2A74C6876FFA7E42683810 /* Views */ = { isa = PBXGroup; children = ( @@ -246,6 +368,7 @@ children = ( 85FF4424D702D8686CC819B8 /* OneSignalService.swift */, 3C25C0582F3409E9005E5E9A /* NotificationSender.swift */, + D676BEDC8A5CB48BBF8FFAF9 /* LogManager.swift */, ); path = Services; sourceTree = ""; @@ -253,6 +376,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + D99FA6E45A25EB4DB04DA0CE /* OneSignalNotificationServiceExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = A98864C40B6D20CA1FCC9136 /* Build configuration list for PBXNativeTarget "OneSignalNotificationServiceExtension" */; + buildPhases = ( + FEA2231F20C185B148CF3295 /* Sources */, + 70893964E2D04EE9C32A8152 /* Frameworks */, + 496CC37F981649C67E534A5E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OneSignalNotificationServiceExtension; + productName = OneSignalNotificationServiceExtension; + productReference = 50B6C9352575073F1873F639 /* OneSignalNotificationServiceExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; F27E45A0AADC4454C26D8C07 /* OneSignalSwiftUIExample */ = { isa = PBXNativeTarget; buildConfigurationList = 94A2EFFFED81A4B2280CE916 /* Build configuration list for PBXNativeTarget "OneSignalSwiftUIExample" */; @@ -261,14 +401,14 @@ 8B945C9AF93265E68BBE57A8 /* Resources */, D1959F94F086AC39994A96BD /* Frameworks */, 510E0294CE200E84088E8CC1 /* Embed Frameworks */, + D6AA72CEF78258953FC1C74B /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + 454F2CC9F3CE935E9091072B /* PBXTargetDependency */, ); name = OneSignalSwiftUIExample; - packageProductDependencies = ( - ); productName = OneSignalSwiftUIExample; productReference = A0506934E0F27B864F3AC273 /* OneSignalSwiftUIExample.app */; productType = "com.apple.product-type.application"; @@ -282,6 +422,9 @@ BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1300; TargetAttributes = { + D99FA6E45A25EB4DB04DA0CE = { + ProvisioningStyle = Automatic; + }; F27E45A0AADC4454C26D8C07 = { ProvisioningStyle = Automatic; }; @@ -297,15 +440,24 @@ ); mainGroup = 8DB2942ED773E3D98EEC5396; minimizedProjectReferenceProxies = 1; + productRefGroup = 3D3976C085CF6E7D61D7F075 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F27E45A0AADC4454C26D8C07 /* OneSignalSwiftUIExample */, + D99FA6E45A25EB4DB04DA0CE /* OneSignalNotificationServiceExtension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 496CC37F981649C67E534A5E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8B945C9AF93265E68BBE57A8 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -338,11 +490,38 @@ DB2BA39FE32DB3D52154F48C /* TagsSection.swift in Sources */, D13A25FE5762A23125227A84 /* ToastView.swift in Sources */, E1BEF5E30555F0ADCBF62B7D /* UserSection.swift in Sources */, + 6486DBFD33978F37842B8326 /* TooltipService.swift in Sources */, + 076B2F5C2E9B739160493063 /* UserFetchService.swift in Sources */, + 8B3A2DC19BD16993EC4B617B /* GuidanceBanner.swift in Sources */, + 85BAC3A9462800CB3A23C362 /* LogManager.swift in Sources */, + FEEC78AA5DE04832192D8F92 /* LogView.swift in Sources */, + BAE6133523D7D07386BD3172 /* AddMultiItemSheet.swift in Sources */, + 6C7913B91320573B2AE46212 /* RemoveMultiSheet.swift in Sources */, + 14AB26AE3A2FF05C9F62CCD2 /* CustomNotificationSheet.swift in Sources */, + 8F3FE40D8D603A8879C6A111 /* TrackEventSheet.swift in Sources */, + 44345BEEC3249B530635D91C /* TrackEventSection.swift in Sources */, + 7C93699D746B0B5807AD13BB /* NextScreenSection.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FEA2231F20C185B148CF3295 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7F7A92F525620CDF81EC46BE /* NotificationService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 454F2CC9F3CE935E9091072B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D99FA6E45A25EB4DB04DA0CE /* OneSignalNotificationServiceExtension */; + targetProxy = DC39EC3039BB65E2D21F753F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 09FF5B5EE6F70E991CEAE373 /* Debug */ = { isa = XCBuildConfiguration; @@ -399,7 +578,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 5.4.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -411,6 +590,31 @@ }; name = Debug; }; + 19B2A627D6CEDFA231590A4B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 99SW8E36CT; + INFOPLIST_FILE = OneSignalNotificationServiceExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 5.4.1; + PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.OneSignalNotificationServiceExtensionA; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 70CAD4886778AACB94FDDB37 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -421,10 +625,12 @@ DEVELOPMENT_TEAM = 99SW8E36CT; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = OneSignalSwiftUIExample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 5.4.1; PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = NO; @@ -482,7 +688,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 5.4.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -503,10 +709,12 @@ DEVELOPMENT_TEAM = 99SW8E36CT; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = OneSignalSwiftUIExample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 5.4.1; PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = NO; @@ -515,6 +723,31 @@ }; name = Debug; }; + F647D1CFE40BD3C0C6511C73 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 99SW8E36CT; + INFOPLIST_FILE = OneSignalNotificationServiceExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 5.4.1; + PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.OneSignalNotificationServiceExtensionA; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -536,6 +769,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + A98864C40B6D20CA1FCC9136 /* Build configuration list for PBXNativeTarget "OneSignalNotificationServiceExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F647D1CFE40BD3C0C6511C73 /* Debug */, + 19B2A627D6CEDFA231590A4B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; /* End XCConfigurationList section */ }; rootObject = 7B7016AA7DF5AB54CD96701C /* Project object */; diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift index daefaa0ef..2907835cb 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift @@ -32,7 +32,7 @@ import OneSignalFramework struct OneSignalSwiftUIExampleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @StateObject private var viewModel = OneSignalViewModel() - + var body: some Scene { WindowGroup { ContentView() @@ -44,40 +44,73 @@ struct OneSignalSwiftUIExampleApp: App { // MARK: - App Delegate class AppDelegate: NSObject, UIApplicationDelegate { - + + // Keys for caching SDK state in UserDefaults + private let cachedIAMPausedKey = "CachedInAppMessagesPaused" + private let cachedLocationSharedKey = "CachedLocationShared" + private let cachedConsentRequiredKey = "CachedConsentRequired" + private let cachedPrivacyConsentKey = "CachedPrivacyConsent" + func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { + // Set consent required before init (must be set before initWithContext) + let consentRequired = UserDefaults.standard.bool(forKey: cachedConsentRequiredKey) + let privacyConsent = UserDefaults.standard.bool(forKey: cachedPrivacyConsentKey) + OneSignal.setConsentRequired(consentRequired) + OneSignal.setConsentGiven(privacyConsent) + // Initialize OneSignal OneSignalService.shared.initialize(launchOptions: launchOptions) - + + // Restore cached SDK states before UI loads + restoreCachedStates() + // Set up notification lifecycle listeners setupNotificationListeners() - + // Set up in-app message listeners setupInAppMessageListeners() - + + // Set up SDK log listener for LogView + setupLogListener() + + // Initialize tooltip service (fetches on background thread, non-blocking) + TooltipService.shared.initialize() + return true } - + + private func setupLogListener() { + OneSignal.Debug.setLogLevel(.LL_VERBOSE) + OneSignal.Debug.addLogListener(SDKLogListener.shared) + } + + private func restoreCachedStates() { + // Restore IAM paused status + let iamPaused = UserDefaults.standard.bool(forKey: cachedIAMPausedKey) + OneSignal.InAppMessages.paused = iamPaused + + // Restore location shared status + let locationShared = UserDefaults.standard.bool(forKey: cachedLocationSharedKey) + OneSignal.Location.isShared = locationShared + } + private func setupNotificationListeners() { // Foreground notification display OneSignal.Notifications.addForegroundLifecycleListener(NotificationLifecycleHandler.shared) - + // Notification click handling OneSignal.Notifications.addClickListener(NotificationClickHandler.shared) } - + private func setupInAppMessageListeners() { // In-app message lifecycle OneSignal.InAppMessages.addLifecycleListener(InAppMessageLifecycleHandler.shared) - + // In-app message click handling OneSignal.InAppMessages.addClickListener(InAppMessageClickHandler.shared) - - // Start with IAM paused - OneSignal.InAppMessages.paused = true } } @@ -85,21 +118,21 @@ class AppDelegate: NSObject, UIApplicationDelegate { class NotificationLifecycleHandler: NSObject, OSNotificationLifecycleListener { static let shared = NotificationLifecycleHandler() - + func onWillDisplay(event: OSNotificationWillDisplayEvent) { - print("[OneSignal] Notification will display: \(event.notification.title ?? "No title")") - // Optionally modify display behavior - // event.preventDefault() // Prevent automatic display - // event.notification.display() // Manually display later + Task { @MainActor in + LogManager.shared.i("Notification", "Will display: \(event.notification.title ?? "No title")") + } } } class NotificationClickHandler: NSObject, OSNotificationClickListener { static let shared = NotificationClickHandler() - + func onClick(event: OSNotificationClickEvent) { - print("[OneSignal] Notification clicked: \(event.notification.title ?? "No title")") - // Handle notification click - navigate to specific screen, etc. + Task { @MainActor in + LogManager.shared.i("Notification", "Clicked: \(event.notification.title ?? "No title")") + } } } @@ -107,29 +140,61 @@ class NotificationClickHandler: NSObject, OSNotificationClickListener { class InAppMessageLifecycleHandler: NSObject, OSInAppMessageLifecycleListener { static let shared = InAppMessageLifecycleHandler() - + func onWillDisplay(event: OSInAppMessageWillDisplayEvent) { - print("[OneSignal] IAM will display: \(event.message.messageId)") + Task { @MainActor in + LogManager.shared.i("IAM", "Will display: \(event.message.messageId)") + } } - + func onDidDisplay(event: OSInAppMessageDidDisplayEvent) { - print("[OneSignal] IAM did display: \(event.message.messageId)") + Task { @MainActor in + LogManager.shared.i("IAM", "Did display: \(event.message.messageId)") + } } - + func onWillDismiss(event: OSInAppMessageWillDismissEvent) { - print("[OneSignal] IAM will dismiss: \(event.message.messageId)") + Task { @MainActor in + LogManager.shared.i("IAM", "Will dismiss: \(event.message.messageId)") + } } - + func onDidDismiss(event: OSInAppMessageDidDismissEvent) { - print("[OneSignal] IAM did dismiss: \(event.message.messageId)") + Task { @MainActor in + LogManager.shared.i("IAM", "Did dismiss: \(event.message.messageId)") + } } } class InAppMessageClickHandler: NSObject, OSInAppMessageClickListener { static let shared = InAppMessageClickHandler() - + func onClick(event: OSInAppMessageClickEvent) { - print("[OneSignal] IAM clicked: \(event.result.actionId ?? "No action ID")") - // Handle IAM click - navigate, track event, etc. + Task { @MainActor in + LogManager.shared.i("IAM", "Clicked: \(event.result.actionId ?? "No action ID")") + } + } +} + +// MARK: - SDK Log Listener + +class SDKLogListener: NSObject, OSLogListener { + static let shared = SDKLogListener() + + func onLogEvent(_ event: OneSignalLogEvent) { + let level: LogLevel + switch event.level { + case .LL_FATAL, .LL_ERROR: + level = .error + case .LL_WARN: + level = .warning + case .LL_INFO: + level = .info + default: + level = .debug + } + Task { @MainActor in + LogManager.shared.log("SDK", event.entry, level: level) + } } } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/OneSignalLogo.imageset/Contents.json b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/OneSignalLogo.imageset/Contents.json new file mode 100644 index 000000000..cbc496abc --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/OneSignalLogo.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "onesignal_rectangle.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/OneSignalLogo.imageset/onesignal_rectangle.png b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Assets.xcassets/OneSignalLogo.imageset/onesignal_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..82772533888f100b953d450807b83514737bbf15 GIT binary patch literal 18638 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 z≶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 literal 0 HcmV?d00001 diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift index b9a802baf..7bd1f3a7c 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift @@ -39,31 +39,13 @@ struct KeyValueItem: Identifiable, Equatable { // MARK: - Notification Type -/// Types of test push notifications that can be sent +/// Types of test push notifications that can be sent (matching Android: Simple, With Image, Custom) enum NotificationType: String, CaseIterable, Identifiable { - case general = "General" - case greetings = "Greetings" - case promotions = "Promotions" - case breakingNews = "Breaking News" - case abandonedCart = "Abandoned Cart" - case newPost = "New Post" - case reEngagement = "Re-Engagement" - case rating = "Rating" - + case simple = "Simple Notification" + case withImage = "Notification With Image" + case custom = "Custom Notification" + var id: String { rawValue } - - var iconName: String { - switch self { - case .general: return "bell.fill" - case .greetings: return "hand.wave.fill" - case .promotions: return "tag.fill" - case .breakingNews: return "newspaper.fill" - case .abandonedCart: return "cart.fill" - case .newPost: return "photo.fill" - case .reEngagement: return "hand.tap.fill" - case .rating: return "star.fill" - } - } } // MARK: - In-App Message Type @@ -74,15 +56,15 @@ enum InAppMessageType: String, CaseIterable, Identifiable { case bottomBanner = "Bottom Banner" case centerModal = "Center Modal" case fullScreen = "Full Screen" - + var id: String { rawValue } - + var iconName: String { switch self { - case .topBanner: return "rectangle.topthird.inset.filled" - case .bottomBanner: return "rectangle.bottomthird.inset.filled" - case .centerModal: return "rectangle.center.inset.filled" - case .fullScreen: return "rectangle.inset.filled" + case .topBanner: return "arrow.up.to.line" + case .bottomBanner: return "arrow.down.to.line" + case .centerModal: return "square" + case .fullScreen: return "arrow.up.left.and.arrow.down.right" } } } @@ -97,7 +79,9 @@ enum AddItemType { case tag case trigger case externalUserId - + case customNotification + case trackEvent + var title: String { switch self { case .alias: return "Add Alias" @@ -106,36 +90,41 @@ enum AddItemType { case .tag: return "Add Tag" case .trigger: return "Add Trigger" case .externalUserId: return "Login User" + case .customNotification: return "Custom Notification" + case .trackEvent: return "Track Event" } } - + var requiresKeyValue: Bool { switch self { - case .alias, .tag, .trigger: return true - case .email, .sms, .externalUserId: return false + case .alias, .tag, .trigger, .customNotification: return true + case .email, .sms, .externalUserId, .trackEvent: return false } } - + var keyPlaceholder: String { switch self { - case .alias: return "Alias Label" - case .tag: return "Tag Key" - case .trigger: return "Trigger Key" + case .alias: return "Label" + case .tag: return "Key" + case .trigger: return "Key" + case .customNotification: return "Title" default: return "Key" } } - + var valuePlaceholder: String { switch self { - case .alias: return "Alias ID" - case .email: return "email@example.com" - case .sms: return "+1234567890" - case .tag: return "Tag Value" - case .trigger: return "Trigger Value" - case .externalUserId: return "External User ID" + case .alias: return "ID" + case .email: return "Email" + case .sms: return "SMS" + case .tag: return "Value" + case .trigger: return "Value" + case .externalUserId: return "External User Id" + case .customNotification: return "Body" + case .trackEvent: return "Event Name" } } - + var keyboardType: UIKeyboardType { switch self { case .email: return .emailAddress @@ -144,3 +133,47 @@ enum AddItemType { } } } + +// MARK: - Multi-Add Item Type + +/// Types for the multi-pair add dialog (Add Aliases, Add Tags, Add Triggers) +enum MultiAddItemType: String { + case aliases = "Add Multiple Aliases" + case tags = "Add Multiple Tags" + case triggers = "Add Multiple Triggers" +} + +// MARK: - Remove Multi Item Type + +/// Types for the remove-multi checkbox dialog +enum RemoveMultiItemType: String { + case aliases = "Remove Aliases" + case tags = "Remove Tags" + case triggers = "Remove Triggers" +} + +// MARK: - User Data + +/// Model for user data fetched from the OneSignal REST API +struct UserData { + let aliases: [String: String] + let tags: [String: String] + let emails: [String] + let smsNumbers: [String] + let externalId: String? +} + +// MARK: - Tooltip Models + +/// Tooltip content fetched from the shared sdk-shared repo +struct TooltipData { + let title: String + let description: String + let options: [TooltipOption]? +} + +/// An individual option within a tooltip +struct TooltipOption { + let name: String + let description: String +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/LogManager.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/LogManager.swift new file mode 100644 index 000000000..d5a576208 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/LogManager.swift @@ -0,0 +1,92 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation +import SwiftUI + +/// Log level for categorizing log entries +enum LogLevel: String { + case debug = "D" + case info = "I" + case warning = "W" + case error = "E" + + var color: Color { + switch self { + case .debug: return .blue + case .info: return .green + case .warning: return .orange + case .error: return .red + } + } +} + +/// A single log entry +struct LogEntry: Identifiable { + let id = UUID() + let timestamp: Date + let level: LogLevel + let message: String + + var formattedTimestamp: String { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss" + return formatter.string(from: timestamp) + } +} + +/// Thread-safe pass-through logger that captures logs for UI display and prints to console +@MainActor +final class LogManager: ObservableObject { + static let shared = LogManager() + + @Published var entries: [LogEntry] = [] + + private let maxEntries = 100 + + private init() {} + + func log(_ tag: String, _ message: String, level: LogLevel = .debug) { + let entry = LogEntry(timestamp: Date(), level: level, message: "[\(tag)] \(message)") + entries.append(entry) + if entries.count > maxEntries { + entries.removeFirst(entries.count - maxEntries) + } + // Also print to console + print("\(entry.formattedTimestamp) \(level.rawValue) \(entry.message)") + } + + func clear() { + entries.removeAll() + } + + // Convenience methods + func d(_ tag: String, _ message: String) { log(tag, message, level: .debug) } + func i(_ tag: String, _ message: String) { log(tag, message, level: .info) } + func w(_ tag: String, _ message: String) { log(tag, message, level: .warning) } + func e(_ tag: String, _ message: String) { log(tag, message, level: .error) } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/NotificationSender.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/NotificationSender.swift index 2a3c96cc4..d1758006a 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/NotificationSender.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/NotificationSender.swift @@ -31,70 +31,90 @@ import OneSignalFramework /// Service for sending push notifications via OneSignal API /// Note: This is for demo purposes only. In production, API calls should be made from your backend. final class NotificationSender { - + static let shared = NotificationSender() - + private let apiURL = URL(string: "https://onesignal.com/api/v1/notifications")! - + private let imageURL = "https://media.onesignal.com/automated_push_templates/ratings_template.png" + private init() {} - - /// Send a notification to this device through the OneSignal Platform. - /// Note: This form of API should not be used in production as it is not safe. - /// The device should make an API call to its own backend, which handles the OneSignal API call. - func sendNotification( - type: NotificationType, + + // MARK: - Public Methods + + /// Send a simple push notification with a basic title and body + func sendSimpleNotification( appId: String, completion: @escaping (Result) -> Void ) { - guard let subscriptionId = OneSignal.User.pushSubscription.id else { - completion(.failure(NotificationError.noSubscriptionId)) - return - } - - guard OneSignal.User.pushSubscription.optedIn else { - completion(.failure(NotificationError.notOptedIn)) - return - } - - let templateData = type.getNextTemplate() - - var payload: [String: Any] = [ + guard let subscriptionId = getSubscriptionId(completion: completion) else { return } + + let payload: [String: Any] = [ "app_id": appId, "include_subscription_ids": [subscriptionId], - "contents": ["en": templateData.message], + "headings": ["en": "Simple Notification"], + "contents": ["en": "This is a simple test notification from OneSignal."], "ios_sound": "nil" ] - - // Add title if present - if !templateData.title.isEmpty { - payload["headings"] = ["en": templateData.title] - } - - // Add large icon if present - if !templateData.largeIconUrl.isEmpty { - payload["ios_attachments"] = ["icon": templateData.largeIconUrl] - } - - // Add big picture if present - if !templateData.bigPictureUrl.isEmpty { - payload["big_picture"] = templateData.bigPictureUrl + + sendRequest(payload: payload, completion: completion) + } + + /// Send a push notification that includes a big image + func sendNotificationWithImage( + appId: String, + completion: @escaping (Result) -> Void + ) { + guard let subscriptionId = getSubscriptionId(completion: completion) else { return } + + let payload: [String: Any] = [ + "app_id": appId, + "include_subscription_ids": [subscriptionId], + "headings": ["en": "Image Notification"], + "contents": ["en": "This notification includes an image attachment."], + "ios_attachments": ["image": imageURL], + "big_picture": imageURL, + "ios_sound": "nil" + ] + + sendRequest(payload: payload, completion: completion) + } + + /// Send a custom push notification with user-provided title and body + func sendCustomNotification( + title: String, + body: String, + appId: String, + completion: @escaping (Result) -> Void + ) { + guard let subscriptionId = getSubscriptionId(completion: completion) else { return } + + let payload: [String: Any] = [ + "app_id": appId, + "include_subscription_ids": [subscriptionId], + "headings": ["en": title], + "contents": ["en": body], + "ios_sound": "nil" + ] + + sendRequest(payload: payload, completion: completion) + } + + // MARK: - Private Helpers + + private func getSubscriptionId(completion: @escaping (Result) -> Void) -> String? { + guard let subscriptionId = OneSignal.User.pushSubscription.id else { + completion(.failure(NotificationError.noSubscriptionId)) + return nil } - - // Add buttons for Breaking News - if type == .breakingNews { - payload["buttons"] = [ - ["id": "view", "text": "View"], - ["id": "save", "text": "Save"], - ["id": "share", "text": "Share"] - ] + + guard OneSignal.User.pushSubscription.optedIn else { + completion(.failure(NotificationError.notOptedIn)) + return nil } - - // Add thread/group ID for notification grouping - payload["thread_id"] = type.rawValue - - sendRequest(payload: payload, completion: completion) + + return subscriptionId } - + private func sendRequest( payload: [String: Any], completion: @escaping (Result) -> Void @@ -104,30 +124,30 @@ final class NotificationSender { request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type") request.setValue("application/vnd.onesignal.v1+json", forHTTPHeaderField: "Accept") request.timeoutInterval = 30 - + do { request.httpBody = try JSONSerialization.data(withJSONObject: payload) } catch { completion(.failure(error)) return } - + URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { - print("💛 [OneSignal] Failed to send notification: \(error.localizedDescription)") + print("[OneSignal] Failed to send notification: \(error.localizedDescription)") completion(.failure(error)) return } - + if let httpResponse = response as? HTTPURLResponse { if httpResponse.statusCode == 200 || httpResponse.statusCode == 202 { if let data = data, let responseStr = String(data: data, encoding: .utf8) { - print("💛 [OneSignal] Success sending notification: \(responseStr)") + print("[OneSignal] Success sending notification: \(responseStr)") } completion(.success(())) } else { if let data = data, let responseStr = String(data: data, encoding: .utf8) { - print("💛 [OneSignal] Failed to send notification (\(httpResponse.statusCode)): \(responseStr)") + print("[OneSignal] Failed (\(httpResponse.statusCode)): \(responseStr)") } completion(.failure(NotificationError.apiError(statusCode: httpResponse.statusCode))) } @@ -142,7 +162,7 @@ enum NotificationError: LocalizedError { case noSubscriptionId case notOptedIn case apiError(statusCode: Int) - + var errorDescription: String? { switch self { case .noSubscriptionId: @@ -154,215 +174,3 @@ enum NotificationError: LocalizedError { } } } - -// MARK: - Notification Template Data - -struct NotificationTemplateData { - let title: String - let message: String - let largeIconUrl: String - let bigPictureUrl: String -} - -// MARK: - Notification Data (matching Android NotificationData.java) - -extension NotificationType { - - private static var templatePositions: [NotificationType: Int] = [:] - - /// Get the next template data, cycling through available templates - func getNextTemplate() -> NotificationTemplateData { - let templates = self.templates - var pos = NotificationType.templatePositions[self] ?? 0 - - let template = templates[pos] - - pos += 1 - if pos >= templates.count { - pos = 0 - } - NotificationType.templatePositions[self] = pos - - return template - } - - private var templates: [NotificationTemplateData] { - switch self { - case .general: - return [ - NotificationTemplateData( - title: "Liked post", - message: "Michael DiCioccio liked your post!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fbell_red.png?alt=media&token=c80c4e76-1fd7-4912-93f4-f1aee1d98b20", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "Birthdays", - message: "Say happy birthday to Rodrigo and 5 others!", - largeIconUrl: "https://images.vexels.com/media/users/3/147226/isolated/preview/068af50eededd7a739aac52d8e509ab5-three-candles-birthday-cake-icon-by-vexels.png", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "New Post", - message: "Neil just posted for the first time in a while, check it out!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fbell_red.png?alt=media&token=c80c4e76-1fd7-4912-93f4-f1aee1d98b20", - bigPictureUrl: "" - ) - ] - - case .greetings: - return [ - NotificationTemplateData( - title: "", - message: "Welcome to Nike!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fhuman-greeting-red.png?alt=media&token=cb9f3418-db61-443c-955a-57e664d30271", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "Welcome to Adidas!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fhuman-greeting-red.png?alt=media&token=cb9f3418-db61-443c-955a-57e664d30271", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "Welcome to Sandra's cooking blog!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fhuman-greeting-red.png?alt=media&token=cb9f3418-db61-443c-955a-57e664d30271", - bigPictureUrl: "" - ) - ] - - case .promotions: - return [ - NotificationTemplateData( - title: "", - message: "Get 20% off site-wide!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fbrightness-percent-red.png?alt=media&token=9e43c45e-8bcc-413e-8a42-612020c406ba", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "Half-off all shoes today only!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fbrightness-percent-red.png?alt=media&token=9e43c45e-8bcc-413e-8a42-612020c406ba", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "3 hour flash sale!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fbrightness-percent-red.png?alt=media&token=9e43c45e-8bcc-413e-8a42-612020c406ba", - bigPictureUrl: "" - ) - ] - - case .breakingNews: - return [ - NotificationTemplateData( - title: "The rap game won't be the same", - message: "Nipsey Hussle shot dead in his own hometown!", - largeIconUrl: "https://pbs.twimg.com/profile_images/719602655337656321/kQUzR2Es_400x400.jpg", - bigPictureUrl: "https://lab.fm/wp-content/uploads/2019/04/nipsey-hussle-cipriani-diamond-ball-2018-nyc-credit-jstone-shutterstock@1800x1013.jpg" - ), - NotificationTemplateData( - title: "CNN being bought by Fox?", - message: "Fox has shown an increasing interest in purchasing CNN and because of some other deals this year it could actually happen!", - largeIconUrl: "https://www.thewrap.com/sites/default/wp-content/uploads/files/2013/Jul/08/101771/gallupinside.png", - bigPictureUrl: "https://i.ytimg.com/vi/C8YBKBuX43Q/maxresdefault.jpg" - ), - NotificationTemplateData( - title: "Tesla's next venture!", - message: "Tesla releasing fully autonomous driving service!", - largeIconUrl: "https://i.etsystatic.com/13567406/r/il/6657a5/1083941709/il_794xN.1083941709_k3vi.jpg", - bigPictureUrl: "https://electrek.co/wp-content/uploads/sites/3/2018/01/screen-shot-2018-01-04-at-12-59-25-pm.jpg?quality=82&strip=all&w=1600" - ) - ] - - case .abandonedCart: - return [ - NotificationTemplateData( - title: "", - message: "You have some shoes left in your cart!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fcart-red.png?alt=media&token=3e9ca206-540c-4275-8f21-1840e9cba930", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "Still want to buy the dress you saw?", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fcart-red.png?alt=media&token=3e9ca206-540c-4275-8f21-1840e9cba930", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "20% off the shoes you saw today.", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fcart-red.png?alt=media&token=3e9ca206-540c-4275-8f21-1840e9cba930", - bigPictureUrl: "" - ) - ] - - case .newPost: - return [ - NotificationTemplateData( - title: "", - message: "I just published a new blog post!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fimage-red.png?alt=media&token=3f44fd3d-27a5-4d05-9544-423edf2f6284", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "Come check out my new blog post on aliens!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fimage-red.png?alt=media&token=3f44fd3d-27a5-4d05-9544-423edf2f6284", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "10 places you have to see before you die.", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fimage-red.png?alt=media&token=3f44fd3d-27a5-4d05-9544-423edf2f6284", - bigPictureUrl: "" - ) - ] - - case .reEngagement: - return [ - NotificationTemplateData( - title: "", - message: "Your friend George just joined Facebook", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fgesture-tap-red.png?alt=media&token=8ea7f6db-18e4-4fdd-aabf-ac97f04522fd", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "Can you beat level 23?", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fgesture-tap-red.png?alt=media&token=8ea7f6db-18e4-4fdd-aabf-ac97f04522fd", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "Check out our Fall collection!", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fgesture-tap-red.png?alt=media&token=8ea7f6db-18e4-4fdd-aabf-ac97f04522fd", - bigPictureUrl: "" - ) - ] - - case .rating: - return [ - NotificationTemplateData( - title: "", - message: "How was your food/experience at Chipotle?", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fstar-red.png?alt=media&token=e18e99ce-96ad-4ee5-b0b9-40c7f90613d1", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "Rate your experience with Amazon.", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fstar-red.png?alt=media&token=e18e99ce-96ad-4ee5-b0b9-40c7f90613d1", - bigPictureUrl: "" - ), - NotificationTemplateData( - title: "", - message: "Let your Lyft driver know how the ride was.", - largeIconUrl: "https://firebasestorage.googleapis.com/v0/b/onesignaltest-e7802.appspot.com/o/NOTIFICATION_ICON%2Fstar-red.png?alt=media&token=e18e99ce-96ad-4ee5-b0b9-40c7f90613d1", - bigPictureUrl: "" - ) - ] - } - } -} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift index c435d32ed..981fe99a9 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift @@ -30,18 +30,18 @@ import OneSignalFramework /// Service layer that wraps OneSignal SDK calls final class OneSignalService { - + // MARK: - Singleton - + static let shared = OneSignalService() - + private init() {} - + // MARK: - App ID - + private let appIdKey = "OneSignalAppId" private let defaultAppId = "77e32082-ea27-42e3-a898-c72e141824ef" - + var appId: String { get { UserDefaults.standard.string(forKey: appIdKey) ?? defaultAppId @@ -50,184 +50,224 @@ final class OneSignalService { UserDefaults.standard.set(newValue, forKey: appIdKey) } } - + // MARK: - Initialization - + func initialize(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { OneSignal.Debug.setLogLevel(.LL_VERBOSE) OneSignal.initialize(appId, withLaunchOptions: launchOptions) + OneSignal.Notifications.requestPermission() } - + + // MARK: - Identity + + var onesignalId: String? { + OneSignal.User.onesignalId + } + + var externalId: String? { + OneSignal.User.externalId + } + // MARK: - Consent - + func setConsentRequired(_ required: Bool) { OneSignal.setConsentRequired(required) } - + func setConsentGiven(_ granted: Bool) { OneSignal.setConsentGiven(granted) } - + func revokeConsent() { // Must set consent as required first, then revoke it OneSignal.setConsentRequired(true) OneSignal.setConsentGiven(false) } - + // MARK: - User Management - + func login(externalId: String) { OneSignal.login(externalId) } - + func logout() { OneSignal.logout() } - + // MARK: - Aliases - + func addAlias(label: String, id: String) { OneSignal.User.addAlias(label: label, id: id) } - + + func addAliases(_ aliases: [String: String]) { + OneSignal.User.addAliases(aliases) + } + func removeAlias(_ label: String) { OneSignal.User.removeAlias(label) } - + + func removeAliases(_ labels: [String]) { + OneSignal.User.removeAliases(labels) + } + // MARK: - Push Subscription - + var pushSubscriptionId: String? { OneSignal.User.pushSubscription.id } - + var isPushEnabled: Bool { OneSignal.User.pushSubscription.optedIn } - + func optInPush() { OneSignal.User.pushSubscription.optIn() } - + func optOutPush() { OneSignal.User.pushSubscription.optOut() } - + func requestPushPermission(completion: @escaping (Bool) -> Void) { OneSignal.Notifications.requestPermission({ accepted in completion(accepted) }, fallbackToSettings: true) } - + // MARK: - Email - + func addEmail(_ email: String) { OneSignal.User.addEmail(email) } - + func removeEmail(_ email: String) { OneSignal.User.removeEmail(email) } - + // MARK: - SMS - + func addSms(_ number: String) { OneSignal.User.addSms(number) } - + func removeSms(_ number: String) { OneSignal.User.removeSms(number) } - + // MARK: - Tags - + func addTag(key: String, value: String) { OneSignal.User.addTag(key: key, value: value) } - + + func addTags(_ tags: [String: String]) { + OneSignal.User.addTags(tags) + } + func removeTag(_ key: String) { OneSignal.User.removeTag(key) } - + + func removeTags(_ keys: [String]) { + OneSignal.User.removeTags(keys) + } + func getTags() -> [String: String] { OneSignal.User.getTags() } - + // MARK: - Outcomes - + func sendOutcome(_ name: String) { OneSignal.Session.addOutcome(name) } - + func sendOutcome(_ name: String, value: NSNumber) { OneSignal.Session.addOutcome(name, value) } - + func sendUniqueOutcome(_ name: String) { OneSignal.Session.addUniqueOutcome(name) } - + // MARK: - In-App Messages - + var isInAppMessagesPaused: Bool { get { OneSignal.InAppMessages.paused } set { OneSignal.InAppMessages.paused = newValue } } - + func addTrigger(key: String, value: String) { OneSignal.InAppMessages.addTrigger(key, withValue: value) } - + + func addTriggers(_ triggers: [String: String]) { + OneSignal.InAppMessages.addTriggers(triggers) + } + func removeTrigger(_ key: String) { OneSignal.InAppMessages.removeTrigger(key) } - + + func removeTriggers(_ keys: [String]) { + OneSignal.InAppMessages.removeTriggers(keys) + } + + func clearTriggers() { + // Remove all triggers by clearing the list + OneSignal.InAppMessages.clearTriggers() + } + // MARK: - Location - + var isLocationShared: Bool { get { OneSignal.Location.isShared } set { OneSignal.Location.isShared = newValue } } - + func requestLocationPermission() { OneSignal.Location.requestPermission() } - + // MARK: - Notifications - + func clearAllNotifications() { OneSignal.Notifications.clearAll() } - + var hasNotificationPermission: Bool { OneSignal.Notifications.permission } - + // MARK: - Observers - + func addPushSubscriptionObserver(_ observer: OSPushSubscriptionObserver) { OneSignal.User.pushSubscription.addObserver(observer) } - + func addUserObserver(_ observer: OSUserStateObserver) { OneSignal.User.addObserver(observer) } - + func addPermissionObserver(_ observer: OSNotificationPermissionObserver) { OneSignal.Notifications.addPermissionObserver(observer) } - + func addNotificationClickListener(_ listener: OSNotificationClickListener) { OneSignal.Notifications.addClickListener(listener) } - + func addNotificationLifecycleListener(_ listener: OSNotificationLifecycleListener) { OneSignal.Notifications.addForegroundLifecycleListener(listener) } - + func addInAppMessageClickListener(_ listener: OSInAppMessageClickListener) { OneSignal.InAppMessages.addClickListener(listener) } - + func addInAppMessageLifecycleListener(_ listener: OSInAppMessageLifecycleListener) { OneSignal.InAppMessages.addLifecycleListener(listener) } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/TooltipService.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/TooltipService.swift new file mode 100644 index 000000000..bbeb8dae8 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/TooltipService.swift @@ -0,0 +1,101 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation + +/// Service that fetches and provides tooltip content from the shared sdk-shared repo. +/// Tooltips are non-critical; if the fetch fails, they are simply unavailable. +final class TooltipService: ObservableObject { + + static let shared = TooltipService() + + private let tooltipURL = URL(string: "https://raw.githubusercontent.com/OneSignal/sdk-shared/main/demo/tooltip_content.json")! + + @Published private(set) var tooltips: [String: TooltipData] = [:] + private var initialized = false + + private init() {} + + /// Fetch tooltip content on a background thread. Safe to call multiple times; only fetches once. + func initialize() { + guard !initialized else { return } + initialized = true + + DispatchQueue.global(qos: .utility).async { [weak self] in + self?.fetchTooltips() + } + } + + /// Returns tooltip data for the given section key, or nil if unavailable. + func getTooltip(key: String) -> TooltipData? { + tooltips[key] + } + + // MARK: - Private + + private func fetchTooltips() { + var request = URLRequest(url: tooltipURL) + request.timeoutInterval = 10 + + let task = URLSession.shared.dataTask(with: request) { [weak self] data, _, error in + guard let data = data, error == nil else { + print("[TooltipService] Failed to fetch tooltips: \(error?.localizedDescription ?? "unknown")") + return + } + + do { + guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { return } + var parsed: [String: TooltipData] = [:] + + for (key, value) in json { + guard let dict = value as? [String: Any], + let title = dict["title"] as? String, + let description = dict["description"] as? String else { continue } + + var options: [TooltipOption]? + if let optionsArray = dict["options"] as? [[String: Any]] { + options = optionsArray.compactMap { optDict in + guard let name = optDict["name"] as? String, + let desc = optDict["description"] as? String else { return nil } + return TooltipOption(name: name, description: desc) + } + } + + parsed[key] = TooltipData(title: title, description: description, options: options) + } + + DispatchQueue.main.async { + self?.tooltips = parsed + print("[TooltipService] Loaded \(parsed.count) tooltips") + } + } catch { + print("[TooltipService] Failed to parse tooltips: \(error.localizedDescription)") + } + } + task.resume() + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/UserFetchService.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/UserFetchService.swift new file mode 100644 index 000000000..eee835141 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/UserFetchService.swift @@ -0,0 +1,125 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Foundation + +/// Service for fetching user data from the OneSignal REST API. +/// No API key is required for this public endpoint. +final class UserFetchService { + + static let shared = UserFetchService() + + private init() {} + + /// Fetch user data by OneSignal ID. No auth header required. + func fetchUser(appId: String, onesignalId: String) async -> UserData? { + let urlString = "https://api.onesignal.com/apps/\(appId)/users/by/onesignal_id/\(onesignalId)" + guard let url = URL(string: urlString) else { return nil } + + var request = URLRequest(url: url) + request.httpMethod = "GET" + request.timeoutInterval = 15 + + do { + let (data, response) = try await URLSession.shared.data(for: request) + + guard let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 else { + print("[UserFetchService] Non-200 response") + return nil + } + + guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { + return nil + } + + return parseUserData(json) + } catch { + print("[UserFetchService] Fetch failed: \(error.localizedDescription)") + return nil + } + } + + // MARK: - Private + + private func parseUserData(_ json: [String: Any]) -> UserData { + // Parse identity (aliases) + var aliases: [String: String] = [:] + var externalId: String? + + if let identity = json["identity"] as? [String: Any] { + for (key, value) in identity { + if key == "external_id" { + externalId = value as? String + } else if key == "onesignal_id" { + // Skip onesignal_id from aliases display + continue + } else if let strValue = value as? String { + aliases[key] = strValue + } + } + } + + // Parse tags from properties + var tags: [String: String] = [:] + if let properties = json["properties"] as? [String: Any], + let tagsDict = properties["tags"] as? [String: Any] { + for (key, value) in tagsDict { + if let strValue = value as? String { + tags[key] = strValue + } else { + tags[key] = "\(value)" + } + } + } + + // Parse subscriptions for emails and SMS + var emails: [String] = [] + var smsNumbers: [String] = [] + + if let subscriptions = json["subscriptions"] as? [[String: Any]] { + for sub in subscriptions { + guard let type = sub["type"] as? String, + let token = sub["token"] as? String else { continue } + + if type == "Email" { + emails.append(token) + } else if type == "SMS" { + smsNumbers.append(token) + } + } + } + + return UserData( + aliases: aliases, + tags: tags, + emails: emails, + smsNumbers: smsNumbers, + externalId: externalId + ) + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift index 4c181c46d..3f3e95926 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift @@ -32,94 +32,178 @@ import OneSignalFramework /// Main ViewModel managing all OneSignal SDK state and interactions @MainActor final class OneSignalViewModel: ObservableObject { - + // MARK: - Published Properties - + // App Info @Published var appId: String - + // User @Published var externalUserId: String? @Published var aliases: [KeyValueItem] = [] - + // Push Subscription @Published var pushSubscriptionId: String? @Published var isPushEnabled: Bool = false - + @Published var notificationPermissionGranted: Bool = false + // Email & SMS @Published var emails: [String] = [] @Published var smsNumbers: [String] = [] - + // Tags @Published var tags: [KeyValueItem] = [] - + // In-App Messaging @Published var isInAppMessagesPaused: Bool = true @Published var triggers: [KeyValueItem] = [] - + // Location @Published var isLocationShared: Bool = false - + // Consent - @Published var consentGiven: Bool = true - + @Published var consentRequired: Bool = UserDefaults.standard.bool(forKey: "CachedConsentRequired") + @Published var consentGiven: Bool = UserDefaults.standard.bool(forKey: "CachedPrivacyConsent") + + // Loading + @Published var isLoading: Bool = false + // UI State @Published var showingAddSheet: Bool = false @Published var addItemType: AddItemType = .email + @Published var showingMultiAddSheet: Bool = false + @Published var multiAddType: MultiAddItemType = .tags + @Published var showingRemoveMultiSheet: Bool = false + @Published var removeMultiType: RemoveMultiItemType = .tags + @Published var showingCustomNotificationSheet: Bool = false + @Published var showingTrackEventSheet: Bool = false @Published var toastMessage: String? - + + // MARK: - Computed Properties + + var isLoggedIn: Bool { + externalUserId != nil && !(externalUserId?.isEmpty ?? true) + } + + var loginButtonTitle: String { + isLoggedIn ? "Switch User" : "Login User" + } + + /// Items for remove-multi dialog based on current type + var removeMultiItems: [KeyValueItem] { + switch removeMultiType { + case .aliases: return aliases + case .tags: return tags + case .triggers: return triggers + } + } + // MARK: - Private Properties - + private let service: OneSignalService private var observers = Observers() - + // MARK: - Initialization - + init(service: OneSignalService = .shared) { self.service = service self.appId = service.appId - + self.notificationPermissionGranted = service.hasNotificationPermission + + // Load external user ID from SDK + self.externalUserId = service.externalId + // Initial state sync refreshState() - + // Set up observers setupObservers() + + // Fetch user data if we have a onesignalId + if service.onesignalId != nil { + Task { + await fetchUserDataFromApi() + } + } } - + // MARK: - State Management - + func refreshState() { pushSubscriptionId = service.pushSubscriptionId isPushEnabled = service.isPushEnabled isInAppMessagesPaused = service.isInAppMessagesPaused isLocationShared = service.isLocationShared - + notificationPermissionGranted = service.hasNotificationPermission + externalUserId = service.externalId + // Sync tags from SDK let sdkTags = service.getTags() tags = sdkTags.map { KeyValueItem(key: $0.key, value: $0.value) } } - + + // MARK: - User Data Fetching + + func fetchUserDataFromApi() async { + guard let onesignalId = service.onesignalId else { return } + + isLoading = true + + if let userData = await UserFetchService.shared.fetchUser(appId: appId, onesignalId: onesignalId) { + aliases = userData.aliases.map { KeyValueItem(key: $0.key, value: $0.value) } + tags = userData.tags.map { KeyValueItem(key: $0.key, value: $0.value) } + emails = userData.emails + smsNumbers = userData.smsNumbers + if let extId = userData.externalId, !extId.isEmpty { + externalUserId = extId + } + } + + // Small delay to ensure UI populates before dismissing loading + try? await Task.sleep(nanoseconds: 100_000_000) + isLoading = false + } + // MARK: - Consent - - func toggleConsent() { - consentGiven.toggle() - if consentGiven { + + func toggleConsentRequired() { + consentRequired.toggle() + service.setConsentRequired(consentRequired) + UserDefaults.standard.set(consentRequired, forKey: "CachedConsentRequired") + if !consentRequired { + // When turning off consent required, also grant consent + consentGiven = true service.setConsentGiven(true) - } else { - service.revokeConsent() + UserDefaults.standard.set(true, forKey: "CachedPrivacyConsent") } + showToast(consentRequired ? "Consent required enabled" : "Consent required disabled") + } + + func toggleConsent() { + consentGiven.toggle() + service.setConsentGiven(consentGiven) + UserDefaults.standard.set(consentGiven, forKey: "CachedPrivacyConsent") showToast(consentGiven ? "Consent given" : "Consent revoked") } - + // MARK: - User Management - + func login(externalId: String) { + isLoading = true service.login(externalId: externalId) externalUserId = externalId + + // Clear old data; will be repopulated by fetchUserDataFromApi when user state changes + aliases.removeAll() + emails.removeAll() + smsNumbers.removeAll() + tags.removeAll() + showToast("Logged in as \(externalId)") } - + func logout() { + isLoading = true service.logout() externalUserId = nil aliases.removeAll() @@ -127,25 +211,44 @@ final class OneSignalViewModel: ObservableObject { smsNumbers.removeAll() tags.removeAll() triggers.removeAll() + isLoading = false showToast("Logged out") } - + // MARK: - Aliases - + func addAlias(label: String, id: String) { service.addAlias(label: label, id: id) + aliases.removeAll { $0.key == label } aliases.append(KeyValueItem(key: label, value: id)) showToast("Alias added") } - + + func addAliases(_ pairs: [(String, String)]) { + let dict = Dictionary(pairs, uniquingKeysWith: { _, last in last }) + service.addAliases(dict) + for (key, value) in pairs { + aliases.removeAll { $0.key == key } + aliases.append(KeyValueItem(key: key, value: value)) + } + showToast("\(pairs.count) alias(es) added") + } + func removeAlias(_ item: KeyValueItem) { service.removeAlias(item.key) aliases.removeAll { $0.id == item.id } showToast("Alias removed") } - + + func removeSelectedAliases(_ keys: [String]) { + guard !keys.isEmpty else { return } + service.removeAliases(keys) + aliases.removeAll { keys.contains($0.key) } + showToast("\(keys.count) alias(es) removed") + } + // MARK: - Push Subscription - + func togglePushEnabled() { if isPushEnabled { service.optOutPush() @@ -157,147 +260,242 @@ final class OneSignalViewModel: ObservableObject { showToast("Push enabled") } } - + func requestPushPermission() { service.requestPushPermission { [weak self] accepted in Task { @MainActor in + self?.notificationPermissionGranted = accepted self?.isPushEnabled = accepted self?.showToast(accepted ? "Push permission granted" : "Push permission denied") } } } - + // MARK: - Email - + func addEmail(_ email: String) { service.addEmail(email) - emails.append(email) + if !emails.contains(email) { + emails.append(email) + } showToast("Email added") } - + func removeEmail(_ email: String) { service.removeEmail(email) emails.removeAll { $0 == email } showToast("Email removed") } - + // MARK: - SMS - + func addSms(_ number: String) { service.addSms(number) - smsNumbers.append(number) + if !smsNumbers.contains(number) { + smsNumbers.append(number) + } showToast("SMS added") } - + func removeSms(_ number: String) { service.removeSms(number) smsNumbers.removeAll { $0 == number } showToast("SMS removed") } - + // MARK: - Tags - + func addTag(key: String, value: String) { service.addTag(key: key, value: value) - // Remove existing tag with same key if present tags.removeAll { $0.key == key } tags.append(KeyValueItem(key: key, value: value)) showToast("Tag added") } - + + func addTags(_ pairs: [(String, String)]) { + let dict = Dictionary(pairs, uniquingKeysWith: { _, last in last }) + service.addTags(dict) + for (key, value) in pairs { + tags.removeAll { $0.key == key } + tags.append(KeyValueItem(key: key, value: value)) + } + showToast("\(pairs.count) tag(s) added") + } + func removeTag(_ item: KeyValueItem) { service.removeTag(item.key) tags.removeAll { $0.id == item.id } showToast("Tag removed") } - + + func removeSelectedTags(_ keys: [String]) { + guard !keys.isEmpty else { return } + service.removeTags(keys) + tags.removeAll { keys.contains($0.key) } + showToast("\(keys.count) tag(s) removed") + } + // MARK: - Outcomes - + func sendOutcome(_ name: String) { service.sendOutcome(name) showToast("Outcome '\(name)' sent") } - + func sendOutcome(_ name: String, value: Double) { service.sendOutcome(name, value: NSNumber(value: value)) showToast("Outcome '\(name)' with value \(value) sent") } - + func sendUniqueOutcome(_ name: String) { service.sendUniqueOutcome(name) showToast("Unique outcome '\(name)' sent") } - + // MARK: - In-App Messaging - + func toggleInAppMessagesPaused() { isInAppMessagesPaused.toggle() service.isInAppMessagesPaused = isInAppMessagesPaused + UserDefaults.standard.set(isInAppMessagesPaused, forKey: "CachedInAppMessagesPaused") showToast(isInAppMessagesPaused ? "In-app messages paused" : "In-app messages resumed") } - + func addTrigger(key: String, value: String) { service.addTrigger(key: key, value: value) - // Remove existing trigger with same key if present triggers.removeAll { $0.key == key } triggers.append(KeyValueItem(key: key, value: value)) showToast("Trigger added") } - + + func addTriggers(_ pairs: [(String, String)]) { + let dict = Dictionary(pairs, uniquingKeysWith: { _, last in last }) + service.addTriggers(dict) + for (key, value) in pairs { + triggers.removeAll { $0.key == key } + triggers.append(KeyValueItem(key: key, value: value)) + } + showToast("\(pairs.count) trigger(s) added") + } + func removeTrigger(_ item: KeyValueItem) { service.removeTrigger(item.key) triggers.removeAll { $0.id == item.id } showToast("Trigger removed") } - + + func removeSelectedTriggers(_ keys: [String]) { + guard !keys.isEmpty else { return } + service.removeTriggers(keys) + triggers.removeAll { keys.contains($0.key) } + showToast("\(keys.count) trigger(s) removed") + } + + func clearTriggers() { + service.clearTriggers() + triggers.removeAll() + showToast("All triggers cleared") + } + + // MARK: - Track Event + + func trackEvent(name: String, properties: [String: Any]? = nil) { + OneSignal.User.trackEvent(name: name, properties: properties) + showToast("Event '\(name)' tracked") + } + // MARK: - Location - + func toggleLocationShared() { isLocationShared.toggle() service.isLocationShared = isLocationShared + UserDefaults.standard.set(isLocationShared, forKey: "CachedLocationShared") showToast(isLocationShared ? "Location sharing enabled" : "Location sharing disabled") } - + func promptLocation() { service.requestLocationPermission() showToast("Location permission requested") } - + // MARK: - Notifications - + func clearAllNotifications() { service.clearAllNotifications() showToast("All notifications cleared") } - - func sendTestNotification(_ type: NotificationType) { - showToast("Sending '\(type.rawValue)' notification...") - - NotificationSender.shared.sendNotification(type: type, appId: appId) { [weak self] result in + + func sendSimpleNotification() { + showToast("Sending simple notification...") + NotificationSender.shared.sendSimpleNotification(appId: appId) { [weak self] result in + Task { @MainActor in + switch result { + case .success: + self?.showToast("Simple notification sent!") + case .failure(let error): + self?.showToast("Failed: \(error.localizedDescription)") + } + } + } + } + + func sendNotificationWithImage() { + showToast("Sending image notification...") + NotificationSender.shared.sendNotificationWithImage(appId: appId) { [weak self] result in Task { @MainActor in switch result { case .success: - self?.showToast("'\(type.rawValue)' notification sent!") + self?.showToast("Image notification sent!") case .failure(let error): self?.showToast("Failed: \(error.localizedDescription)") } } } } - + + func sendCustomNotification(title: String, body: String) { + showToast("Sending custom notification...") + NotificationSender.shared.sendCustomNotification(title: title, body: body, appId: appId) { [weak self] result in + Task { @MainActor in + switch result { + case .success: + self?.showToast("Custom notification sent!") + case .failure(let error): + self?.showToast("Failed: \(error.localizedDescription)") + } + } + } + } + func sendTestInAppMessage(_ type: InAppMessageType) { - // In a real app, this would trigger an IAM via your backend - // For demo purposes, we just show a toast - showToast("Test '\(type.rawValue)' in-app message triggered") + let triggerValue: String + switch type { + case .topBanner: triggerValue = "top_banner" + case .bottomBanner: triggerValue = "bottom_banner" + case .centerModal: triggerValue = "center_modal" + case .fullScreen: triggerValue = "full_screen" + } + service.addTrigger(key: "iam_type", value: triggerValue) + showToast("Sent In-App Message: \(type.rawValue)") } - + // MARK: - Add Sheet - + func showAddSheet(for type: AddItemType) { addItemType = type showingAddSheet = true } - + + func showMultiAddSheet(for type: MultiAddItemType) { + multiAddType = type + showingMultiAddSheet = true + } + + func showRemoveMultiSheet(for type: RemoveMultiItemType) { + removeMultiType = type + showingRemoveMultiSheet = true + } + func handleAddItem(key: String, value: String) { switch addItemType { case .alias: @@ -312,24 +510,52 @@ final class OneSignalViewModel: ObservableObject { addTrigger(key: key, value: value) case .externalUserId: login(externalId: value) + case .customNotification: + sendCustomNotification(title: key, body: value) + case .trackEvent: + trackEvent(name: value) } showingAddSheet = false } - + + func handleMultiAdd(pairs: [(String, String)]) { + switch multiAddType { + case .aliases: + addAliases(pairs) + case .tags: + addTags(pairs) + case .triggers: + addTriggers(pairs) + } + showingMultiAddSheet = false + } + + func handleRemoveMulti(keys: [String]) { + switch removeMultiType { + case .aliases: + removeSelectedAliases(keys) + case .tags: + removeSelectedTags(keys) + case .triggers: + removeSelectedTriggers(keys) + } + showingRemoveMultiSheet = false + } + // MARK: - Toast - - private func showToast(_ message: String) { + + func showToast(_ message: String) { toastMessage = message - + // Auto-dismiss after 2 seconds Task { try? await Task.sleep(nanoseconds: 2_000_000_000) toastMessage = nil } } - + // MARK: - Observers - + private func setupObservers() { observers.viewModel = self service.addPushSubscriptionObserver(observers) @@ -342,23 +568,25 @@ final class OneSignalViewModel: ObservableObject { private class Observers: NSObject, OSPushSubscriptionObserver, OSUserStateObserver, OSNotificationPermissionObserver { weak var viewModel: OneSignalViewModel? - + func onPushSubscriptionDidChange(state: OSPushSubscriptionChangedState) { Task { @MainActor in viewModel?.pushSubscriptionId = state.current.id viewModel?.isPushEnabled = state.current.optedIn } } - + func onUserStateDidChange(state: OSUserChangedState) { Task { @MainActor in - // User state changed - could refresh aliases, etc. - print("User state changed: \(state.jsonRepresentation())") + LogManager.shared.i("User", "User state changed: \(state.jsonRepresentation())") + // Fetch fresh user data from API when user state changes + await viewModel?.fetchUserDataFromApi() } } - + func onNotificationPermissionDidChange(_ permission: Bool) { Task { @MainActor in + viewModel?.notificationPermissionGranted = permission viewModel?.isPushEnabled = permission && (viewModel?.isPushEnabled ?? false) } } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddMultiItemSheet.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddMultiItemSheet.swift new file mode 100644 index 000000000..75e734bf2 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddMultiItemSheet.swift @@ -0,0 +1,140 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A multi-pair add dialog with dynamic rows, matching the Android "Add Tags/Aliases/Triggers" dialog. +struct AddMultiItemSheet: View { + let type: MultiAddItemType + let onAdd: ([(String, String)]) -> Void + let onCancel: () -> Void + + @State private var rows: [(key: String, value: String)] = [("", "")] + + var body: some View { + NavigationStack { + VStack(spacing: 16) { + // Title + Text(type.rawValue) + .font(.title2) + .fontWeight(.semibold) + .frame(maxWidth: .infinity, alignment: .leading) + + // Rows + ScrollView { + VStack(spacing: 12) { + ForEach(rows.indices, id: \.self) { index in + HStack(spacing: 8) { + TextField("", text: Binding( + get: { rows[index].key }, + set: { rows[index].key = $0 } + )) + .textFieldStyle(UnderlineTextFieldStyle()) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + + TextField("", text: Binding( + get: { rows[index].value }, + set: { rows[index].value = $0 } + )) + .textFieldStyle(UnderlineTextFieldStyle()) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + + if rows.count > 1 { + Button { + rows.remove(at: index) + } label: { + Image(systemName: "xmark") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.red) + } + .buttonStyle(.borderless) + } + } + } + } + } + + // Add Row button + Button { + rows.append(("", "")) + } label: { + Text("+ ADD ROW") + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(.accentColor) + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + .background(Color(.systemGray6)) + .cornerRadius(8) + } + .buttonStyle(.plain) + + Spacer() + + // Action Buttons + HStack(spacing: 24) { + Spacer() + + Button("CANCEL") { + onCancel() + } + .foregroundColor(.accentColor) + + Button("ADD") { + let pairs = rows + .filter { !$0.key.trimmingCharacters(in: .whitespaces).isEmpty && + !$0.value.trimmingCharacters(in: .whitespaces).isEmpty } + .map { ($0.key, $0.value) } + onAdd(pairs) + } + .foregroundColor(isValid ? .accentColor : .gray) + .disabled(!isValid) + } + .font(.system(size: 16, weight: .semibold)) + } + .padding(24) + } + .presentationDetents([.medium]) + .presentationDragIndicator(.visible) + } + + private var isValid: Bool { + rows.allSatisfy { + !$0.key.trimmingCharacters(in: .whitespaces).isEmpty && + !$0.value.trimmingCharacters(in: .whitespaces).isEmpty + } + } +} + +#Preview { + AddMultiItemSheet( + type: .tags, + onAdd: { pairs in print("Add: \(pairs)") }, + onCancel: { print("Cancel") } + ) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/CustomNotificationSheet.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/CustomNotificationSheet.swift new file mode 100644 index 000000000..896f1f063 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/CustomNotificationSheet.swift @@ -0,0 +1,113 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A dialog for entering a custom notification title and body. +struct CustomNotificationSheet: View { + let onSend: (String, String) -> Void + let onCancel: () -> Void + + @State private var titleText: String = "" + @State private var bodyText: String = "" + @FocusState private var focusedField: Field? + + private enum Field { + case title, body + } + + var body: some View { + NavigationStack { + VStack(spacing: 24) { + // Title + Text("Custom Notification") + .font(.title2) + .fontWeight(.semibold) + .frame(maxWidth: .infinity, alignment: .leading) + + VStack(alignment: .leading, spacing: 8) { + Text("Title") + .font(.caption) + .foregroundColor(.secondary) + TextField("Notification title", text: $titleText) + .textFieldStyle(UnderlineTextFieldStyle()) + .focused($focusedField, equals: .title) + .textInputAutocapitalization(.sentences) + .autocorrectionDisabled() + } + + VStack(alignment: .leading, spacing: 8) { + Text("Body") + .font(.caption) + .foregroundColor(.secondary) + TextField("Notification body", text: $bodyText) + .textFieldStyle(UnderlineTextFieldStyle()) + .focused($focusedField, equals: .body) + .textInputAutocapitalization(.sentences) + .autocorrectionDisabled() + } + + Spacer() + + // Action Buttons + HStack(spacing: 24) { + Spacer() + + Button("CANCEL") { + onCancel() + } + .foregroundColor(.accentColor) + + Button("SEND") { + onSend(titleText, bodyText) + } + .foregroundColor(isValid ? .accentColor : .gray) + .disabled(!isValid) + } + .font(.system(size: 16, weight: .semibold)) + } + .padding(24) + .onAppear { + focusedField = .title + } + } + .presentationDetents([.medium]) + .presentationDragIndicator(.visible) + } + + private var isValid: Bool { + !titleText.trimmingCharacters(in: .whitespaces).isEmpty && + !bodyText.trimmingCharacters(in: .whitespaces).isEmpty + } +} + +#Preview { + CustomNotificationSheet( + onSend: { title, body in print("Send: \(title) - \(body)") }, + onCancel: { print("Cancel") } + ) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/GuidanceBanner.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/GuidanceBanner.swift new file mode 100644 index 000000000..a2f73edf0 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/GuidanceBanner.swift @@ -0,0 +1,54 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A guidance banner that instructs users to add their own App ID. +/// Matches the Android demo's cream/yellow info banner. +struct GuidanceBanner: View { + var body: some View { + VStack(alignment: .leading, spacing: 6) { + Text("Add your own App ID, then rebuild to fully test all functionality.") + .font(.system(size: 14)) + .foregroundColor(.primary) + + Link("Get your keys at onesignal.com", destination: URL(string: "https://onesignal.com")!) + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.accentColor) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(12) + .background(Color(red: 1.0, green: 0.98, blue: 0.90)) + .cornerRadius(12) + } +} + +#Preview { + GuidanceBanner() + .padding() + .background(Color(.systemGroupedBackground)) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift index ae9f25966..efee8e54a 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift @@ -32,7 +32,7 @@ import SwiftUI /// A full-width red button with white uppercase text struct ActionButtonStyle: ButtonStyle { var isDestructive: Bool = false - + func makeBody(configuration: Configuration) -> some View { configuration.label .font(.system(size: 16, weight: .semibold)) @@ -49,7 +49,7 @@ struct ActionButtonStyle: ButtonStyle { struct ActionButton: View { let title: String let action: () -> Void - + var body: some View { Button(action: action) { Text(title) @@ -58,16 +58,74 @@ struct ActionButton: View { } } +/// Outlined button style: red border, white background, red text (for destructive actions like LOGOUT) +struct OutlineActionButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.accentColor) + .textCase(.uppercase) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(Color(.systemBackground).opacity(configuration.isPressed ? 0.8 : 1.0)) + .cornerRadius(8) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color.accentColor, lineWidth: 1.5) + ) + } +} + +/// A full-width outlined action button (red border, white background, red text) +struct OutlineActionButton: View { + let title: String + let action: () -> Void + + var body: some View { + Button(action: action) { + Text(title) + } + .buttonStyle(OutlineActionButtonStyle()) + } +} + +/// A full-width action button with a leading icon (for Send In-App Message buttons) +struct ActionButtonWithIcon: View { + let title: String + let iconName: String + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack(spacing: 12) { + Image(systemName: iconName) + .font(.system(size: 18)) + Text(title) + .font(.system(size: 16, weight: .semibold)) + .textCase(.uppercase) + Spacer() + } + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .padding(.horizontal, 16) + .background(Color.accentColor) + .cornerRadius(8) + } + .buttonStyle(.plain) + } +} + // MARK: - Card Container /// A white card container with rounded corners struct CardContainer: View { let content: Content - + init(@ViewBuilder content: () -> Content) { self.content = content() } - + var body: some View { VStack(spacing: 0) { content @@ -79,18 +137,58 @@ struct CardContainer: View { // MARK: - Section Header -/// A small gray section header +/// A small gray section header with optional info tooltip button struct SectionHeader: View { let title: String - + var tooltipKey: String? + + @State private var showingTooltip = false + var body: some View { - Text(title) - .font(.system(size: 14, weight: .medium)) - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 4) - .padding(.top, 16) - .padding(.bottom, 8) + HStack { + Text(title) + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.secondary) + + Spacer() + + if tooltipKey != nil { + Button { + showingTooltip = true + } label: { + Image(systemName: "info.circle.fill") + .font(.system(size: 16)) + .foregroundColor(.accentColor) + } + .buttonStyle(.borderless) + } + } + .padding(.horizontal, 4) + .padding(.top, 16) + .padding(.bottom, 8) + .alert(isPresented: $showingTooltip) { + if let key = tooltipKey, + let tooltip = TooltipService.shared.getTooltip(key: key) { + var message = tooltip.description + if let options = tooltip.options { + message += "\n" + for option in options { + message += "\n\(option.name): \(option.description)" + } + } + return Alert( + title: Text(tooltip.title), + message: Text(message), + dismissButton: .default(Text("OK")) + ) + } else { + return Alert( + title: Text(title), + message: Text("Tooltip content not available."), + dismissButton: .default(Text("OK")) + ) + } + } } } @@ -100,12 +198,12 @@ struct SectionHeader: View { struct KeyValueRow: View { let item: KeyValueItem let onDelete: (() -> Void)? - + init(item: KeyValueItem, onDelete: (() -> Void)? = nil) { self.item = item self.onDelete = onDelete } - + var body: some View { HStack { VStack(alignment: .leading, spacing: 2) { @@ -115,12 +213,13 @@ struct KeyValueRow: View { Text(item.value) .font(.body) } - + Spacer() - + if let onDelete = onDelete { Button(action: onDelete) { - Image(systemName: "trash") + Image(systemName: "xmark") + .font(.system(size: 14, weight: .medium)) .foregroundColor(.red) } .buttonStyle(.borderless) @@ -138,22 +237,23 @@ struct KeyValueRow: View { struct SingleValueRow: View { let value: String let onDelete: (() -> Void)? - + init(value: String, onDelete: (() -> Void)? = nil) { self.value = value self.onDelete = onDelete } - + var body: some View { HStack { Text(value) .font(.body) - + Spacer() - + if let onDelete = onDelete { Button(action: onDelete) { - Image(systemName: "trash") + Image(systemName: "xmark") + .font(.system(size: 14, weight: .medium)) .foregroundColor(.red) } .buttonStyle(.borderless) @@ -172,17 +272,19 @@ struct InfoRow: View { let label: String let value: String let isMonospaced: Bool - + init(label: String, value: String, isMonospaced: Bool = false) { self.label = label self.value = value self.isMonospaced = isMonospaced } - + var body: some View { - HStack(alignment: .top, spacing: 0) { + HStack(alignment: .top) { Text(label) .font(.system(size: 15, weight: .medium)) + .foregroundColor(.secondary) + Spacer() Text(value) .font(isMonospaced ? .system(size: 15, design: .monospaced) : .system(size: 15)) .foregroundColor(.primary) @@ -201,13 +303,15 @@ struct ToggleRow: View { let title: String let subtitle: String? @Binding var isOn: Bool - - init(title: String, subtitle: String? = nil, isOn: Binding) { + let isEnabled: Bool + + init(title: String, subtitle: String? = nil, isOn: Binding, isEnabled: Bool = true) { self.title = title self.subtitle = subtitle self._isOn = isOn + self.isEnabled = isEnabled } - + var body: some View { HStack { VStack(alignment: .leading, spacing: 2) { @@ -219,14 +323,16 @@ struct ToggleRow: View { .foregroundColor(.secondary) } } - + Spacer() - + Toggle("", isOn: $isOn) .labelsHidden() + .disabled(!isEnabled) } .padding(.horizontal, 16) .padding(.vertical, 12) + .opacity(isEnabled ? 1.0 : 0.5) } } @@ -235,7 +341,7 @@ struct ToggleRow: View { /// A placeholder row for empty lists struct EmptyListRow: View { let message: String - + var body: some View { Text(message) .font(.system(size: 16, weight: .medium)) @@ -255,37 +361,3 @@ struct CardDivider: View { .frame(height: 0.5) } } - -#Preview { - ScrollView { - VStack(spacing: 16) { - SectionHeader(title: "Key-Value Items") - CardContainer { - KeyValueRow( - item: KeyValueItem(key: "external_id", value: "user_123"), - onDelete: {} - ) - CardDivider() - KeyValueRow( - item: KeyValueItem(key: "subscription_tier", value: "premium") - ) - } - - SectionHeader(title: "Info Rows") - CardContainer { - InfoRow(label: "Push-Id:", value: "77e32082-ea27-42e3-a898-c72e141824ef", isMonospaced: true) - CardDivider() - ToggleRow(title: "Enabled", isOn: .constant(true)) - } - - SectionHeader(title: "Empty") - CardContainer { - EmptyListRow(message: "No Items Added") - } - - ActionButton(title: "Add Item") {} - } - .padding() - } - .background(Color(.systemGroupedBackground)) -} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/LogView.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/LogView.swift new file mode 100644 index 000000000..fb03c3db9 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/LogView.swift @@ -0,0 +1,130 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Collapsible log view showing SDK and app logs, matching Android's LogView +struct LogView: View { + @ObservedObject var logManager: LogManager + @State private var isExpanded = false + + var body: some View { + VStack(spacing: 0) { + // Header bar + Button { + withAnimation(.easeInOut(duration: 0.2)) { + isExpanded.toggle() + } + } label: { + HStack { + Text("LOGS") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(.primary) + + Text("(\(logManager.entries.count))") + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.secondary) + + Spacer() + + Button { + logManager.clear() + } label: { + Image(systemName: "trash") + .font(.system(size: 14)) + .foregroundColor(.secondary) + } + .buttonStyle(.borderless) + + Image(systemName: isExpanded ? "chevron.up" : "chevron.down") + .font(.system(size: 12, weight: .medium)) + .foregroundColor(.secondary) + } + .padding(.horizontal, 16) + .padding(.vertical, 10) + } + .buttonStyle(.plain) + + // Log entries (expanded) + if isExpanded { + Divider() + + if logManager.entries.isEmpty { + Text("No logs yet") + .font(.system(size: 13)) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + } else { + ScrollViewReader { proxy in + ScrollView { + LazyVStack(alignment: .leading, spacing: 2) { + ForEach(logManager.entries) { entry in + HStack(alignment: .top, spacing: 6) { + Text(entry.formattedTimestamp) + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(.secondary) + + Text(entry.level.rawValue) + .font(.system(size: 11, weight: .bold, design: .monospaced)) + .foregroundColor(entry.level.color) + + Text(entry.message) + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(.primary) + .lineLimit(2) + } + .padding(.horizontal, 12) + .padding(.vertical, 2) + .id(entry.id) + } + } + .padding(.vertical, 4) + } + .frame(height: 100) + .onChange(of: logManager.entries.count) { _ in + if let lastEntry = logManager.entries.last { + withAnimation { + proxy.scrollTo(lastEntry.id, anchor: .bottom) + } + } + } + } + } + } + } + .background(Color(.systemBackground)) + .cornerRadius(0) + } +} + +#Preview { + VStack { + LogView(logManager: LogManager.shared) + } + .background(Color(.systemGroupedBackground)) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift index 07a26704d..1545313c5 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift @@ -27,39 +27,33 @@ import SwiftUI -/// A grid of notification type buttons -struct NotificationTypeGrid: View { - let onSelect: (NotificationType) -> Void - - private let columns = [ - GridItem(.flexible(), spacing: 12), - GridItem(.flexible(), spacing: 12) - ] - +/// Three full-width push notification buttons matching the Android layout: +/// SIMPLE NOTIFICATION, NOTIFICATION WITH IMAGE, CUSTOM NOTIFICATION +struct SendPushButtons: View { + let onSimple: () -> Void + let onWithImage: () -> Void + let onCustom: () -> Void + var body: some View { - LazyVGrid(columns: columns, spacing: 12) { - ForEach(NotificationType.allCases) { type in - NotificationTypeButton(type: type) { - onSelect(type) - } - } + VStack(spacing: 8) { + ActionButton(title: "Simple", action: onSimple) + ActionButton(title: "With Image", action: onWithImage) + ActionButton(title: "Custom", action: onCustom) } } } -/// A grid of in-app message type buttons -struct InAppMessageTypeGrid: View { +/// Four full-width in-app message buttons with trailing icons matching the Android layout +struct SendInAppButtons: View { let onSelect: (InAppMessageType) -> Void - - private let columns = [ - GridItem(.flexible(), spacing: 12), - GridItem(.flexible(), spacing: 12) - ] - + var body: some View { - LazyVGrid(columns: columns, spacing: 12) { + VStack(spacing: 8) { ForEach(InAppMessageType.allCases) { type in - InAppMessageTypeButton(type: type) { + ActionButtonWithIcon( + title: type.rawValue, + iconName: type.iconName + ) { onSelect(type) } } @@ -67,68 +61,22 @@ struct InAppMessageTypeGrid: View { } } -/// A button for a notification type with icon and label -struct NotificationTypeButton: View { - let type: NotificationType - let action: () -> Void - - var body: some View { - Button(action: action) { - VStack(spacing: 8) { - Image(systemName: type.iconName) - .font(.system(size: 32)) - Text(type.rawValue) - .font(.system(size: 14, weight: .medium)) - .multilineTextAlignment(.center) - } - .foregroundColor(.white) - .frame(maxWidth: .infinity) - .frame(height: 100) - .background(Color.accentColor) - .cornerRadius(12) - } - .buttonStyle(.plain) - } -} - -/// A button for an in-app message type with icon and label -struct InAppMessageTypeButton: View { - let type: InAppMessageType - let action: () -> Void - - var body: some View { - Button(action: action) { - VStack(spacing: 8) { - Image(systemName: type.iconName) - .font(.system(size: 32)) - Text(type.rawValue) - .font(.system(size: 14, weight: .medium)) - .multilineTextAlignment(.center) - } - .foregroundColor(.white) - .frame(maxWidth: .infinity) - .frame(height: 100) - .background(Color.accentColor) - .cornerRadius(12) - } - .buttonStyle(.plain) - } -} - #Preview { ScrollView { VStack(alignment: .leading, spacing: 20) { Text("Send Push Notification") .font(.system(size: 14, weight: .medium)) .foregroundColor(.secondary) - NotificationTypeGrid(onSelect: { type in - print("Selected: \(type.rawValue)") - }) - + SendPushButtons( + onSimple: { print("Simple") }, + onWithImage: { print("With Image") }, + onCustom: { print("Custom") } + ) + Text("Send In-App Message") .font(.system(size: 14, weight: .medium)) .foregroundColor(.secondary) - InAppMessageTypeGrid(onSelect: { type in + SendInAppButtons(onSelect: { type in print("Selected: \(type.rawValue)") }) } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/RemoveMultiSheet.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/RemoveMultiSheet.swift new file mode 100644 index 000000000..85e0e5ed3 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/RemoveMultiSheet.swift @@ -0,0 +1,118 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A checkbox dialog for selectively removing items, matching the Android "Remove Tags/Aliases/Triggers" dialog. +struct RemoveMultiSheet: View { + let type: RemoveMultiItemType + let items: [KeyValueItem] + let onRemove: ([String]) -> Void + let onCancel: () -> Void + + @State private var selectedKeys: Set = [] + + var body: some View { + NavigationStack { + VStack(spacing: 16) { + // Title + Text(type.rawValue) + .font(.title2) + .fontWeight(.semibold) + .frame(maxWidth: .infinity, alignment: .leading) + + // Checkbox list + ScrollView { + VStack(alignment: .leading, spacing: 0) { + ForEach(items) { item in + Button { + if selectedKeys.contains(item.key) { + selectedKeys.remove(item.key) + } else { + selectedKeys.insert(item.key) + } + } label: { + HStack(spacing: 12) { + Image(systemName: selectedKeys.contains(item.key) ? "checkmark.square.fill" : "square") + .font(.system(size: 22)) + .foregroundColor(selectedKeys.contains(item.key) ? .accentColor : .secondary) + + Text("\(item.key): \(item.value)") + .font(.system(size: 16)) + .foregroundColor(.primary) + + Spacer() + } + .padding(.vertical, 10) + } + .buttonStyle(.plain) + + if item.id != items.last?.id { + Divider() + } + } + } + } + + Spacer() + + // Action Buttons + HStack(spacing: 24) { + Spacer() + + Button("CANCEL") { + onCancel() + } + .foregroundColor(.accentColor) + + Button("REMOVE") { + onRemove(Array(selectedKeys)) + } + .foregroundColor(selectedKeys.isEmpty ? .gray : .accentColor) + .disabled(selectedKeys.isEmpty) + } + .font(.system(size: 16, weight: .semibold)) + } + .padding(24) + } + .presentationDetents([.medium]) + .presentationDragIndicator(.visible) + } +} + +#Preview { + RemoveMultiSheet( + type: .tags, + items: [ + KeyValueItem(key: "name", value: "John"), + KeyValueItem(key: "age", value: "25"), + KeyValueItem(key: "city", value: "NYC") + ], + onRemove: { keys in print("Remove: \(keys)") }, + onCancel: { print("Cancel") } + ) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/TrackEventSheet.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/TrackEventSheet.swift new file mode 100644 index 000000000..f3aa65c6d --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/TrackEventSheet.swift @@ -0,0 +1,150 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// A dialog for tracking an event with an optional JSON properties string. +struct TrackEventSheet: View { + let onTrack: (String, [String: Any]?) -> Void + let onCancel: () -> Void + + @State private var eventName: String = "" + @State private var propertiesText: String = "" + @State private var nameError: String? + @State private var propertiesError: String? + @FocusState private var focusedField: Field? + + private enum Field { + case name, properties + } + + var body: some View { + NavigationStack { + VStack(spacing: 24) { + // Title + Text("Track Event") + .font(.title2) + .fontWeight(.semibold) + .frame(maxWidth: .infinity, alignment: .leading) + + VStack(alignment: .leading, spacing: 4) { + Text("Event Name") + .font(.caption) + .foregroundColor(.secondary) + TextField("", text: $eventName) + .textFieldStyle(UnderlineTextFieldStyle()) + .focused($focusedField, equals: .name) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .onChange(of: eventName) { _ in + nameError = nil + } + if let error = nameError { + Text(error) + .font(.caption2) + .foregroundColor(.red) + } + } + + VStack(alignment: .leading, spacing: 4) { + Text("Properties (optional, JSON)") + .font(.caption) + .foregroundColor(.secondary) + TextField("{\"ABC\":123}", text: $propertiesText) + .textFieldStyle(UnderlineTextFieldStyle()) + .focused($focusedField, equals: .properties) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .onChange(of: propertiesText) { _ in + propertiesError = nil + } + if let error = propertiesError { + Text(error) + .font(.caption2) + .foregroundColor(.red) + } + } + + Spacer() + + // Action Buttons + HStack(spacing: 24) { + Spacer() + + Button("CANCEL") { + onCancel() + } + .foregroundColor(.accentColor) + + Button("TRACK") { + submitForm() + } + .foregroundColor(.accentColor) + } + .font(.system(size: 16, weight: .semibold)) + } + .padding(24) + .onAppear { + focusedField = .name + } + } + .presentationDetents([.medium]) + .presentationDragIndicator(.visible) + } + + private func submitForm() { + let trimmedName = eventName.trimmingCharacters(in: .whitespaces) + + if trimmedName.isEmpty { + nameError = "Required" + return + } + + var properties: [String: Any]? + + let trimmedProps = propertiesText.trimmingCharacters(in: .whitespaces) + .replacingOccurrences(of: "\u{201C}", with: "\"") + .replacingOccurrences(of: "\u{201D}", with: "\"") + if !trimmedProps.isEmpty { + guard let data = trimmedProps.data(using: .utf8), + let parsed = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { + propertiesError = "Invalid JSON" + return + } + properties = parsed + } + + onTrack(trimmedName, properties) + } +} + +#Preview { + TrackEventSheet( + onTrack: { name, props in print("Track: \(name), \(String(describing: props))") }, + onCancel: { print("Cancel") } + ) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift index fdef572c3..5ec527d36 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift @@ -27,55 +27,104 @@ import SwiftUI -/// Main content view composing all sections +/// Main content view composing all sections in the order matching the Android demo app struct ContentView: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { NavigationStack { - ScrollView { - VStack(spacing: 0) { - AppInfoSection() - UserSection() - AliasesSection() - PushSection() - EmailsSection() - SMSSection() - TagsSection() - OutcomeEventsSection() - InAppMessagingSection() - TriggersSection() - LocationSection() - NotificationSection() + ZStack { + ScrollView { + VStack(spacing: 0) { + // Collapsible log view at top + LogView(logManager: LogManager.shared) + + // 1. App (includes consent, guidance banner) + AppInfoSection() + + // 2. User (status, external ID, login/logout) + UserSection() + + // 3. Push + PushSection() + + // 4. Send Push Notification + SendPushSection() + + // 5. In-App Messaging + InAppMessagingSection() + + // 6. Send In-App Message + SendInAppSection() + + // 7. Aliases + AliasesSection() + + // 8. Emails + EmailsSection() + + // 9. SMS + SMSSection() + + // 10. Tags + TagsSection() + + // 11. Outcome Events + OutcomeEventsSection() + + // 12. Triggers + TriggersSection() + + // 13. Track Event + TrackEventSection() + + // 14. Location + LocationSection() + + // 15. Next Activity + NextScreenSection() + } + .padding(.horizontal, 16) + .padding(.bottom, 32) + } + .background(Color(.systemGroupedBackground)) + + // Loading overlay + if viewModel.isLoading { + Color.black.opacity(0.3) + .ignoresSafeArea() + ProgressView() + .scaleEffect(1.5) + .tint(.white) } - .padding(.horizontal, 16) - .padding(.bottom, 32) } - .background(Color(.systemGroupedBackground)) .safeAreaInset(edge: .top) { // Compact header bar - HStack { - HStack(spacing: 8) { - Image(systemName: "bell.circle.fill") - .font(.title3) - Text("OneSignal") - .font(.headline) - } - - Spacer() - - Button { - viewModel.refreshState() - } label: { - Image(systemName: "arrow.clockwise") + VStack(spacing: 0) { + Color.accentColor + .frame(height: UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .first?.statusBarManager?.statusBarFrame.height ?? 0) + HStack(spacing: 10) { + Image("OneSignalLogo") + .renderingMode(.template) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 24) + Text("Sample App") + .font(.subheadline) + .opacity(0.9) + Spacer() } + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 12) + .background(Color.accentColor) } - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 12) - .background(Color.accentColor) + .ignoresSafeArea(edges: .top) } .navigationBarHidden(true) + // Single add sheet .sheet(isPresented: $viewModel.showingAddSheet) { AddItemSheet( itemType: viewModel.addItemType, @@ -87,6 +136,55 @@ struct ContentView: View { } ) } + // Multi-add sheet + .sheet(isPresented: $viewModel.showingMultiAddSheet) { + AddMultiItemSheet( + type: viewModel.multiAddType, + onAdd: { pairs in + viewModel.handleMultiAdd(pairs: pairs) + }, + onCancel: { + viewModel.showingMultiAddSheet = false + } + ) + } + // Remove-multi sheet + .sheet(isPresented: $viewModel.showingRemoveMultiSheet) { + RemoveMultiSheet( + type: viewModel.removeMultiType, + items: viewModel.removeMultiItems, + onRemove: { keys in + viewModel.handleRemoveMulti(keys: keys) + }, + onCancel: { + viewModel.showingRemoveMultiSheet = false + } + ) + } + // Custom notification sheet + .sheet(isPresented: $viewModel.showingCustomNotificationSheet) { + CustomNotificationSheet( + onSend: { title, body in + viewModel.sendCustomNotification(title: title, body: body) + viewModel.showingCustomNotificationSheet = false + }, + onCancel: { + viewModel.showingCustomNotificationSheet = false + } + ) + } + // Track event sheet + .sheet(isPresented: $viewModel.showingTrackEventSheet) { + TrackEventSheet( + onTrack: { name, properties in + viewModel.trackEvent(name: name, properties: properties) + viewModel.showingTrackEventSheet = false + }, + onCancel: { + viewModel.showingTrackEventSheet = false + } + ) + } } .toast(message: $viewModel.toastMessage) } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift index 5ad56be35..d0b80f902 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift @@ -27,33 +27,58 @@ import SwiftUI -/// Section displaying app information and consent management +/// Section displaying app information, consent, logged-in state, and login/logout. +/// Merges the previous separate UserSection content. struct AppInfoSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { VStack(spacing: 0) { SectionHeader(title: "App") - + + // App ID card + CardContainer { + InfoRow(label: "App ID", value: viewModel.appId, isMonospaced: true) + } + + // Guidance banner + GuidanceBanner() + .padding(.top, 8) + + // Consent card with up to two toggles CardContainer { - InfoRow(label: "App-Id:", value: viewModel.appId, isMonospaced: true) - CardDivider() ToggleRow( - title: "Privacy Consent", - subtitle: "Grant or revoke privacy consent", + title: "Consent Required", + subtitle: "Require consent before SDK processes data", isOn: Binding( - get: { viewModel.consentGiven }, - set: { _ in viewModel.toggleConsent() } + get: { viewModel.consentRequired }, + set: { _ in viewModel.toggleConsentRequired() } ) ) + + // Privacy Consent toggle (only visible when Consent Required is ON) + if viewModel.consentRequired { + CardDivider() + ToggleRow( + title: "Privacy Consent", + subtitle: "Consent given for data collection", + isOn: Binding( + get: { viewModel.consentGiven }, + set: { _ in viewModel.toggleConsent() } + ) + ) + } } + .padding(.top, 8) } } } #Preview { - AppInfoSection() - .padding() - .background(Color(.systemGroupedBackground)) - .environmentObject(OneSignalViewModel()) + ScrollView { + AppInfoSection() + .padding() + } + .background(Color(.systemGroupedBackground)) + .environmentObject(OneSignalViewModel()) } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift index c6414917d..8554cdc6e 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift @@ -30,22 +30,22 @@ import SwiftUI /// Section for location sharing and permissions struct LocationSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { VStack(spacing: 0) { - SectionHeader(title: "Location") - + SectionHeader(title: "Location", tooltipKey: "location") + CardContainer { ToggleRow( - title: "Location Shared:", - subtitle: "Location will be shared from device", + title: "Location Shared", + subtitle: "Share device location with OneSignal", isOn: Binding( get: { viewModel.isLocationShared }, set: { _ in viewModel.toggleLocationShared() } ) ) } - + ActionButton(title: "Prompt Location") { viewModel.promptLocation() } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift index f92de093d..6e8c0b203 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift @@ -31,23 +31,27 @@ import SwiftUI struct OutcomeEventsSection: View { @EnvironmentObject var viewModel: OneSignalViewModel @State private var showingOutcomeSheet = false - + var body: some View { VStack(spacing: 0) { - SectionHeader(title: "Outcome Events") - + SectionHeader(title: "Outcome Events", tooltipKey: "outcomes") + ActionButton(title: "Send Outcome") { showingOutcomeSheet = true } } .sheet(isPresented: $showingOutcomeSheet) { OutcomeSheet( - onSend: { name, value in - if let value = value { - viewModel.sendOutcome(name, value: value) - } else { - viewModel.sendOutcome(name) - } + onSendNormal: { name in + viewModel.sendOutcome(name) + showingOutcomeSheet = false + }, + onSendUnique: { name in + viewModel.sendUniqueOutcome(name) + showingOutcomeSheet = false + }, + onSendWithValue: { name, value in + viewModel.sendOutcome(name, value: value) showingOutcomeSheet = false }, onCancel: { @@ -61,15 +65,15 @@ struct OutcomeEventsSection: View { /// Section for in-app messaging controls struct InAppMessagingSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { VStack(spacing: 0) { - SectionHeader(title: "In-App Messaging") - + SectionHeader(title: "In-App Messaging", tooltipKey: "inAppMessaging") + CardContainer { ToggleRow( - title: "Pause In-App Messages:", - subtitle: "Toggle in-app messages", + title: "Pause In-App Messages", + subtitle: "Toggle in-app message display", isOn: Binding( get: { viewModel.isInAppMessagesPaused }, set: { _ in viewModel.toggleInAppMessagesPaused() } @@ -80,17 +84,17 @@ struct InAppMessagingSection: View { } } -/// Section for trigger management +/// Section for trigger management with Add Trigger, Add Triggers (multi), Remove Triggers, and Clear Triggers struct TriggersSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { VStack(spacing: 0) { - SectionHeader(title: "Triggers") - + SectionHeader(title: "Triggers", tooltipKey: "triggers") + CardContainer { if viewModel.triggers.isEmpty { - EmptyListRow(message: "No Triggers Added") + EmptyListRow(message: "No triggers added") } else { ForEach(Array(viewModel.triggers.enumerated()), id: \.element.id) { index, trigger in if index > 0 { @@ -102,73 +106,142 @@ struct TriggersSection: View { } } } - - ActionButton(title: "Add Trigger") { + + ActionButton(title: "Add") { viewModel.showAddSheet(for: .trigger) } .padding(.top, 12) + + ActionButton(title: "Add Multiple") { + viewModel.showMultiAddSheet(for: .triggers) + } + .padding(.top, 8) + + // Remove Selected and Clear All - only visible when triggers exist + if !viewModel.triggers.isEmpty { + OutlineActionButton(title: "Remove Selected") { + viewModel.showRemoveMultiSheet(for: .triggers) + } + .padding(.top, 8) + + OutlineActionButton(title: "Clear All") { + viewModel.clearTriggers() + } + .padding(.top, 8) + } + } + } +} + +/// Outcome type options matching Android's radio button selection +private enum OutcomeType: Int, CaseIterable { + case normal = 0 + case unique = 1 + case withValue = 2 + + var label: String { + switch self { + case .normal: return "Normal Outcome" + case .unique: return "Unique Outcome" + case .withValue: return "Outcome with Value" } } } -/// Sheet for sending outcomes +/// Sheet for sending outcomes with radio button selection (Normal/Unique/With Value) struct OutcomeSheet: View { - let onSend: (String, Double?) -> Void + let onSendNormal: (String) -> Void + let onSendUnique: (String) -> Void + let onSendWithValue: (String, Double) -> Void let onCancel: () -> Void - + + @State private var selectedType: OutcomeType = .normal @State private var outcomeName = "" @State private var outcomeValue = "" - @State private var includeValue = false @FocusState private var focusedField: Field? - + private enum Field { case name, value } - + + private var isSendDisabled: Bool { + let nameEmpty = outcomeName.trimmingCharacters(in: .whitespaces).isEmpty + if selectedType == .withValue { + return nameEmpty || Double(outcomeValue) == nil + } + return nameEmpty + } + var body: some View { NavigationStack { - VStack(spacing: 24) { + VStack(spacing: 20) { + // Radio button selection + VStack(alignment: .leading, spacing: 4) { + ForEach(OutcomeType.allCases, id: \.rawValue) { type in + Button { + selectedType = type + } label: { + HStack(spacing: 8) { + Image(systemName: selectedType == type ? "largecircle.fill.circle" : "circle") + .font(.system(size: 20)) + .foregroundColor(selectedType == type ? .accentColor : .secondary) + Text(type.label) + .font(.system(size: 15)) + .foregroundColor(.primary) + } + .padding(.vertical, 6) + } + .buttonStyle(.plain) + } + } + + // Outcome name field (always shown) VStack(alignment: .leading, spacing: 8) { Text("Outcome Name") .font(.caption) .foregroundColor(.secondary) - TextField("Enter outcome name", text: $outcomeName) + TextField("Outcome Name", text: $outcomeName) .textFieldStyle(.roundedBorder) .focused($focusedField, equals: .name) .textInputAutocapitalization(.never) .autocorrectionDisabled() } - - Toggle("Include Value", isOn: $includeValue) - - if includeValue { + + // Value field (only when "Outcome with Value" selected) + if selectedType == .withValue { VStack(alignment: .leading, spacing: 8) { Text("Value") .font(.caption) .foregroundColor(.secondary) - TextField("Enter value", text: $outcomeValue) + TextField("Value", text: $outcomeValue) .textFieldStyle(.roundedBorder) .focused($focusedField, equals: .value) .keyboardType(.decimalPad) } } - + Spacer() - + HStack(spacing: 16) { Button("Cancel") { onCancel() } .foregroundColor(.accentColor) - + Spacer() - + Button("Send") { - let value = includeValue ? Double(outcomeValue) : nil - onSend(outcomeName, value) + switch selectedType { + case .normal: + onSendNormal(outcomeName) + case .unique: + onSendUnique(outcomeName) + case .withValue: + onSendWithValue(outcomeName, Double(outcomeValue) ?? 0) + } } .foregroundColor(.accentColor) - .disabled(outcomeName.trimmingCharacters(in: .whitespaces).isEmpty) + .disabled(isSendDisabled) } .textCase(.uppercase) .font(.system(size: 16, weight: .semibold)) diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NextScreenSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NextScreenSection.swift new file mode 100644 index 000000000..2361b4996 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NextScreenSection.swift @@ -0,0 +1,81 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section with a button to navigate to a secondary placeholder view +struct NextScreenSection: View { + var body: some View { + VStack(spacing: 0) { + NavigationLink(destination: SecondaryView()) { + Text("Next Activity") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .textCase(.uppercase) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(Color.accentColor) + .cornerRadius(8) + } + .buttonStyle(.plain) + .padding(.top, 16) + } + } +} + +/// A placeholder secondary view +struct SecondaryView: View { + var body: some View { + VStack(spacing: 16) { + Image(systemName: "bell.circle.fill") + .font(.system(size: 60)) + .foregroundColor(.accentColor) + + Text("Secondary Activity") + .font(.title2) + .fontWeight(.semibold) + + Text("This is a placeholder secondary view for testing navigation and in-app message display on a different screen.") + .font(.body) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 32) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(.systemGroupedBackground)) + .navigationTitle("Secondary Activity") + .navigationBarTitleDisplayMode(.inline) + } +} + +#Preview { + NavigationStack { + NextScreenSection() + .padding() + } + .background(Color(.systemGroupedBackground)) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift index 6c70e1203..444f77ac2 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift @@ -27,21 +27,38 @@ import SwiftUI -/// Section for sending test push notifications and in-app messages -struct NotificationSection: View { +/// Section for sending test push notifications (3 full-width buttons) +struct SendPushSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { VStack(spacing: 0) { - // Push Notification Grid - SectionHeader(title: "Send Push Notification") - NotificationTypeGrid { type in - viewModel.sendTestNotification(type) - } - - // In-App Message Grid - SectionHeader(title: "Send In-App Message") - InAppMessageTypeGrid { type in + SectionHeader(title: "Send Push Notification", tooltipKey: "sendPushNotification") + + SendPushButtons( + onSimple: { + viewModel.sendSimpleNotification() + }, + onWithImage: { + viewModel.sendNotificationWithImage() + }, + onCustom: { + viewModel.showingCustomNotificationSheet = true + } + ) + } + } +} + +/// Section for sending test in-app messages (4 full-width buttons with trailing icons) +struct SendInAppSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "Send In-App Message", tooltipKey: "sendInAppMessage") + + SendInAppButtons { type in viewModel.sendTestInAppMessage(type) } } @@ -50,8 +67,11 @@ struct NotificationSection: View { #Preview { ScrollView { - NotificationSection() - .padding() + VStack { + SendPushSection() + SendInAppSection() + } + .padding() } .background(Color(.systemGroupedBackground)) .environmentObject(OneSignalViewModel()) diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift index 06f21990f..b0aec36f4 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift @@ -27,17 +27,19 @@ import SwiftUI +// MARK: - Push Section + /// Section for push subscription management struct PushSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { VStack(spacing: 0) { - SectionHeader(title: "Push") - + SectionHeader(title: "Push", tooltipKey: "push") + CardContainer { InfoRow( - label: "Push-Id:", + label: "Push ID", value: viewModel.pushSubscriptionId ?? "Not available", isMonospaced: true ) @@ -47,26 +49,40 @@ struct PushSection: View { isOn: Binding( get: { viewModel.isPushEnabled }, set: { _ in viewModel.togglePushEnabled() } - ) + ), + isEnabled: viewModel.notificationPermissionGranted ) } + + // Prompt Push button - only visible when permission not granted + if !viewModel.notificationPermissionGranted { + ActionButton(title: "Prompt Push") { + viewModel.requestPushPermission() + } + .padding(.top, 12) + } } } } -/// Section for email subscription management +// MARK: - Emails Section + +/// Section for email subscription management with collapsible >5 items struct EmailsSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + @State private var isExpanded = false + var body: some View { VStack(spacing: 0) { - SectionHeader(title: "Emails") - + SectionHeader(title: "Emails", tooltipKey: "emails") + CardContainer { if viewModel.emails.isEmpty { - EmptyListRow(message: "No Emails Added") + EmptyListRow(message: "No emails added") } else { - ForEach(Array(viewModel.emails.enumerated()), id: \.element) { index, email in + let displayEmails = isExpanded ? viewModel.emails : Array(viewModel.emails.prefix(5)) + + ForEach(Array(displayEmails.enumerated()), id: \.element) { index, email in if index > 0 { CardDivider() } @@ -74,9 +90,24 @@ struct EmailsSection: View { viewModel.removeEmail(email) } } + + // "X more available" when collapsed and more than 5 + if !isExpanded && viewModel.emails.count > 5 { + CardDivider() + Button { + isExpanded = true + } label: { + Text("\(viewModel.emails.count - 5) more available") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.accentColor) + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + } + .buttonStyle(.plain) + } } } - + ActionButton(title: "Add Email") { viewModel.showAddSheet(for: .email) } @@ -85,19 +116,24 @@ struct EmailsSection: View { } } -/// Section for SMS subscription management +// MARK: - SMS Section + +/// Section for SMS subscription management with collapsible >5 items struct SMSSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + @State private var isExpanded = false + var body: some View { VStack(spacing: 0) { - SectionHeader(title: "SMSs") - + SectionHeader(title: "SMS", tooltipKey: "sms") + CardContainer { if viewModel.smsNumbers.isEmpty { - EmptyListRow(message: "No SMSs Added") + EmptyListRow(message: "No SMS added") } else { - ForEach(Array(viewModel.smsNumbers.enumerated()), id: \.element) { index, sms in + let displaySms = isExpanded ? viewModel.smsNumbers : Array(viewModel.smsNumbers.prefix(5)) + + ForEach(Array(displaySms.enumerated()), id: \.element) { index, sms in if index > 0 { CardDivider() } @@ -105,9 +141,23 @@ struct SMSSection: View { viewModel.removeSms(sms) } } + + if !isExpanded && viewModel.smsNumbers.count > 5 { + CardDivider() + Button { + isExpanded = true + } label: { + Text("\(viewModel.smsNumbers.count - 5) more available") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.accentColor) + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + } + .buttonStyle(.plain) + } } } - + ActionButton(title: "Add SMS") { viewModel.showAddSheet(for: .sms) } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift index 5177e9591..5bea36558 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift @@ -27,17 +27,17 @@ import SwiftUI -/// Section for managing user tags +/// Section for managing user tags with Add Tag, Add Tags (multi), and Remove Tags struct TagsSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { VStack(spacing: 0) { - SectionHeader(title: "Tags") - + SectionHeader(title: "Tags", tooltipKey: "tags") + CardContainer { if viewModel.tags.isEmpty { - EmptyListRow(message: "No Tags Added") + EmptyListRow(message: "No tags added") } else { ForEach(Array(viewModel.tags.enumerated()), id: \.element.id) { index, tag in if index > 0 { @@ -49,11 +49,24 @@ struct TagsSection: View { } } } - - ActionButton(title: "Add Tag") { + + ActionButton(title: "Add") { viewModel.showAddSheet(for: .tag) } .padding(.top, 12) + + ActionButton(title: "Add Multiple") { + viewModel.showMultiAddSheet(for: .tags) + } + .padding(.top, 8) + + // Remove Selected - only visible when tags exist + if !viewModel.tags.isEmpty { + OutlineActionButton(title: "Remove Selected") { + viewModel.showRemoveMultiSheet(for: .tags) + } + .padding(.top, 8) + } } } } diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TrackEventSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TrackEventSection.swift new file mode 100644 index 000000000..803342735 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TrackEventSection.swift @@ -0,0 +1,50 @@ +/** + * Modified MIT License + * + * Copyright 2024 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import SwiftUI + +/// Section for tracking custom events +struct TrackEventSection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "Track Event", tooltipKey: "trackEvent") + + ActionButton(title: "Track Event") { + viewModel.showingTrackEventSheet = true + } + } + } +} + +#Preview { + TrackEventSection() + .padding() + .background(Color(.systemGroupedBackground)) + .environmentObject(OneSignalViewModel()) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift index c9c762d69..0cbe80a64 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift @@ -27,51 +27,94 @@ import SwiftUI -/// Section for user login/logout +/// Section displaying user login status, external ID, and login/logout buttons. +/// Matches the Android demo's USER section layout. struct UserSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { - VStack(spacing: 12) { - ActionButton(title: "Login User") { + VStack(spacing: 0) { + SectionHeader(title: "User") + + // Status / External ID card + CardContainer { + // Status row + HStack { + Text("Status") + .font(.system(size: 15, weight: .medium)) + .foregroundColor(.secondary) + Spacer() + Text(viewModel.isLoggedIn ? "Logged In" : "Anonymous") + .font(.system(size: 15, weight: .medium)) + .foregroundColor(viewModel.isLoggedIn ? Color(red: 0.20, green: 0.66, blue: 0.33) : .secondary) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + + CardDivider() + + // External ID row + HStack { + Text("External ID") + .font(.system(size: 15, weight: .medium)) + .foregroundColor(.secondary) + Spacer() + Text(viewModel.externalUserId ?? "\u{2014}") + .font(.system(size: 15, weight: .medium)) + .lineLimit(1) + .truncationMode(.middle) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + } + + // Login / Switch User button (filled) + ActionButton(title: viewModel.loginButtonTitle) { viewModel.showAddSheet(for: .externalUserId) } - - ActionButton(title: "Logout User") { - viewModel.logout() + .padding(.top, 12) + + // Logout button (outlined, only when logged in) + if viewModel.isLoggedIn { + OutlineActionButton(title: "Logout User") { + viewModel.logout() + } + .padding(.top, 8) } } - .padding(.top, 12) } } -/// Section for alias management +/// Section for alias management with Add and Add Multiple (read-only list, no delete icons) struct AliasesSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { VStack(spacing: 0) { - SectionHeader(title: "Aliases") - + SectionHeader(title: "Aliases", tooltipKey: "aliases") + CardContainer { if viewModel.aliases.isEmpty { - EmptyListRow(message: "No Aliases Added") + EmptyListRow(message: "No aliases added") } else { ForEach(Array(viewModel.aliases.enumerated()), id: \.element.id) { index, alias in if index > 0 { CardDivider() } - KeyValueRow(item: alias) { - viewModel.removeAlias(alias) - } + KeyValueRow(item: alias) } } } - - ActionButton(title: "Add Alias") { + + ActionButton(title: "Add") { viewModel.showAddSheet(for: .alias) } .padding(.top, 12) + + ActionButton(title: "Add Multiple") { + viewModel.showMultiAddSheet(for: .aliases) + } + .padding(.top, 8) } } } From 0b4ca927a46c710d2c79934c150c9fcf378a9103 Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 19 Feb 2026 00:40:29 -0800 Subject: [PATCH 6/8] add live activities to new demo --- .../project.pbxproj | 176 ++++++++++++++ .../App/OneSignalSwiftUIExampleApp.swift | 9 + .../ExampleAppWidgetAttributes.swift | 37 +++ .../OneSignalSwiftUIExample/Info.plist | 2 + .../Services/LiveActivityController.swift | 83 +++++++ .../ViewModels/OneSignalViewModel.swift | 26 ++ .../Views/ContentView.swift | 5 +- .../Views/Sections/LiveActivitySection.swift | 46 ++++ .../OneSignalWidgetExtension.entitlements | 10 + .../OneSignalWidgetExtension/Info.plist | 11 + .../OneSignalWidgetExtension.swift | 43 ++++ .../OneSignalWidgetExtensionBundle.swift | 13 + ...OneSignalWidgetExtensionLiveActivity.swift | 226 ++++++++++++++++++ 13 files changed, 686 insertions(+), 1 deletion(-) create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ExampleAppWidgetAttributes.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/LiveActivityController.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LiveActivitySection.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension.entitlements create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/Info.plist create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtension.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtensionBundle.swift create mode 100644 iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtensionLiveActivity.swift diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj index 4d59a6aa0..a5578aa23 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample.xcodeproj/project.pbxproj @@ -65,6 +65,17 @@ EFE6A330EF362418C68B3F6E /* OneSignalCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 533347B40563BF22FB6EAC9C /* OneSignalCore.framework */; }; F8CF0C2C1A1F8842BCF86784 /* OneSignalExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B441216A875FE941AED4964E /* OneSignalExtension.framework */; }; FEEC78AA5DE04832192D8F92 /* LogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B8D8F4DF8E47F8BA53423A /* LogView.swift */; }; + 63C9FA76B516816B1B0AED19 /* ExampleAppWidgetAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29513EB5D1B9AF2511CAD95A /* ExampleAppWidgetAttributes.swift */; }; + 34446FC058592648ACCD90ED /* ExampleAppWidgetAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29513EB5D1B9AF2511CAD95A /* ExampleAppWidgetAttributes.swift */; }; + 1630D207F53B85B5C5A4806C /* LiveActivityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503C246BD2BCA26C96E0EF56 /* LiveActivityController.swift */; }; + 2BD9DE389DE56B72D031CA01 /* OneSignalWidgetExtensionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CE2D8F93E1D14CD1F6D3078 /* OneSignalWidgetExtensionBundle.swift */; }; + 50091A346024B3DDB1053760 /* OneSignalWidgetExtensionLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99719345F5489106C54EBEA8 /* OneSignalWidgetExtensionLiveActivity.swift */; }; + 544423F60A011F8B8D2971BF /* OneSignalWidgetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284A0F176D491DA3AF85DD6C /* OneSignalWidgetExtension.swift */; }; + 32AEC6117F13C0D50A3314D3 /* OneSignalLiveActivities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A265470C544D20273EEB62B0 /* OneSignalLiveActivities.framework */; }; + 2D326C2E53A5678B824B52F5 /* OneSignalWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 14520AFC1CB985AE6C04E572 /* OneSignalWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 6DFB09B0E23B8B3D42ED4C33 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70C8DB47C4C80E1D595FE1DE /* WidgetKit.framework */; }; + 2A7E93C9978EA3A5DD713D88 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2B5D4431B0C1F4352A359427 /* SwiftUI.framework */; }; + 45050FAF882B8B140E005816 /* LiveActivitySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D896003C77EC90B7A3C75BD /* LiveActivitySection.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -75,6 +86,13 @@ remoteGlobalIDString = D99FA6E45A25EB4DB04DA0CE; remoteInfo = OneSignalNotificationServiceExtension; }; + EEAB30880E225535B2F366FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7B7016AA7DF5AB54CD96701C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7729C3101598B16A32244980; + remoteInfo = OneSignalWidgetExtension; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -105,6 +123,7 @@ dstSubfolderSpec = 13; files = ( 1FC9DF3B1B444569E585CA2B /* OneSignalNotificationServiceExtension.appex in Embed App Extensions */, + 2D326C2E53A5678B824B52F5 /* OneSignalWidgetExtension.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -161,6 +180,17 @@ FDEE7A98A0EBB6BF767A041D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; FE9826D274F62E747F3A931B /* OneSignalSwiftUIExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalSwiftUIExampleApp.swift; sourceTree = ""; }; FEE98DA6A075AEB7709CCBE2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 14520AFC1CB985AE6C04E572 /* OneSignalWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OneSignalWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 1CE2D8F93E1D14CD1F6D3078 /* OneSignalWidgetExtensionBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalWidgetExtensionBundle.swift; sourceTree = ""; }; + 99719345F5489106C54EBEA8 /* OneSignalWidgetExtensionLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalWidgetExtensionLiveActivity.swift; sourceTree = ""; }; + 284A0F176D491DA3AF85DD6C /* OneSignalWidgetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalWidgetExtension.swift; sourceTree = ""; }; + 18DDEB83BD3876BA863B546E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 552B0F025E1F9787E6AB2C3C /* OneSignalWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OneSignalWidgetExtension.entitlements; sourceTree = ""; }; + 29513EB5D1B9AF2511CAD95A /* ExampleAppWidgetAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleAppWidgetAttributes.swift; sourceTree = ""; }; + 503C246BD2BCA26C96E0EF56 /* LiveActivityController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityController.swift; sourceTree = ""; }; + 70C8DB47C4C80E1D595FE1DE /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 2B5D4431B0C1F4352A359427 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 2D896003C77EC90B7A3C75BD /* LiveActivitySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivitySection.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -195,6 +225,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D37A7E2DA634F0A44E3347CD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6DFB09B0E23B8B3D42ED4C33 /* WidgetKit.framework in Frameworks */, + 2A7E93C9978EA3A5DD713D88 /* SwiftUI.framework in Frameworks */, + 32AEC6117F13C0D50A3314D3 /* OneSignalLiveActivities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -222,10 +262,22 @@ children = ( A0506934E0F27B864F3AC273 /* OneSignalSwiftUIExample.app */, 50B6C9352575073F1873F639 /* OneSignalNotificationServiceExtension.appex */, + 14520AFC1CB985AE6C04E572 /* OneSignalWidgetExtension.appex */, ); name = Products; sourceTree = ""; }; + 1464CBA90C4A8A500815FA26 /* OneSignalWidgetExtension */ = { + isa = PBXGroup; + children = ( + 1CE2D8F93E1D14CD1F6D3078 /* OneSignalWidgetExtensionBundle.swift */, + 99719345F5489106C54EBEA8 /* OneSignalWidgetExtensionLiveActivity.swift */, + 284A0F176D491DA3AF85DD6C /* OneSignalWidgetExtension.swift */, + 18DDEB83BD3876BA863B546E /* Info.plist */, + ); + path = OneSignalWidgetExtension; + sourceTree = ""; + }; 4320E847C9D875EA2342DB5F /* Models */ = { isa = PBXGroup; children = ( @@ -280,6 +332,8 @@ 985DE7F1C6162433D11943B0 /* SystemConfiguration.framework */, 6CD2DA9DC09F1EF7D989C291 /* UserNotifications.framework */, A084A32F6E0CA6A6354705C0 /* WebKit.framework */, + 70C8DB47C4C80E1D595FE1DE /* WidgetKit.framework */, + 2B5D4431B0C1F4352A359427 /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -289,6 +343,8 @@ children = ( A21AEF40FA55F829CF1DA4B4 /* OneSignalSwiftUIExample */, 1E3DC3EAE97EBB641F71FE7D /* OneSignalNotificationServiceExtension */, + 1464CBA90C4A8A500815FA26 /* OneSignalWidgetExtension */, + 552B0F025E1F9787E6AB2C3C /* OneSignalWidgetExtension.entitlements */, 82619F0C016CC5B46729A454 /* Frameworks */, 3D3976C085CF6E7D61D7F075 /* Products */, ); @@ -298,6 +354,7 @@ isa = PBXGroup; children = ( 564304E2BA4613FD7649116D /* AppInfoSection.swift */, + 2D896003C77EC90B7A3C75BD /* LiveActivitySection.swift */, 60D373C8C00DE06FC5CD01C5 /* LocationSection.swift */, 962607A12EFC53D2F77E5948 /* MessagingSection.swift */, E237FD3F84CE3831CAB8A6DA /* NotificationSection.swift */, @@ -326,6 +383,7 @@ DE2A74C6876FFA7E42683810 /* Views */, FEE98DA6A075AEB7709CCBE2 /* Assets.xcassets */, FDEE7A98A0EBB6BF767A041D /* Info.plist */, + 29513EB5D1B9AF2511CAD95A /* ExampleAppWidgetAttributes.swift */, 130E67036ECE13331B1CFFFA /* Services */, 6CC0A9DDAC1CB43170E00043 /* Views */, ); @@ -369,6 +427,7 @@ 85FF4424D702D8686CC819B8 /* OneSignalService.swift */, 3C25C0582F3409E9005E5E9A /* NotificationSender.swift */, D676BEDC8A5CB48BBF8FFAF9 /* LogManager.swift */, + 503C246BD2BCA26C96E0EF56 /* LiveActivityController.swift */, ); path = Services; sourceTree = ""; @@ -407,12 +466,30 @@ ); dependencies = ( 454F2CC9F3CE935E9091072B /* PBXTargetDependency */, + 330BABE7E258541AC08182DB /* PBXTargetDependency */, ); name = OneSignalSwiftUIExample; productName = OneSignalSwiftUIExample; productReference = A0506934E0F27B864F3AC273 /* OneSignalSwiftUIExample.app */; productType = "com.apple.product-type.application"; }; + 7729C3101598B16A32244980 /* OneSignalWidgetExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0A4805483C01DAEEFCD42066 /* Build configuration list for PBXNativeTarget "OneSignalWidgetExtension" */; + buildPhases = ( + BB92D8E493D58D5BF63C9978 /* Sources */, + D37A7E2DA634F0A44E3347CD /* Frameworks */, + E119F524BCE81A602BBBBEB6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OneSignalWidgetExtension; + productName = OneSignalWidgetExtension; + productReference = 14520AFC1CB985AE6C04E572 /* OneSignalWidgetExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -428,6 +505,9 @@ F27E45A0AADC4454C26D8C07 = { ProvisioningStyle = Automatic; }; + 7729C3101598B16A32244980 = { + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 74CB95FE4C8E95D018B0A01A /* Build configuration list for PBXProject "OneSignalSwiftUIExample" */; @@ -446,6 +526,7 @@ targets = ( F27E45A0AADC4454C26D8C07 /* OneSignalSwiftUIExample */, D99FA6E45A25EB4DB04DA0CE /* OneSignalNotificationServiceExtension */, + 7729C3101598B16A32244980 /* OneSignalWidgetExtension */, ); }; /* End PBXProject section */ @@ -466,6 +547,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E119F524BCE81A602BBBBEB6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -501,6 +589,9 @@ 8F3FE40D8D603A8879C6A111 /* TrackEventSheet.swift in Sources */, 44345BEEC3249B530635D91C /* TrackEventSection.swift in Sources */, 7C93699D746B0B5807AD13BB /* NextScreenSection.swift in Sources */, + 63C9FA76B516816B1B0AED19 /* ExampleAppWidgetAttributes.swift in Sources */, + 1630D207F53B85B5C5A4806C /* LiveActivityController.swift in Sources */, + 45050FAF882B8B140E005816 /* LiveActivitySection.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -512,6 +603,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + BB92D8E493D58D5BF63C9978 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2BD9DE389DE56B72D031CA01 /* OneSignalWidgetExtensionBundle.swift in Sources */, + 50091A346024B3DDB1053760 /* OneSignalWidgetExtensionLiveActivity.swift in Sources */, + 544423F60A011F8B8D2971BF /* OneSignalWidgetExtension.swift in Sources */, + 34446FC058592648ACCD90ED /* ExampleAppWidgetAttributes.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -520,6 +622,11 @@ target = D99FA6E45A25EB4DB04DA0CE /* OneSignalNotificationServiceExtension */; targetProxy = DC39EC3039BB65E2D21F753F /* PBXContainerItemProxy */; }; + 330BABE7E258541AC08182DB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7729C3101598B16A32244980 /* OneSignalWidgetExtension */; + targetProxy = EEAB30880E225535B2F366FA /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -748,6 +855,66 @@ }; name = Debug; }; + D80553ADD3452D4E48E4EB6E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = OneSignalWidgetExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 99SW8E36CT; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = OneSignalWidgetExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = OneSignalWidgetExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 5.4.1; + PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.OneSignalWidgetExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C93E6DBD405CD230ED4C302B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = OneSignalWidgetExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 99SW8E36CT; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = OneSignalWidgetExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = OneSignalWidgetExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 5.4.1; + PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.OneSignalWidgetExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -778,6 +945,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + 0A4805483C01DAEEFCD42066 /* Build configuration list for PBXNativeTarget "OneSignalWidgetExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D80553ADD3452D4E48E4EB6E /* Debug */, + C93E6DBD405CD230ED4C302B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; /* End XCConfigurationList section */ }; rootObject = 7B7016AA7DF5AB54CD96701C /* Project object */; diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift index 2907835cb..5ffabb522 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift @@ -37,6 +37,10 @@ struct OneSignalSwiftUIExampleApp: App { WindowGroup { ContentView() .environmentObject(viewModel) + .onOpenURL { url in + let originalURL = OneSignal.LiveActivities.trackClickAndReturnOriginal(url) + LogManager.shared.i("LiveActivity", "Opened with URL: \(url), original: \(String(describing: originalURL))") + } } } } @@ -64,6 +68,11 @@ class AppDelegate: NSObject, UIApplicationDelegate { // Initialize OneSignal OneSignalService.shared.initialize(launchOptions: launchOptions) + // Start Live Activity listeners + if #available(iOS 16.1, *) { + LiveActivityController.start() + } + // Restore cached SDK states before UI loads restoreCachedStates() diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ExampleAppWidgetAttributes.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ExampleAppWidgetAttributes.swift new file mode 100644 index 000000000..6a8ed79a3 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ExampleAppWidgetAttributes.swift @@ -0,0 +1,37 @@ +#if targetEnvironment(macCatalyst) +#else +import ActivityKit +import OneSignalLiveActivities + +struct ExampleAppFirstWidgetAttributes: OneSignalLiveActivityAttributes { + public struct ContentState: OneSignalLiveActivityContentState { + var message: String + var onesignal: OneSignalLiveActivityContentStateData? + } + + var title: String + var onesignal: OneSignalLiveActivityAttributeData +} + +struct ExampleAppSecondWidgetAttributes: OneSignalLiveActivityAttributes { + public struct ContentState: OneSignalLiveActivityContentState { + var message: String + var status: String + var progress: Double + var bugs: Int + var onesignal: OneSignalLiveActivityContentStateData? + } + + var title: String + var onesignal: OneSignalLiveActivityAttributeData +} + +struct ExampleAppThirdWidgetAttributes: ActivityAttributes { + public struct ContentState: Codable, Hashable { + var message: String + } + + var title: String + var isPushToStart: Bool +} +#endif diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist index 994a1f9a2..82b16a177 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist @@ -29,6 +29,8 @@ UIApplicationSupportsMultipleScenes + NSSupportsLiveActivities + UIBackgroundModes remote-notification diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/LiveActivityController.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/LiveActivityController.swift new file mode 100644 index 000000000..090a51ba4 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/LiveActivityController.swift @@ -0,0 +1,83 @@ +import Foundation +import OneSignalFramework +#if targetEnvironment(macCatalyst) +#else +import ActivityKit +import OneSignalLiveActivities + +class LiveActivityController { + + @available(iOS 16.1, *) + static func start() { + OneSignal.LiveActivities.setup(ExampleAppFirstWidgetAttributes.self) + OneSignal.LiveActivities.setup(ExampleAppSecondWidgetAttributes.self) + OneSignal.LiveActivities.setupDefault() + + if #available(iOS 17.2, *) { + Task { + for try await data in Activity.pushToStartTokenUpdates { + let token = data.map { String(format: "%02x", $0) }.joined() + OneSignal.LiveActivities.setPushToStartToken(ExampleAppThirdWidgetAttributes.self, withToken: token) + } + } + Task { + for await activity in Activity.activityUpdates + where activity.attributes.isPushToStart { + Task { + for await pushToken in activity.pushTokenUpdates { + let token = pushToken.map { String(format: "%02x", $0) }.joined() + OneSignal.LiveActivities.enter("my-activity-id", withToken: token) + } + } + } + } + } + } + + static var counter1 = 0 + + @available(iOS 16.1, *) + static func createOneSignalAwareActivity(activityId: String) { + counter1 += 1 + let oneSignalAttribute = OneSignalLiveActivityAttributeData.create(activityId: activityId) + let attributes = ExampleAppFirstWidgetAttributes(title: "#\(counter1) Live Activity", onesignal: oneSignalAttribute) + let contentState = ExampleAppFirstWidgetAttributes.ContentState(message: "Update this message through push") + do { + _ = try Activity.request( + attributes: attributes, + contentState: contentState, + pushType: .token) + } catch { + print(error.localizedDescription) + } + } + + @available(iOS 16.1, *) + static func createDefaultActivity(activityId: String) { + let attributeData: [String: Any] = ["title": "in-app-title"] + let contentData: [String: Any] = ["message": ["en": "HELLO", "es": "HOLA"], "progress": 0.58, "status": "1/15", "bugs": 2] + OneSignal.LiveActivities.startDefault(activityId, attributes: attributeData, content: contentData) + } + + static var counter2 = 0 + + @available(iOS 16.1, *) + static func createActivity(activityId: String) async { + counter2 += 1 + let attributes = ExampleAppThirdWidgetAttributes(title: "#\(counter2) Live Activity", isPushToStart: false) + let contentState = ExampleAppThirdWidgetAttributes.ContentState(message: "Update this message through push") + do { + let activity = try Activity.request( + attributes: attributes, + contentState: contentState, + pushType: .token) + for await data in activity.pushTokenUpdates { + let myToken = data.map { String(format: "%02x", $0) }.joined() + OneSignal.LiveActivities.enter(activityId, withToken: myToken) + } + } catch { + print(error.localizedDescription) + } + } +} +#endif diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift index 3f3e95926..bbe2bc5d9 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift @@ -418,6 +418,32 @@ final class OneSignalViewModel: ObservableObject { showToast("Location permission requested") } + // MARK: - Live Activities + + func enterLiveActivity(activityId: String) { + let id = activityId.trimmingCharacters(in: .whitespacesAndNewlines) + guard !id.isEmpty else { + showToast("Please enter an activity ID") + return + } + if #available(iOS 16.1, *) { + LiveActivityController.createOneSignalAwareActivity(activityId: id) + showToast("Live Activity '\(id)' entered") + } else { + showToast("Live Activities require iOS 16.1+") + } + } + + func exitLiveActivity(activityId: String) { + let id = activityId.trimmingCharacters(in: .whitespacesAndNewlines) + guard !id.isEmpty else { + showToast("Please enter an activity ID") + return + } + OneSignal.LiveActivities.exit(id) + showToast("Live Activity '\(id)' exited") + } + // MARK: - Notifications func clearAllNotifications() { diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift index 5ec527d36..d06a3a6a6 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift @@ -81,7 +81,10 @@ struct ContentView: View { // 14. Location LocationSection() - // 15. Next Activity + // 15. Live Activities + LiveActivitySection() + + // 16. Next Activity NextScreenSection() } .padding(.horizontal, 16) diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LiveActivitySection.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LiveActivitySection.swift new file mode 100644 index 000000000..294c904e6 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LiveActivitySection.swift @@ -0,0 +1,46 @@ +import SwiftUI +import OneSignalFramework + +struct LiveActivitySection: View { + @EnvironmentObject var viewModel: OneSignalViewModel + @State private var activityId: String = "" + + var body: some View { + VStack(spacing: 0) { + SectionHeader(title: "Live Activities", tooltipKey: "liveActivities") + + CardContainer { + HStack { + Text("Activity ID") + .font(.system(size: 15, weight: .medium)) + .foregroundColor(.secondary) + Spacer() + TextField("Enter activity ID", text: $activityId) + .font(.system(size: 15)) + .multilineTextAlignment(.trailing) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + } + + ActionButton(title: "Enter Live Activity") { + viewModel.enterLiveActivity(activityId: activityId) + } + .padding(.top, 12) + + OutlineActionButton(title: "Exit Live Activity") { + viewModel.exitLiveActivity(activityId: activityId) + } + .padding(.top, 8) + } + } +} + +#Preview { + LiveActivitySection() + .padding() + .background(Color(.systemGroupedBackground)) + .environmentObject(OneSignalViewModel()) +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension.entitlements b/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension.entitlements new file mode 100644 index 000000000..ee95ab7e5 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/Info.plist b/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/Info.plist new file mode 100644 index 000000000..0f118fb75 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtension.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtension.swift new file mode 100644 index 000000000..60d247cf3 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtension.swift @@ -0,0 +1,43 @@ +import WidgetKit +import SwiftUI + +struct SimpleProvider: TimelineProvider { + func placeholder(in context: Context) -> SimpleEntry { + SimpleEntry(date: Date()) + } + + func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) { + completion(SimpleEntry(date: Date())) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + var entries: [SimpleEntry] = [] + let currentDate = Date() + for hourOffset in 0..<5 { + let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! + entries.append(SimpleEntry(date: entryDate)) + } + completion(Timeline(entries: entries, policy: .atEnd)) + } +} + +struct SimpleEntry: TimelineEntry { + let date: Date +} + +struct OneSignalWidgetExtensionWidget: Widget { + let kind: String = "OneSignalWidgetExtension" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: SimpleProvider()) { entry in + if #available(iOS 17.0, *) { + Text(entry.date, style: .time) + .containerBackground(.fill.tertiary, for: .widget) + } else { + Text(entry.date, style: .time) + } + } + .configurationDisplayName("OneSignal Widget") + .description("An example widget.") + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtensionBundle.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtensionBundle.swift new file mode 100644 index 000000000..1dd09de51 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtensionBundle.swift @@ -0,0 +1,13 @@ +import WidgetKit +import SwiftUI + +@main +struct OneSignalWidgetExtensionBundle: WidgetBundle { + var body: some Widget { + OneSignalWidgetExtensionWidget() + ExampleAppFirstWidget() + ExampleAppSecondWidget() + ExampleAppThirdWidget() + DefaultOneSignalLiveActivityWidget() + } +} diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtensionLiveActivity.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtensionLiveActivity.swift new file mode 100644 index 000000000..4d5fbaaa2 --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalWidgetExtension/OneSignalWidgetExtensionLiveActivity.swift @@ -0,0 +1,226 @@ +import ActivityKit +import WidgetKit +import SwiftUI +import OneSignalLiveActivities + +struct ExampleAppFirstWidget: Widget { + var body: some WidgetConfiguration { + ActivityConfiguration(for: ExampleAppFirstWidgetAttributes.self) { context in + VStack { + Spacer() + Text("FIRST: " + context.attributes.title).font(.headline) + Spacer() + HStack { + Spacer() + Label { + Text(String(context.state.message)) + } icon: { + Image(systemName: "bell.circle.fill") + .resizable() + .scaledToFit() + .frame(width: 40.0, height: 40.0) + } + Spacer() + } + Spacer() + } + .foregroundColor(.black) + .onesignalWidgetURL(URL(string: "https://example.com/page?param1=value1¶m2=value2#section"), context: context) + .activitySystemActionForegroundColor(.black) + .activityBackgroundTint(.white) + } dynamicIsland: { context in + DynamicIsland { + DynamicIslandExpandedRegion(.leading) { + Text("Leading") + } + DynamicIslandExpandedRegion(.trailing) { + Text("Trailing") + } + DynamicIslandExpandedRegion(.bottom) { + Text("Bottom") + } + } compactLeading: { + Text("L") + } compactTrailing: { + Text("T") + } minimal: { + Text("Min") + } + .onesignalWidgetURL(URL(string: "https://example.com/page?param1=value1¶m2=value2#section"), context: context) + .keylineTint(Color.red) + } + } +} + +struct ExampleAppSecondWidget: Widget { + var body: some WidgetConfiguration { + ActivityConfiguration(for: ExampleAppSecondWidgetAttributes.self) { context in + VStack { + Spacer() + HStack { + Image(systemName: "bell.circle.fill") + .resizable() + .scaledToFit() + .frame(width: 40.0, height: 40.0) + Spacer() + Text(context.attributes.title).font(.headline) + } + Spacer() + HStack(alignment: .firstTextBaseline, spacing: 16) { + Text("Update: ").font(.title2) + Spacer() + Text(context.state.message) + } + Spacer() + HStack(alignment: .firstTextBaseline, spacing: 16) { + Text("Progress: ").font(.title2) + ProgressView(value: context.state.progress) + .padding([.bottom, .top], 5) + Text(context.state.status) + } + HStack(alignment: .firstTextBaseline, spacing: 16) { + Text("Bugs: ").font(.title2) + Spacer() + Text(String(context.state.bugs)) + } + Spacer() + } + .foregroundColor(.black) + .padding([.all], 20) + .activitySystemActionForegroundColor(.black) + .activityBackgroundTint(.white) + .onesignalWidgetURL(URL(string: "https://example.com/page?param1=value1¶m2=value2#section"), context: context) + } dynamicIsland: { context in + DynamicIsland { + DynamicIslandExpandedRegion(.leading) { + Text("Leading") + } + DynamicIslandExpandedRegion(.trailing) { + Text("Trailing") + } + DynamicIslandExpandedRegion(.bottom) { + Text("Bottom") + } + } compactLeading: { + Text("L") + } compactTrailing: { + Text("T") + } minimal: { + Text("Min") + } + .keylineTint(Color.red) + .onesignalWidgetURL(URL(string: "https://example.com/page?param1=value1¶m2=value2#section"), context: context) + } + } +} + +struct ExampleAppThirdWidget: Widget { + var body: some WidgetConfiguration { + ActivityConfiguration(for: ExampleAppThirdWidgetAttributes.self) { context in + VStack { + Spacer() + Text("THIRD: " + context.attributes.title).font(.headline) + Spacer() + HStack { + Spacer() + Label { + Text(context.state.message) + } icon: { + Image(systemName: "bell.circle.fill") + .resizable() + .scaledToFit() + .frame(width: 40.0, height: 40.0) + } + Spacer() + } + Spacer() + } + .foregroundColor(.black) + .activitySystemActionForegroundColor(.black) + .activityBackgroundTint(.white) + } dynamicIsland: { _ in + DynamicIsland { + DynamicIslandExpandedRegion(.leading) { + Text("Leading") + } + DynamicIslandExpandedRegion(.trailing) { + Text("Trailing") + } + DynamicIslandExpandedRegion(.bottom) { + Text("Bottom") + } + } compactLeading: { + Text("L") + } compactTrailing: { + Text("T") + } minimal: { + Text("Min") + } + .widgetURL(URL(string: "http://www.apple.com")) + .keylineTint(Color.red) + } + } +} + +struct DefaultOneSignalLiveActivityWidget: Widget { + var body: some WidgetConfiguration { + ActivityConfiguration(for: DefaultLiveActivityAttributes.self) { context in + VStack { + Spacer() + HStack { + Image(systemName: "bell.circle.fill") + .resizable() + .scaledToFit() + .frame(width: 40.0, height: 40.0) + Spacer() + Text("DEFAULT: " + (context.attributes.data["title"]?.asString() ?? "")).font(.headline) + } + Spacer() + HStack(alignment: .firstTextBaseline, spacing: 16) { + Text("Update: ").font(.title2) + Spacer() + Text(context.state.data["message"]?.asDict()?["en"]?.asString() ?? "") + } + Spacer() + HStack(alignment: .firstTextBaseline, spacing: 16) { + Text("Progress: ").font(.title2) + ProgressView( + value: context.state.data["progress"]?.asDouble() ?? 0.0 + ).padding([.bottom, .top], 5) + Text(context.state.data["status"]?.asString() ?? "") + } + HStack(alignment: .firstTextBaseline, spacing: 16) { + Text("Bugs: ").font(.title2) + Spacer() + Text(String(context.state.data["bugs"]?.asInt() ?? 0)) + } + Spacer() + } + .foregroundColor(.black) + .padding([.all], 20) + .activitySystemActionForegroundColor(.black) + .activityBackgroundTint(.white) + .onesignalWidgetURL(URL(string: "https://example.com/page?param1=value1¶m2=value2#section"), context: context) + } dynamicIsland: { context in + DynamicIsland { + DynamicIslandExpandedRegion(.leading) { + Text("Leading") + } + DynamicIslandExpandedRegion(.trailing) { + Text("Trailing") + } + DynamicIslandExpandedRegion(.bottom) { + Text("Bottom") + } + } compactLeading: { + Text("L") + } compactTrailing: { + Text("T") + } minimal: { + Text("Min") + } + .keylineTint(Color.red) + .onesignalWidgetURL(URL(string: "https://example.com/page?param1=value1¶m2=value2#section"), context: context) + } + } +} From 79669d35f37ef742c076611b141595071133c9f8 Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 19 Feb 2026 02:00:56 -0800 Subject: [PATCH 7/8] add build prompt --- .../build_app_prompt.md | 1117 +++++++++++++++++ 1 file changed, 1117 insertions(+) create mode 100644 iOS_SDK/OneSignalSwiftUIExample/build_app_prompt.md diff --git a/iOS_SDK/OneSignalSwiftUIExample/build_app_prompt.md b/iOS_SDK/OneSignalSwiftUIExample/build_app_prompt.md new file mode 100644 index 000000000..35a15cb5f --- /dev/null +++ b/iOS_SDK/OneSignalSwiftUIExample/build_app_prompt.md @@ -0,0 +1,1117 @@ +# OneSignal iOS Sample App - Build Guide + +This document contains all the prompts and requirements needed to build the OneSignal SwiftUI Sample App from scratch. Give these prompts to an AI assistant or follow them manually to recreate the app. + +--- + +## Phase 1: Initial Setup + +### Prompt 1.1 - Project Foundation + +``` +Build a sample iOS app with: +- SwiftUI app lifecycle (@main App struct with UIApplicationDelegateAdaptor) +- MVVM architecture with a single ObservableObject ViewModel +- @MainActor ViewModel with @Published properties +- @EnvironmentObject for passing ViewModel to views +- iOS 16.0 minimum deployment target +- Xcode project (not Swift Package Manager) +- Bundle identifier: com.onesignal.example +- All sheets should have EMPTY input fields (for test automation - test framework enters values) +- OneSignal brand colors via AccentColor in asset catalog (#E54B4D red) +- App name: "OneSignalSwiftUIExample" +- Top header bar: OneSignal logo image + "Sample App" text, left-aligned, red background spanning full width including status bar area +- Three targets: main app, Notification Service Extension, Widget Extension +``` + +### Prompt 1.2 - OneSignal Service Layer + +``` +Centralize all OneSignal SDK calls in a single OneSignalService.swift class (singleton): + +App ID: +- Stored in UserDefaults with key "OneSignalAppId" +- Default: "77e32082-ea27-42e3-a898-c72e141824ef" + +Initialization: +- initialize(launchOptions:) -> sets log level verbose, calls OneSignal.initialize(), requests push permission + +Identity: +- onesignalId: String? (reads OneSignal.User.onesignalId) +- externalId: String? (reads OneSignal.User.externalId) + +Consent: +- setConsentRequired(_ required: Bool) +- setConsentGiven(_ granted: Bool) + +User operations: +- login(externalId: String) +- logout() + +Alias operations: +- addAlias(label: String, id: String) +- addAliases(_ aliases: [String: String]) +- removeAlias(_ label: String) +- removeAliases(_ labels: [String]) + +Push subscription: +- pushSubscriptionId: String? +- isPushEnabled: Bool +- optInPush() / optOutPush() +- requestPushPermission(completion: @escaping (Bool) -> Void) with fallbackToSettings: true + +Email operations: +- addEmail(_ email: String) +- removeEmail(_ email: String) + +SMS operations: +- addSms(_ number: String) +- removeSms(_ number: String) + +Tag operations: +- addTag(key: String, value: String) +- addTags(_ tags: [String: String]) +- removeTag(_ key: String) +- removeTags(_ keys: [String]) +- getTags() -> [String: String] + +Outcome operations: +- sendOutcome(_ name: String) +- sendOutcome(_ name: String, value: NSNumber) +- sendUniqueOutcome(_ name: String) + +In-App Messages: +- isInAppMessagesPaused: Bool (get/set) +- addTrigger(key: String, value: String) +- addTriggers(_ triggers: [String: String]) +- removeTrigger(_ key: String) +- removeTriggers(_ keys: [String]) +- clearTriggers() + +Location: +- isLocationShared: Bool (get/set) +- requestLocationPermission() + +Notifications: +- clearAllNotifications() +- hasNotificationPermission: Bool + +Observers: +- addPushSubscriptionObserver(_ observer: OSPushSubscriptionObserver) +- addUserObserver(_ observer: OSUserStateObserver) +- addPermissionObserver(_ observer: OSNotificationPermissionObserver) +- addNotificationClickListener(_ listener: OSNotificationClickListener) +- addNotificationLifecycleListener(_ listener: OSNotificationLifecycleListener) +- addInAppMessageClickListener(_ listener: OSInAppMessageClickListener) +- addInAppMessageLifecycleListener(_ listener: OSInAppMessageLifecycleListener) +``` + +### Prompt 1.3 - NotificationSender (REST API Client) + +``` +Create NotificationSender.swift singleton for sending test notifications via REST API: + +Properties: +- apiURL: "https://onesignal.com/api/v1/notifications" +- imageURL: "https://media.onesignal.com/automated_push_templates/ratings_template.png" + +Methods: +- sendSimpleNotification(appId:completion:) +- sendNotificationWithImage(appId:completion:) +- sendCustomNotification(title:body:appId:completion:) + +All methods: +- Get subscription ID from OneSignal.User.pushSubscription.id +- Check optedIn status +- POST to API with "Accept: application/vnd.onesignal.v1+json" header +- Use include_subscription_ids (not include_player_ids) +- Image notification includes ios_attachments and big_picture +- Completion handler returns Result + +Error enum NotificationError: +- noSubscriptionId +- notOptedIn +- apiError(statusCode: Int) + +Note: REST API key is NOT required for sending to self via subscription ID. +``` + +### Prompt 1.4 - UserFetchService + +``` +Create UserFetchService.swift singleton: + +Method: +- fetchUser(appId: String, onesignalId: String) async -> UserData? + +Endpoint: +- GET https://api.onesignal.com/apps/{app_id}/users/by/onesignal_id/{onesignal_id} +- NO Authorization header needed (public endpoint) + +Parsing: +- identity object -> aliases (filter out "external_id" and "onesignal_id") +- identity.external_id -> externalId +- properties.tags -> tags (convert all values to String) +- subscriptions where type="Email" -> emails (token field) +- subscriptions where type="SMS" -> smsNumbers (token field) + +Returns UserData struct with aliases, tags, emails, smsNumbers, externalId. +``` + +### Prompt 1.5 - SDK Observers and App Delegate + +``` +In the AppDelegate within OneSignalSwiftUIExampleApp.swift, set up in didFinishLaunchingWithOptions: + +1. BEFORE SDK init: Restore consent state from UserDefaults: + - OneSignal.setConsentRequired(cached value) + - OneSignal.setConsentGiven(cached value) + +2. Initialize OneSignal via OneSignalService.shared.initialize() + +3. Start Live Activity listeners: + if #available(iOS 16.1, *) { LiveActivityController.start() } + +4. AFTER init: Restore remaining cached states from UserDefaults: + - OneSignal.InAppMessages.paused = cached paused status + - OneSignal.Location.isShared = cached location shared status + +5. Set up listeners: + - OSNotificationLifecycleListener (onWillDisplay -> log via LogManager) + - OSNotificationClickListener (onClick -> log via LogManager) + - OSInAppMessageLifecycleListener (onWillDisplay, onDidDisplay, onWillDismiss, onDidDismiss -> log) + - OSInAppMessageClickListener (onClick -> log) + - OSLogListener -> maps SDK log levels to LogManager levels, posts to main actor + +6. Initialize TooltipService (fetches on background thread, non-blocking) + +7. On the SwiftUI App body, add .onOpenURL handler: + - Calls OneSignal.LiveActivities.trackClickAndReturnOriginal(url) + - Logs via LogManager + +In OneSignalViewModel.swift, implement observers via private Observers class: +- OSPushSubscriptionObserver -> update pushSubscriptionId, isPushEnabled +- OSUserStateObserver -> log state change, call fetchUserDataFromApi() +- OSNotificationPermissionObserver -> update notificationPermissionGranted, conditionally update isPushEnabled +``` + +### Prompt 1.6 - LogManager + +``` +Create LogManager.swift: + +@MainActor final class LogManager: ObservableObject { + static let shared = LogManager() + @Published var entries: [LogEntry] = [] + private let maxEntries = 100 + + func log(_ tag: String, _ message: String, level: LogLevel) + func clear() + func d/i/w/e(_ tag: String, _ message: String) // Convenience +} + +LogLevel enum: debug, info, warning, error +- Each has a rawValue (D/I/W/E) and a SwiftUI Color (blue/green/orange/red) + +LogEntry struct: Identifiable with UUID, timestamp, level, message +- formattedTimestamp using "HH:mm:ss" format + +Every log call also prints to console via print(). +Max 100 entries, oldest removed when exceeded. +``` + +--- + +## Phase 2: UI Sections + +### Section Order (top to bottom) - FINAL + +1. **App Section** (App ID, Guidance Banner, Consent Toggles) +2. **User Section** (Status, External ID, Login/Logout) +3. **Push Section** (Push ID, Enabled Toggle, Prompt Push) +4. **Send Push Notification Section** (Simple, With Image, Custom) +5. **In-App Messaging Section** (Pause toggle) +6. **Send In-App Message Section** (Top Banner, Bottom Banner, Center Modal, Full Screen) +7. **Aliases Section** (Add/Add Multiple, read-only list) +8. **Emails Section** (Collapsible list >5 items) +9. **SMS Section** (Collapsible list >5 items) +10. **Tags Section** (Add/Add Multiple/Remove Selected) +11. **Outcome Events Section** (Send Outcome sheet with type selection) +12. **Triggers Section** (Add/Add Multiple/Remove Selected/Clear All - IN MEMORY ONLY) +13. **Track Event Section** (Track Event with JSON validation) +14. **Location Section** (Location Shared toggle, Prompt Location button) +15. **Live Activities Section** (Activity ID field, Enter/Exit buttons) +16. **Next Activity Button** + +### Prompt 2.1 - App Section + +``` +App Section layout: + +1. SectionHeader with title "App" + +2. CardContainer with App ID display (InfoRow, readonly) + +3. Sticky guidance banner below App ID: + - Text: "Add your own App ID, then rebuild to fully test all functionality." + - Link text: "Get your keys at onesignal.com" (clickable, opens browser) + - Light cream/yellow background (Color(red: 1.0, green: 0.98, blue: 0.90)) + - Rounded corners (12pt) + +4. Consent card with up to two toggles: + a. "Consent Required" toggle (always visible): + - Subtitle: "Require consent before SDK processes data" + - Sets OneSignal.consentRequired, persists to UserDefaults + b. "Privacy Consent" toggle (only visible when Consent Required is ON): + - Subtitle: "Consent given for data collection" + - Sets OneSignal.consentGiven, persists to UserDefaults + - Separated from above by CardDivider + - NOT a blocking overlay - user can interact with app regardless + +5. App version display: + - Reads from Bundle.main CFBundleShortVersionString +``` + +### Prompt 2.2 - User Section + +``` +User Section: +- SectionHeader with title "User" +- Status card (CardContainer) with two rows separated by CardDivider: + - Row 1: "Status" label | value ("Anonymous" in gray, or "Logged In" in green) + - Row 2: "External ID" label | value (actual ID or em dash "—") + - Green color: Color(red: 0.20, green: 0.66, blue: 0.33) + +- LOGIN USER button (ActionButton): + - Shows "LOGIN USER" when no user logged in + - Shows "SWITCH USER" when user is logged in + - Opens AddItemSheet with .externalUserId type + +- LOGOUT USER button (OutlineActionButton): + - Only visible when a user is logged in +``` + +### Prompt 2.3 - Push Section + +``` +Push Section: +- SectionHeader with title "Push" and tooltipKey "push" +- CardContainer with: + - InfoRow showing Push Subscription ID (readonly, truncated middle) + - CardDivider + - ToggleRow for "Enabled" (controls optIn/optOut) + - isEnabled parameter bound to notificationPermissionGranted + - When disabled (no permission): toggle appears dimmed at 50% opacity + +- PROMPT PUSH button (ActionButton): + - Only visible when notification permission is NOT granted + - Requests notification permission with fallbackToSettings + - Hidden once permission is granted + +Notification permission is automatically requested during SDK initialization. +``` + +### Prompt 2.4 - Send Push Notification Section + +``` +Send Push Notification Section: +- SectionHeader with title "Send Push Notification" and tooltipKey "sendPushNotification" +- Three full-width ActionButtons stacked vertically with 8pt spacing: + 1. SIMPLE - sends basic notification via NotificationSender + 2. WITH IMAGE - sends notification with big picture attachment + 3. CUSTOM - opens CustomNotificationSheet for custom title/body +``` + +### Prompt 2.5 - In-App Messaging Section + +``` +In-App Messaging Section: +- SectionHeader with title "In-App Messaging" and tooltipKey "inAppMessaging" +- CardContainer with ToggleRow: + - Title: "Pause In-App Messages" + - Subtitle: "Toggle in-app message display" + - Persists to UserDefaults on toggle +``` + +### Prompt 2.6 - Send In-App Message Section + +``` +Send In-App Message Section: +- SectionHeader with title "Send In-App Message" and tooltipKey "sendInAppMessage" +- Four full-width ActionButtonWithIcon buttons with 8pt spacing: + 1. TOP BANNER - icon "arrow.up.to.line", trigger: "iam_type" = "top_banner" + 2. BOTTOM BANNER - icon "arrow.down.to.line", trigger: "iam_type" = "bottom_banner" + 3. CENTER MODAL - icon "square", trigger: "iam_type" = "center_modal" + 4. FULL SCREEN - icon "arrow.up.left.and.arrow.down.right", trigger: "iam_type" = "full_screen" +- Button styling: + - RED background (AccentColor) + - WHITE text and icon + - SF Symbol icon on LEFT side + - Full width, left-aligned content + - UPPERCASE text +- On tap: adds trigger key/value and shows toast +``` + +### Prompt 2.7 - Aliases Section + +``` +Aliases Section: +- SectionHeader with title "Aliases" and tooltipKey "aliases" +- CardContainer list showing key-value pairs (read-only, NO delete icons) +- Each item shows Label | ID via KeyValueRow (no onDelete) +- Filter out "external_id" and "onesignal_id" from display +- "No aliases added" EmptyListRow when empty +- ADD button -> opens AddItemSheet with .alias type +- ADD MULTIPLE button -> opens AddMultiItemSheet with .aliases type +- No remove/delete functionality (aliases are add-only from the UI) +``` + +### Prompt 2.8 - Emails Section + +``` +Emails Section: +- SectionHeader with title "Emails" and tooltipKey "emails" +- CardContainer showing email addresses via SingleValueRow with delete (xmark) icon +- "No emails added" EmptyListRow when empty +- ADD EMAIL button -> opens AddItemSheet with .email type +- Collapse behavior when >5 items: + - Show first 5 items + - Show "X more available" text (tappable, AccentColor) + - Expand to show all when tapped +``` + +### Prompt 2.9 - SMS Section + +``` +SMS Section: +- SectionHeader with title "SMS" and tooltipKey "sms" +- Same pattern as Emails Section but for phone numbers +- ADD SMS button -> opens AddItemSheet with .sms type +- Same collapse behavior when >5 items +``` + +### Prompt 2.10 - Tags Section + +``` +Tags Section: +- SectionHeader with title "Tags" and tooltipKey "tags" +- CardContainer list of key-value pairs via KeyValueRow with delete icon +- "No tags added" EmptyListRow when empty +- ADD button -> opens AddItemSheet with .tag type +- ADD MULTIPLE button -> opens AddMultiItemSheet with .tags type +- REMOVE SELECTED button (OutlineActionButton): + - Only visible when at least one tag exists + - Opens RemoveMultiSheet with checkboxes +``` + +### Prompt 2.11 - Outcome Events Section + +``` +Outcome Events Section: +- SectionHeader with title "Outcome Events" and tooltipKey "outcomes" +- SEND OUTCOME button -> opens OutcomeSheet with 3 radio options: + 1. Normal Outcome -> shows name input field + 2. Unique Outcome -> shows name input field + 3. Outcome with Value -> shows name and value (decimal) input fields +- Radio buttons using SF Symbols: largecircle.fill.circle (selected) / circle (unselected) +- Send button disabled until name is filled AND (if with value) value is valid number +``` + +### Prompt 2.12 - Triggers Section (IN MEMORY ONLY) + +``` +Triggers Section: +- SectionHeader with title "Triggers" and tooltipKey "triggers" +- CardContainer list of key-value pairs with delete icon +- "No triggers added" EmptyListRow when empty +- ADD button -> opens AddItemSheet with .trigger type +- ADD MULTIPLE button -> opens AddMultiItemSheet with .triggers type +- Two action buttons (only visible when triggers exist): + - REMOVE SELECTED (OutlineActionButton) -> RemoveMultiSheet + - CLEAR ALL (OutlineActionButton) -> removes all triggers at once + +IMPORTANT: Triggers are stored IN MEMORY ONLY during the app session. +- triggers is a @Published [KeyValueItem] in ViewModel +- Triggers are NOT persisted to UserDefaults +- Triggers are cleared when the app is killed/restarted +- This is intentional - triggers are transient test data for IAM testing +``` + +### Prompt 2.13 - Track Event Section + +``` +Track Event Section: +- SectionHeader with title "Track Event" and tooltipKey "trackEvent" +- TRACK EVENT button -> opens TrackEventSheet with: + - "Event Name" label + empty text field (required, shows "Required" error if empty on submit) + - "Properties (optional, JSON)" label + text field with placeholder {"ABC":123} + - If non-empty and not valid JSON, shows "Invalid JSON" error + - If valid JSON, parsed via JSONSerialization to [String: Any] + - If empty, passes nil + - IMPORTANT: Replace iOS smart quotes (U+201C, U+201D) with standard quotes before JSON parsing + - Calls OneSignal.User.trackEvent(name:properties:) +``` + +### Prompt 2.14 - Location Section + +``` +Location Section: +- SectionHeader with title "Location" and tooltipKey "location" +- CardContainer with ToggleRow: + - Title: "Location Shared" + - Subtitle: "Share device location with OneSignal" + - Persists to UserDefaults on toggle +- PROMPT LOCATION button (ActionButton) +``` + +### Prompt 2.15 - Live Activities Section + +``` +Live Activities Section: +- SectionHeader with title "Live Activities" and tooltipKey "liveActivities" +- CardContainer with a text field for Activity ID: + - Label "Activity ID" on left, TextField on right (trailing aligned) + - autocorrectionDisabled, textInputAutocapitalization(.never) +- ENTER LIVE ACTIVITY button (ActionButton): + - Validates ID is non-empty + - Calls LiveActivityController.createOneSignalAwareActivity(activityId:) + - Guarded by @available(iOS 16.1, *) +- EXIT LIVE ACTIVITY button (OutlineActionButton): + - Validates ID is non-empty + - Calls OneSignal.LiveActivities.exit(activityId) +``` + +### Prompt 2.16 - Secondary View + +``` +Next Activity section: +- NavigationLink styled as full-width ActionButton +- Navigates to SecondaryView + +SecondaryView: +- Centered content: bell.circle.fill icon (60pt), "Secondary Activity" title, description text +- Navigation title "Secondary Activity" with inline display mode +- Simple screen for testing navigation and IAM display on different screen +``` + +--- + +## Phase 3: View User API Integration + +### Prompt 3.1 - Data Loading Flow + +``` +Loading indicator overlay: +- Full-screen semi-transparent overlay (Color.black.opacity(0.3)) with centered ProgressView +- isLoading @Published property in ViewModel +- Show/hide based on isLoading state +- IMPORTANT: Add 100ms delay after populating data before dismissing loading indicator + - Use Task.sleep(nanoseconds: 100_000_000) + +On cold start (init): +- Check if OneSignal.User.onesignalId is not null +- If exists: call fetchUserDataFromApi() -> populate UI -> delay 100ms -> set isLoading = false +- If null: just show empty state + +On login: +- Set isLoading = true immediately +- Call OneSignal.login(externalId) +- Clear old data (aliases, emails, sms, tags) +- Wait for onUserStateDidChange callback +- Callback calls fetchUserDataFromApi() + +On logout: +- Set isLoading = true +- Call OneSignal.logout() +- Clear local lists +- Set isLoading = false + +On onUserStateDidChange: +- Call fetchUserDataFromApi() to sync with server state + +Note: REST API key is NOT required for fetchUser endpoint. +``` + +### Prompt 3.2 - UserData Model + +``` +struct UserData { + let aliases: [String: String] // From identity (filter out external_id, onesignal_id) + let tags: [String: String] // From properties.tags + let emails: [String] // From subscriptions where type="Email" -> token + let smsNumbers: [String] // From subscriptions where type="SMS" -> token + let externalId: String? // From identity.external_id +} +``` + +--- + +## Phase 4: Info Tooltips + +### Prompt 4.1 - Tooltip Content (Remote) + +``` +Tooltip content is fetched at runtime from the sdk-shared repo. Do NOT bundle a local copy. + +URL: +https://raw.githubusercontent.com/OneSignal/sdk-shared/main/demo/tooltip_content.json + +This file is maintained in the sdk-shared repo and shared across all platform demo apps. +``` + +### Prompt 4.2 - TooltipService + +``` +Create TooltipService.swift: + +final class TooltipService: ObservableObject { + static let shared = TooltipService() + @Published private(set) var tooltips: [String: TooltipData] = [:] + private var initialized = false + + func initialize() { + guard !initialized else { return } + initialized = true + // Fetch on background thread (DispatchQueue.global(qos: .utility)) + // Parse JSON into tooltips map + // Update on main thread + // On failure: leave tooltips empty - tooltips are non-critical + } + + func getTooltip(key: String) -> TooltipData? +} + +struct TooltipData { + let title: String + let description: String + let options: [TooltipOption]? +} + +struct TooltipOption { + let name: String + let description: String +} +``` + +### Prompt 4.3 - Tooltip UI Integration + +``` +SectionHeader has an optional tooltipKey parameter. +When tooltipKey is set, an info.circle.fill icon button appears. +On tap, shows an Alert with: +- Title from tooltip.title +- Message from tooltip.description + options list +- Single "OK" dismiss button +If tooltip not available: shows "Tooltip content not available." +``` + +--- + +## Phase 5: Data Persistence & Initialization + +### What IS Persisted (UserDefaults) + +``` +UserDefaults stores: +- "OneSignalAppId" - App ID +- "CachedConsentRequired" - Consent required status +- "CachedPrivacyConsent" - Privacy consent status +- "CachedInAppMessagesPaused" - IAM paused status +- "CachedLocationShared" - Location shared status + +Note: External user ID is NOT cached in UserDefaults. +It is read from OneSignal.User.externalId on each app launch. +``` + +### Initialization Flow + +``` +On app startup, state is restored in two layers: + +1. AppDelegate.didFinishLaunchingWithOptions restores SDK state from UserDefaults BEFORE init: + - OneSignal.setConsentRequired(cached) + - OneSignal.setConsentGiven(cached) + - OneSignalService.shared.initialize() + Then AFTER init: + - Start LiveActivityController + - OneSignal.InAppMessages.paused = cached + - OneSignal.Location.isShared = cached + +2. OneSignalViewModel.init() reads UI state from the SDK (not UserDefaults): + - consentRequired and consentGiven read from UserDefaults at @Published declaration + - All other state read from OneSignalService (which reads from SDK) + - refreshState() syncs push ID, push enabled, IAM paused, location, permission, external ID, tags + +This two-layer approach ensures: +- The SDK is configured before anything else runs +- The ViewModel reads SDK's actual state as the source of truth +- The UI always reflects what the SDK reports +``` + +### What is NOT Persisted (In-Memory Only) + +``` +ViewModel holds in memory: +- triggers: [KeyValueItem] - session-only, cleared on restart +- aliases: populated from REST API each session +- emails, smsNumbers: populated from REST API each session +- tags: can be read from SDK via getTags(), also fetched from API +``` + +--- + +## Phase 6: Reusable Components + +### Prompt 6.1 - Button Styles + +``` +ActionButtonStyle: ButtonStyle +- 16pt semibold white text, uppercase +- Full width, 14pt vertical padding +- AccentColor background with 0.8 opacity on press +- 8pt corner radius + +ActionButton: View (title: String, action: () -> Void) +- Wraps Button with ActionButtonStyle + +OutlineActionButtonStyle: ButtonStyle +- 16pt semibold AccentColor text, uppercase +- Full width, 14pt vertical padding +- systemBackground background +- 1.5pt AccentColor border, 8pt corner radius + +OutlineActionButton: View (title: String, action: () -> Void) +- Wraps Button with OutlineActionButtonStyle + +ActionButtonWithIcon: View (title: String, iconName: String, action: () -> Void) +- HStack with SF Symbol icon (18pt) + text (16pt semibold uppercase) + Spacer +- White text on AccentColor background, 8pt corner radius +- Left-aligned content +``` + +### Prompt 6.2 - Card and Layout Components + +``` +CardContainer: View +- VStack(spacing: 0) wrapping content +- systemBackground color, 12pt corner radius + +SectionHeader: View (title: String, tooltipKey: String?) +- HStack with title (14pt medium, secondary color) + Spacer + optional info icon +- Padding: horizontal 4, top 16, bottom 8 + +CardDivider: View +- Rectangle, separator color, 0.5pt height + +InfoRow: View (label: String, value: String, isMonospaced: Bool = false) +- HStack with label (15pt medium secondary) + Spacer + value (15pt primary, lineLimit 1, truncateMiddle) +- 16pt horizontal, 12pt vertical padding + +ToggleRow: View (title: String, subtitle: String?, isOn: Binding, isEnabled: Bool = true) +- HStack with VStack(title, subtitle) + Spacer + Toggle +- When !isEnabled: toggle disabled, entire row at 50% opacity +- 16pt horizontal, 12pt vertical padding + +KeyValueRow: View (item: KeyValueItem, onDelete: (() -> Void)?) +- HStack with VStack(key as subheadline secondary, value as body) + Spacer + optional xmark delete button + +SingleValueRow: View (value: String, onDelete: (() -> Void)?) +- HStack with value text + Spacer + optional xmark delete button + +EmptyListRow: View (message: String) +- Centered text (16pt medium), 16pt vertical padding +``` + +### Prompt 6.3 - Sheets + +``` +AddItemSheet: View (itemType: AddItemType, onAdd: (String, String) -> Void, onCancel: () -> Void) +- Presents title, one or two text fields based on itemType.requiresKeyValue +- UnderlineTextFieldStyle (custom: font 17, 8pt vertical padding, 1pt separator line below) +- CANCEL / ADD (or LOGIN) buttons at bottom right +- ADD disabled until fields are valid (non-empty after trimming) +- presentationDetents([.medium]), presentationDragIndicator(.visible) +- autocorrectionDisabled, textInputAutocapitalization(.never) + +AddMultiItemSheet: View (type: MultiAddItemType, onAdd: ([(String, String)]) -> Void, onCancel: () -> Void) +- Dynamic rows of key-value pairs +- "+ ADD ROW" button to append new empty row +- Remove button (xmark) per row, hidden when only one row +- ADD disabled until ALL key AND value fields in every row are non-empty +- Batch submit + +RemoveMultiSheet: View (type: RemoveMultiItemType, items: [KeyValueItem], onRemove: ([String]) -> Void, onCancel: () -> Void) +- Checkbox list (checkmark.square.fill / square SF Symbols) +- Each row shows "key: value" +- REMOVE button disabled when nothing selected + +CustomNotificationSheet: View (onSend: (String, String) -> Void, onCancel: () -> Void) +- Title and Body text fields +- SEND disabled until both non-empty + +TrackEventSheet: View (onTrack: (String, [String: Any]?) -> Void, onCancel: () -> Void) +- Event Name field (required, shows "Required" error) +- Properties field (optional JSON, shows "Invalid JSON" error) +- IMPORTANT: Replace smart quotes (\u{201C}, \u{201D}) with standard quotes before parsing +- Parse via JSONSerialization.jsonObject as [String: Any] + +OutcomeSheet: View +- Radio selection: Normal / Unique / With Value +- Name field always shown +- Value field only when "Outcome with Value" selected +- Send button disabled until valid +``` + +### Prompt 6.4 - LogView + +``` +LogView: View (@ObservedObject logManager: LogManager) +- Collapsible header bar (default collapsed): + - "LOGS" text + "(N)" count + trash button + chevron + - Tap to expand/collapse with animation +- When expanded: + - 100pt height ScrollView + - LazyVStack of log entries + - Each entry: timestamp (11pt mono secondary) + level indicator (11pt bold mono, color-coded) + message (11pt mono, 2 line limit) + - Auto-scroll to bottom on new entries via ScrollViewReader + onChange + - "No logs yet" when empty + +ToastView: View (message: String) +- Subheadline white text +- Black 80% opacity background, 8pt corner radius, 4pt shadow +- ViewModifier that overlays at bottom with slide+opacity transition +- Auto-dismiss after 2 seconds (handled in ViewModel's showToast method) + +GuidanceBanner: View +- VStack with instruction text + Link to onesignal.com +- Light cream background, 12pt corner radius +``` + +--- + +## Phase 7: Extensions + +### Prompt 7.1 - Notification Service Extension + +``` +Target: OneSignalNotificationServiceExtension +- Bundle ID: com.onesignal.example.OneSignalNotificationServiceExtensionA +- Deployment target: iOS 16.0 +- Frameworks: OneSignalExtension, OneSignalCore, OneSignalOutcomes + +NotificationService.swift (UNNotificationServiceExtension subclass): +- didReceive: calls OneSignalExtension.didReceiveNotificationExtensionRequest() +- serviceExtensionTimeWillExpire: calls OneSignalExtension.serviceExtensionTimeWillExpireRequest() + +Info.plist: +- NSExtensionPointIdentifier: com.apple.usernotifications.service +- NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).NotificationService + +Entitlements: +- com.apple.security.application-groups: group.com.onesignal.example.onesignal +``` + +### Prompt 7.2 - Widget Extension for Live Activities + +``` +Target: OneSignalWidgetExtension +- Bundle ID: com.onesignal.example.OneSignalWidgetExtension +- Deployment target: iOS 16.1 +- Frameworks: WidgetKit, SwiftUI, OneSignalLiveActivities + +REQUIRED: Add NSSupportsLiveActivities = true to main app's Info.plist + +Shared file (compiled into BOTH main app and widget extension targets): +ExampleAppWidgetAttributes.swift: +- Wrapped in #if targetEnvironment(macCatalyst) #else ... #endif +- ExampleAppFirstWidgetAttributes: OneSignalLiveActivityAttributes (simple message) +- ExampleAppSecondWidgetAttributes: OneSignalLiveActivityAttributes (message, status, progress, bugs) +- ExampleAppThirdWidgetAttributes: ActivityAttributes (NOT OneSignal-aware, manual token management) + +Widget Extension files: +1. OneSignalWidgetExtensionBundle.swift (@main WidgetBundle): + - Registers: OneSignalWidgetExtensionWidget, ExampleAppFirstWidget, ExampleAppSecondWidget, + ExampleAppThirdWidget, DefaultOneSignalLiveActivityWidget + +2. OneSignalWidgetExtensionLiveActivity.swift: + - 4 widgets using ActivityConfiguration(for:) with Lock Screen and Dynamic Island UI + - IMPORTANT: Apply .foregroundColor(.black) to each Lock Screen VStack (white background = invisible text otherwise) + - Use .onesignalWidgetURL() instead of .widgetURL() for click tracking + - Use .activityBackgroundTint(.white) and .activitySystemActionForegroundColor(.black) + +3. OneSignalWidgetExtension.swift: + - Basic StaticConfiguration widget showing time + - Uses containerBackground(.fill.tertiary, for: .widget) on iOS 17+ (required by Apple) + +4. Info.plist: NSExtensionPointIdentifier = com.apple.widgetkit-extension + +Entitlements: +- com.apple.security.app-sandbox: true +- com.apple.security.network.client: true +``` + +### Prompt 7.3 - LiveActivityController (Main App) + +``` +Create LiveActivityController.swift in Services: +- Wrapped in #if targetEnvironment(macCatalyst) #else ... #endif + +static func start(): +- OneSignal.LiveActivities.setup(ExampleAppFirstWidgetAttributes.self) +- OneSignal.LiveActivities.setup(ExampleAppSecondWidgetAttributes.self) +- OneSignal.LiveActivities.setupDefault() +- For iOS 17.2+: manually monitor pushToStartTokenUpdates and activityUpdates + for ExampleAppThirdWidgetAttributes (non-OneSignal-aware type) + +static func createOneSignalAwareActivity(activityId:): +- Creates ExampleAppFirstWidgetAttributes with OneSignalLiveActivityAttributeData +- Requests Activity with .token push type + +static func createDefaultActivity(activityId:): +- Uses OneSignal.LiveActivities.startDefault() with attribute/content dictionaries + +static func createActivity(activityId:) async: +- Creates ExampleAppThirdWidgetAttributes (non-OneSignal-aware) +- Manually monitors pushTokenUpdates and calls OneSignal.LiveActivities.enter() +``` + +--- + +## Phase 8: Important Implementation Details + +### Smart Quotes Handling + +``` +iOS automatically replaces straight double quotes with smart/curly quotes in text fields. +This breaks JSON parsing. In TrackEventSheet, ALWAYS replace smart quotes before parsing: + +let trimmedProps = propertiesText + .trimmingCharacters(in: .whitespaces) + .replacingOccurrences(of: "\u{201C}", with: "\"") // Left double quotation mark + .replacingOccurrences(of: "\u{201D}", with: "\"") // Right double quotation mark +``` + +### Consent Initialization Order + +``` +Consent state MUST be set BEFORE OneSignal.initialize(): + +1. Read from UserDefaults +2. OneSignal.setConsentRequired(cachedValue) +3. OneSignal.setConsentGiven(cachedValue) +4. OneSignal.initialize(appId, withLaunchOptions: launchOptions) + +If consent is set after init, the SDK may process data before consent is configured. +``` + +### Push Permission and Enabled Toggle + +``` +The Push "Enabled" toggle must be disabled when notification permission is not granted: +- ToggleRow has isEnabled parameter +- Pass isEnabled: viewModel.notificationPermissionGranted +- When isEnabled is false: Toggle is .disabled(), row opacity is 0.5 +- This matches Android behavior where the toggle is grayed out without permission +``` + +### Live Activity Click Tracking + +``` +When a user taps a Live Activity on the Lock Screen, iOS opens the app via a URL. +The URL is set in the widget via .onesignalWidgetURL(). + +In the SwiftUI App body, intercept with .onOpenURL: +- Call OneSignal.LiveActivities.trackClickAndReturnOriginal(url) +- This sends the click event to OneSignal and returns the original URL +- Log the event via LogManager +``` + +### Alias Management + +``` +Aliases use a hybrid approach: +1. On app start/login: Fetched from REST API via fetchUserDataFromApi() +2. When user adds locally: SDK call + immediate local list update (don't wait for API) +3. On next launch: fresh data from API includes synced alias +``` + +### Toast Messages + +``` +All user actions display toast messages: +- Login: "Logged in as {userId}" +- Logout: "Logged out" +- Add alias/tag/trigger: "Alias added", "Tag added", etc. +- Add multiple: "{count} alias(es) added" +- Notifications: "Simple notification sent!" or "Failed: {error}" +- In-App Messages: "Sent In-App Message: {type}" +- Outcomes: "Outcome '{name}' sent" +- Events: "Event '{name}' tracked" +- Location: "Location sharing enabled/disabled" +- Push: "Push enabled/disabled" +- Live Activities: "Live Activity '{id}' entered/exited" + +Implementation: +- ViewModel has @Published toastMessage: String? +- showToast() sets message and auto-nils after 2 seconds via Task.sleep +- ToastModifier overlays at bottom of screen with animation +``` + +--- + +## Configuration + +### Info.plist Required Keys + +```xml + +NSSupportsLiveActivities + +NSLocationAlwaysAndWhenInUseUsageDescription +This app uses your location to provide location-based notifications and services. +NSLocationWhenInUseUsageDescription +This app uses your location to provide location-based notifications. +UIBackgroundModes + + remote-notification + +``` + +### Entitlements + +``` +Main app (OneSignalSwiftUIExample.entitlements): +- aps-environment: development +- com.apple.security.application-groups: group.com.onesignal.example.onesignal + +NSE (OneSignalNotificationServiceExtension.entitlements): +- com.apple.security.application-groups: group.com.onesignal.example.onesignal + +Widget Extension (OneSignalWidgetExtension.entitlements): +- com.apple.security.app-sandbox: true +- com.apple.security.network.client: true +``` + +### Bundle Identifiers + +``` +Main app: com.onesignal.example +NSE: com.onesignal.example.OneSignalNotificationServiceExtensionA +Widget: com.onesignal.example.OneSignalWidgetExtension +``` + +### OneSignal Frameworks + +``` +Main app links: +- OneSignalFramework, OneSignalCore, OneSignalExtension, OneSignalOutcomes +- OneSignalOSCore, OneSignalUser, OneSignalNotifications +- OneSignalInAppMessages, OneSignalLocation, OneSignalLiveActivities +- CoreLocation, SystemConfiguration, UserNotifications, WebKit + +NSE links: +- OneSignalExtension, OneSignalCore, OneSignalOutcomes + +Widget Extension links: +- WidgetKit, SwiftUI, OneSignalLiveActivities +``` + +--- + +## Key Files Structure + +``` +OneSignalSwiftUIExample/ +├── OneSignalSwiftUIExample.xcodeproj/ +├── OneSignalSwiftUIExample.entitlements +├── OneSignalWidgetExtension.entitlements +├── OneSignalSwiftUIExample/ +│ ├── App/ +│ │ └── OneSignalSwiftUIExampleApp.swift # @main App, AppDelegate, observers +│ ├── Models/ +│ │ └── AppModels.swift # KeyValueItem, enums, UserData, TooltipData +│ ├── Services/ +│ │ ├── OneSignalService.swift # SDK wrapper singleton +│ │ ├── NotificationSender.swift # REST API notification sender +│ │ ├── UserFetchService.swift # REST API user data fetcher +│ │ ├── TooltipService.swift # Remote tooltip loader +│ │ ├── LogManager.swift # Thread-safe pass-through logger +│ │ └── LiveActivityController.swift # Live Activity setup and creation +│ ├── ViewModels/ +│ │ └── OneSignalViewModel.swift # Main @MainActor ObservableObject +│ ├── Views/ +│ │ ├── ContentView.swift # Root view composing all sections +│ │ ├── Components/ +│ │ │ ├── KeyValueRow.swift # All reusable UI components +│ │ │ ├── NotificationGrid.swift # Push and IAM button groups +│ │ │ ├── AddItemSheet.swift # Single-item add sheet +│ │ │ ├── AddMultiItemSheet.swift # Multi-pair add sheet +│ │ │ ├── RemoveMultiSheet.swift # Checkbox remove sheet +│ │ │ ├── CustomNotificationSheet.swift # Custom notification sheet +│ │ │ ├── TrackEventSheet.swift # Track event with JSON sheet +│ │ │ ├── LogView.swift # Collapsible log viewer +│ │ │ ├── ToastView.swift # Toast overlay +│ │ │ └── GuidanceBanner.swift # Setup instruction banner +│ │ └── Sections/ +│ │ ├── AppInfoSection.swift # App ID, banner, consent +│ │ ├── UserSection.swift # User + Aliases sections +│ │ ├── SubscriptionSection.swift # Push + Emails + SMS sections +│ │ ├── NotificationSection.swift # Send Push + Send IAM sections +│ │ ├── MessagingSection.swift # IAM toggle + Triggers + Outcomes +│ │ ├── TagsSection.swift # Tags section +│ │ ├── TrackEventSection.swift # Track Event section +│ │ ├── LocationSection.swift # Location section +│ │ ├── LiveActivitySection.swift # Live Activities section +│ │ └── NextScreenSection.swift # Navigation + SecondaryView +│ ├── ExampleAppWidgetAttributes.swift # Shared ActivityAttributes (both targets) +│ ├── Assets.xcassets/ # App icon, AccentColor, OneSignalLogo +│ └── Info.plist +├── OneSignalNotificationServiceExtension/ +│ ├── NotificationService.swift +│ ├── Info.plist +│ └── OneSignalNotificationServiceExtension.entitlements +└── OneSignalWidgetExtension/ + ├── OneSignalWidgetExtensionBundle.swift + ├── OneSignalWidgetExtensionLiveActivity.swift + ├── OneSignalWidgetExtension.swift + └── Info.plist +``` + +Note: + +- All UI is SwiftUI (no UIKit storyboards/xibs) +- Tooltip content is fetched from remote URL (not bundled locally) +- LogView at top of screen displays SDK and app logs for debugging +- Multiple sections may share a single .swift file (e.g., MessagingSection.swift contains OutcomeEvents, IAM, and Triggers) + +--- + +## Summary + +This app demonstrates all OneSignal iOS SDK features: + +- User management (login/logout, aliases with batch add) +- Push notifications (subscription, sending with images, permission handling) +- Email and SMS subscriptions +- Tags for segmentation (batch add/remove support) +- Triggers for in-app message targeting (in-memory only, batch operations) +- Outcomes for conversion tracking +- Event tracking with JSON properties validation +- In-app messages (display testing with type-specific icons) +- Location sharing +- Privacy consent management +- Live Activities (enter/exit, push-to-start, widget extension, click tracking) +- Notification Service Extension (rich notifications) + +The app is designed to be: + +1. **Testable** - Empty sheets for test automation +2. **Comprehensive** - All SDK features demonstrated +3. **Clean** - MVVM architecture with SwiftUI +4. **Cross-platform ready** - Tooltip content shared via JSON across all platforms +5. **Session-based triggers** - Triggers stored in memory only, cleared on restart +6. **Responsive UI** - Loading indicator with delay to ensure UI populates before dismissing +7. **Performant** - Tooltip JSON loaded on background thread +8. **Modern UI** - SwiftUI with reusable components matching Android Material3 design +9. **Batch Operations** - Add multiple items at once, select and remove multiple items +10. **Extension-ready** - Notification Service Extension and Widget Extension for Live Activities From 3561365ba219aabc39440ce5c05a618d4958d1e7 Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 19 Feb 2026 02:10:13 -0800 Subject: [PATCH 8/8] chore: run swiftlint --- .../App/OneSignalSwiftUIExampleApp.swift | 34 ++--- .../Models/AppModels.swift | 18 +-- .../Services/OneSignalService.swift | 112 +++++++-------- .../ViewModels/OneSignalViewModel.swift | 128 +++++++++--------- .../Views/Components/AddItemSheet.swift | 12 +- .../Views/Components/KeyValueRow.swift | 28 ++-- .../Views/Components/NotificationGrid.swift | 14 +- .../Views/Components/ToastView.swift | 6 +- .../Views/ContentView.swift | 2 +- .../Views/Sections/AppInfoSection.swift | 4 +- .../Views/Sections/LocationSection.swift | 4 +- .../Views/Sections/MessagingSection.swift | 20 +-- .../Views/Sections/NotificationSection.swift | 4 +- .../Views/Sections/SubscriptionSection.swift | 12 +- .../Views/Sections/TagsSection.swift | 4 +- .../Views/Sections/UserSection.swift | 10 +- .../ViewModels/OneSignalViewModel.swift | 34 +++-- .../Views/Components/AddItemSheet.swift | 25 ++-- .../Views/Components/ToastView.swift | 6 +- 19 files changed, 243 insertions(+), 234 deletions(-) diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift index 64b465526..891c6a1ca 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift @@ -34,7 +34,7 @@ import OneSignalLocation struct OneSignalSwiftUIExampleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @StateObject private var viewModel = OneSignalViewModel() - + var body: some Scene { WindowGroup { ContentView() @@ -46,38 +46,38 @@ struct OneSignalSwiftUIExampleApp: App { // MARK: - App Delegate class AppDelegate: NSObject, UIApplicationDelegate { - + func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { // Initialize OneSignal OneSignalService.shared.initialize(launchOptions: launchOptions) - + // Set up notification lifecycle listeners setupNotificationListeners() - + // Set up in-app message listeners setupInAppMessageListeners() - + return true } - + private func setupNotificationListeners() { // Foreground notification display OneSignal.Notifications.addForegroundLifecycleListener(NotificationLifecycleHandler.shared) - + // Notification click handling OneSignal.Notifications.addClickListener(NotificationClickHandler.shared) } - + private func setupInAppMessageListeners() { // In-app message lifecycle OneSignal.InAppMessages.addLifecycleListener(InAppMessageLifecycleHandler.shared) - + // In-app message click handling OneSignal.InAppMessages.addClickListener(InAppMessageClickHandler.shared) - + // Start with IAM paused OneSignal.InAppMessages.paused = true } @@ -87,7 +87,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { class NotificationLifecycleHandler: NSObject, OSNotificationLifecycleListener { static let shared = NotificationLifecycleHandler() - + func onWillDisplay(event: OSNotificationWillDisplayEvent) { print("[OneSignal] Notification will display: \(event.notification.title ?? "No title")") // Optionally modify display behavior @@ -98,7 +98,7 @@ class NotificationLifecycleHandler: NSObject, OSNotificationLifecycleListener { class NotificationClickHandler: NSObject, OSNotificationClickListener { static let shared = NotificationClickHandler() - + func onClick(event: OSNotificationClickEvent) { print("[OneSignal] Notification clicked: \(event.notification.title ?? "No title")") // Handle notification click - navigate to specific screen, etc. @@ -109,19 +109,19 @@ class NotificationClickHandler: NSObject, OSNotificationClickListener { class InAppMessageLifecycleHandler: NSObject, OSInAppMessageLifecycleListener { static let shared = InAppMessageLifecycleHandler() - + func onWillDisplay(event: OSInAppMessageWillDisplayEvent) { print("[OneSignal] IAM will display: \(event.message.messageId)") } - + func onDidDisplay(event: OSInAppMessageDidDisplayEvent) { print("[OneSignal] IAM did display: \(event.message.messageId)") } - + func onWillDismiss(event: OSInAppMessageWillDismissEvent) { print("[OneSignal] IAM will dismiss: \(event.message.messageId)") } - + func onDidDismiss(event: OSInAppMessageDidDismissEvent) { print("[OneSignal] IAM did dismiss: \(event.message.messageId)") } @@ -129,7 +129,7 @@ class InAppMessageLifecycleHandler: NSObject, OSInAppMessageLifecycleListener { class InAppMessageClickHandler: NSObject, OSInAppMessageClickListener { static let shared = InAppMessageClickHandler() - + func onClick(event: OSInAppMessageClickEvent) { print("[OneSignal] IAM clicked: \(event.result.actionId ?? "No action ID")") // Handle IAM click - navigate, track event, etc. diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift index 6cef48660..85a9bfc37 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Models/AppModels.swift @@ -48,9 +48,9 @@ enum NotificationType: String, CaseIterable, Identifiable { case newPost = "New Post" case reEngagement = "Re-Engagement" case rating = "Rating" - + var id: String { rawValue } - + var iconName: String { switch self { case .general: return "bell.fill" @@ -73,9 +73,9 @@ enum InAppMessageType: String, CaseIterable, Identifiable { case bottomBanner = "Bottom Banner" case centerModal = "Center Modal" case fullScreen = "Full Screen" - + var id: String { rawValue } - + var iconName: String { switch self { case .topBanner: return "rectangle.topthird.inset.filled" @@ -96,7 +96,7 @@ enum AddItemType { case tag case trigger case externalUserId - + var title: String { switch self { case .alias: return "Add Alias" @@ -107,14 +107,14 @@ enum AddItemType { case .externalUserId: return "Login User" } } - + var requiresKeyValue: Bool { switch self { case .alias, .tag, .trigger: return true case .email, .sms, .externalUserId: return false } } - + var keyPlaceholder: String { switch self { case .alias: return "Alias Label" @@ -123,7 +123,7 @@ enum AddItemType { default: return "Key" } } - + var valuePlaceholder: String { switch self { case .alias: return "Alias ID" @@ -134,7 +134,7 @@ enum AddItemType { case .externalUserId: return "External User ID" } } - + var keyboardType: UIKeyboardType { switch self { case .email: return .emailAddress diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift index 78e49e4dd..5684a2a34 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Services/OneSignalService.swift @@ -32,18 +32,18 @@ import OneSignalLocation /// Service layer that wraps OneSignal SDK calls final class OneSignalService { - + // MARK: - Singleton - + static let shared = OneSignalService() - + private init() {} - + // MARK: - App ID - + private let appIdKey = "OneSignalAppId" private let defaultAppId = "77e32082-ea27-42e3-a898-c72e141824ef" - + var appId: String { get { UserDefaults.standard.string(forKey: appIdKey) ?? defaultAppId @@ -52,184 +52,184 @@ final class OneSignalService { UserDefaults.standard.set(newValue, forKey: appIdKey) } } - + // MARK: - Initialization - + func initialize(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { OneSignal.Debug.setLogLevel(.LL_VERBOSE) OneSignal.initialize(appId, withLaunchOptions: launchOptions) } - + // MARK: - Consent - + var consentRequired: Bool { get { OneSignal.privacyConsentRequired } set { OneSignal.setConsentRequired(newValue) } } - + var consentGiven: Bool { get { OneSignal.privacyConsentGiven } set { OneSignal.setConsentGiven(newValue) } } - + func revokeConsent() { OneSignal.setConsentGiven(false) } - + // MARK: - User Management - + func login(externalId: String) { OneSignal.login(externalId) } - + func logout() { OneSignal.logout() } - + // MARK: - Aliases - + func addAlias(label: String, id: String) { OneSignal.User.addAlias(label, id: id) } - + func removeAlias(_ label: String) { OneSignal.User.removeAlias(label) } - + // MARK: - Push Subscription - + var pushSubscriptionId: String? { OneSignal.User.pushSubscription.id } - + var isPushEnabled: Bool { OneSignal.User.pushSubscription.optedIn } - + func optInPush() { OneSignal.User.pushSubscription.optIn() } - + func optOutPush() { OneSignal.User.pushSubscription.optOut() } - + func requestPushPermission(completion: @escaping (Bool) -> Void) { OneSignal.Notifications.requestPermission({ accepted in completion(accepted) }, fallbackToSettings: true) } - + // MARK: - Email - + func addEmail(_ email: String) { OneSignal.User.addEmail(email) } - + func removeEmail(_ email: String) { OneSignal.User.removeEmail(email) } - + // MARK: - SMS - + func addSms(_ number: String) { OneSignal.User.addSms(number) } - + func removeSms(_ number: String) { OneSignal.User.removeSms(number) } - + // MARK: - Tags - + func addTag(key: String, value: String) { OneSignal.User.addTag(key: key, value: value) } - + func removeTag(_ key: String) { OneSignal.User.removeTag(key) } - + func getTags() -> [String: String] { OneSignal.User.getTags() } - + // MARK: - Outcomes - + func sendOutcome(_ name: String) { OneSignal.Session.addOutcome(name) } - + func sendOutcome(_ name: String, value: NSNumber) { OneSignal.Session.addOutcome(name, value: value) } - + func sendUniqueOutcome(_ name: String) { OneSignal.Session.addUniqueOutcome(name) } - + // MARK: - In-App Messages - + var isInAppMessagesPaused: Bool { get { OneSignal.InAppMessages.paused } set { OneSignal.InAppMessages.paused = newValue } } - + func addTrigger(key: String, value: String) { OneSignal.InAppMessages.addTrigger(key, withValue: value) } - + func removeTrigger(_ key: String) { OneSignal.InAppMessages.removeTrigger(key) } - + // MARK: - Location - + var isLocationShared: Bool { get { OneSignal.Location.isShared } set { OneSignal.Location.isShared = newValue } } - + func requestLocationPermission() { OneSignal.Location.requestPermission() } - + // MARK: - Notifications - + func clearAllNotifications() { OneSignal.Notifications.clearAll() } - + var hasNotificationPermission: Bool { OneSignal.Notifications.permission } - + // MARK: - Observers - + func addPushSubscriptionObserver(_ observer: OSPushSubscriptionObserver) { OneSignal.User.pushSubscription.addObserver(observer) } - + func addUserObserver(_ observer: OSUserStateObserver) { OneSignal.User.addObserver(observer) } - + func addPermissionObserver(_ observer: OSNotificationPermissionObserver) { OneSignal.Notifications.addPermissionObserver(observer) } - + func addNotificationClickListener(_ listener: OSNotificationClickListener) { OneSignal.Notifications.addClickListener(listener) } - + func addNotificationLifecycleListener(_ listener: OSNotificationLifecycleListener) { OneSignal.Notifications.addForegroundLifecycleListener(listener) } - + func addInAppMessageClickListener(_ listener: OSInAppMessageClickListener) { OneSignal.InAppMessages.addClickListener(listener) } - + func addInAppMessageLifecycleListener(_ listener: OSInAppMessageLifecycleListener) { OneSignal.InAppMessages.addLifecycleListener(listener) } diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift index 9b8cc0227..7e8e8dae9 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift @@ -34,85 +34,85 @@ import OneSignalLocation /// Main ViewModel managing all OneSignal SDK state and interactions @MainActor final class OneSignalViewModel: ObservableObject { - + // MARK: - Published Properties - + // App Info @Published var appId: String - + // User @Published var externalUserId: String? @Published var aliases: [KeyValueItem] = [] - + // Push Subscription @Published var pushSubscriptionId: String? @Published var isPushEnabled: Bool = false - + // Email & SMS @Published var emails: [String] = [] @Published var smsNumbers: [String] = [] - + // Tags @Published var tags: [KeyValueItem] = [] - + // In-App Messaging @Published var isInAppMessagesPaused: Bool = true @Published var triggers: [KeyValueItem] = [] - + // Location @Published var isLocationShared: Bool = false - + // UI State @Published var showingAddSheet: Bool = false @Published var addItemType: AddItemType = .email @Published var toastMessage: String? - + // MARK: - Private Properties - + private let service: OneSignalService private var observers = Observers() - + // MARK: - Initialization - + init(service: OneSignalService = .shared) { self.service = service self.appId = service.appId - + // Initial state sync refreshState() - + // Set up observers setupObservers() } - + // MARK: - State Management - + func refreshState() { pushSubscriptionId = service.pushSubscriptionId isPushEnabled = service.isPushEnabled isInAppMessagesPaused = service.isInAppMessagesPaused isLocationShared = service.isLocationShared - + // Sync tags from SDK let sdkTags = service.getTags() tags = sdkTags.map { KeyValueItem(key: $0.key, value: $0.value) } } - + // MARK: - Consent - + func revokeConsent() { service.revokeConsent() showToast("Consent revoked") } - + // MARK: - User Management - + func login(externalId: String) { service.login(externalId: externalId) externalUserId = externalId showToast("Logged in as \(externalId)") } - + func logout() { service.logout() externalUserId = nil @@ -123,23 +123,23 @@ final class OneSignalViewModel: ObservableObject { triggers.removeAll() showToast("Logged out") } - + // MARK: - Aliases - + func addAlias(label: String, id: String) { service.addAlias(label: label, id: id) aliases.append(KeyValueItem(key: label, value: id)) showToast("Alias added") } - + func removeAlias(_ item: KeyValueItem) { service.removeAlias(item.key) aliases.removeAll { $0.id == item.id } showToast("Alias removed") } - + // MARK: - Push Subscription - + func togglePushEnabled() { if isPushEnabled { service.optOutPush() @@ -151,7 +151,7 @@ final class OneSignalViewModel: ObservableObject { showToast("Push enabled") } } - + func requestPushPermission() { service.requestPushPermission { [weak self] accepted in Task { @MainActor in @@ -160,37 +160,37 @@ final class OneSignalViewModel: ObservableObject { } } } - + // MARK: - Email - + func addEmail(_ email: String) { service.addEmail(email) emails.append(email) showToast("Email added") } - + func removeEmail(_ email: String) { service.removeEmail(email) emails.removeAll { $0 == email } showToast("Email removed") } - + // MARK: - SMS - + func addSms(_ number: String) { service.addSms(number) smsNumbers.append(number) showToast("SMS added") } - + func removeSms(_ number: String) { service.removeSms(number) smsNumbers.removeAll { $0 == number } showToast("SMS removed") } - + // MARK: - Tags - + func addTag(key: String, value: String) { service.addTag(key: key, value: value) // Remove existing tag with same key if present @@ -198,38 +198,38 @@ final class OneSignalViewModel: ObservableObject { tags.append(KeyValueItem(key: key, value: value)) showToast("Tag added") } - + func removeTag(_ item: KeyValueItem) { service.removeTag(item.key) tags.removeAll { $0.id == item.id } showToast("Tag removed") } - + // MARK: - Outcomes - + func sendOutcome(_ name: String) { service.sendOutcome(name) showToast("Outcome '\(name)' sent") } - + func sendOutcome(_ name: String, value: Double) { service.sendOutcome(name, value: NSNumber(value: value)) showToast("Outcome '\(name)' with value \(value) sent") } - + func sendUniqueOutcome(_ name: String) { service.sendUniqueOutcome(name) showToast("Unique outcome '\(name)' sent") } - + // MARK: - In-App Messaging - + func toggleInAppMessagesPaused() { isInAppMessagesPaused.toggle() service.isInAppMessagesPaused = isInAppMessagesPaused showToast(isInAppMessagesPaused ? "In-app messages paused" : "In-app messages resumed") } - + func addTrigger(key: String, value: String) { service.addTrigger(key: key, value: value) // Remove existing trigger with same key if present @@ -237,52 +237,52 @@ final class OneSignalViewModel: ObservableObject { triggers.append(KeyValueItem(key: key, value: value)) showToast("Trigger added") } - + func removeTrigger(_ item: KeyValueItem) { service.removeTrigger(item.key) triggers.removeAll { $0.id == item.id } showToast("Trigger removed") } - + // MARK: - Location - + func toggleLocationShared() { isLocationShared.toggle() service.isLocationShared = isLocationShared showToast(isLocationShared ? "Location sharing enabled" : "Location sharing disabled") } - + func promptLocation() { service.requestLocationPermission() showToast("Location permission requested") } - + // MARK: - Notifications - + func clearAllNotifications() { service.clearAllNotifications() showToast("All notifications cleared") } - + func sendTestNotification(_ type: NotificationType) { // In a real app, this would trigger a notification via your backend // For demo purposes, we just show a toast showToast("Test '\(type.rawValue)' notification triggered") } - + func sendTestInAppMessage(_ type: InAppMessageType) { // In a real app, this would trigger an IAM via your backend // For demo purposes, we just show a toast showToast("Test '\(type.rawValue)' in-app message triggered") } - + // MARK: - Add Sheet - + func showAddSheet(for type: AddItemType) { addItemType = type showingAddSheet = true } - + func handleAddItem(key: String, value: String) { switch addItemType { case .alias: @@ -300,21 +300,21 @@ final class OneSignalViewModel: ObservableObject { } showingAddSheet = false } - + // MARK: - Toast - + private func showToast(_ message: String) { toastMessage = message - + // Auto-dismiss after 2 seconds Task { try? await Task.sleep(nanoseconds: 2_000_000_000) toastMessage = nil } } - + // MARK: - Observers - + private func setupObservers() { observers.viewModel = self service.addPushSubscriptionObserver(observers) @@ -327,21 +327,21 @@ final class OneSignalViewModel: ObservableObject { private class Observers: NSObject, OSPushSubscriptionObserver, OSUserStateObserver, OSNotificationPermissionObserver { weak var viewModel: OneSignalViewModel? - + func onPushSubscriptionDidChange(state: OSPushSubscriptionChangedState) { Task { @MainActor in viewModel?.pushSubscriptionId = state.current.id viewModel?.isPushEnabled = state.current.optedIn } } - + func onUserStateDidChange(state: OSUserChangedState) { Task { @MainActor in // User state changed - could refresh aliases, etc. print("User state changed: \(state.jsonRepresentation())") } } - + func onNotificationPermissionDidChange(_ permission: Bool) { Task { @MainActor in viewModel?.isPushEnabled = permission && (viewModel?.isPushEnabled ?? false) diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift index ada4a861a..ed1c621d6 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift @@ -32,15 +32,15 @@ struct AddItemSheet: View { let itemType: AddItemType let onAdd: (String, String) -> Void let onCancel: () -> Void - + @State private var keyText: String = "" @State private var valueText: String = "" @FocusState private var focusedField: Field? - + private enum Field { case key, value } - + var body: some View { NavigationStack { Form { @@ -50,7 +50,7 @@ struct AddItemSheet: View { .focused($focusedField, equals: .key) .textInputAutocapitalization(.never) .autocorrectionDisabled() - + TextField(itemType.valuePlaceholder, text: $valueText) .focused($focusedField, equals: .value) .textInputAutocapitalization(.never) @@ -75,7 +75,7 @@ struct AddItemSheet: View { onCancel() } } - + ToolbarItem(placement: .confirmationAction) { Button(itemType == .externalUserId ? "Login" : "Add") { onAdd(keyText, valueText) @@ -90,7 +90,7 @@ struct AddItemSheet: View { .presentationDetents([.medium]) .presentationDragIndicator(.visible) } - + private var isValid: Bool { if itemType.requiresKeyValue { return !keyText.trimmingCharacters(in: .whitespaces).isEmpty && diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift index d8b149e53..af50dc8b2 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/KeyValueRow.swift @@ -31,12 +31,12 @@ import SwiftUI struct KeyValueRow: View { let item: KeyValueItem let onDelete: (() -> Void)? - + init(item: KeyValueItem, onDelete: (() -> Void)? = nil) { self.item = item self.onDelete = onDelete } - + var body: some View { HStack { VStack(alignment: .leading, spacing: 2) { @@ -46,9 +46,9 @@ struct KeyValueRow: View { Text(item.value) .font(.body) } - + Spacer() - + if let onDelete = onDelete { Button(action: onDelete) { Image(systemName: "trash") @@ -65,19 +65,19 @@ struct KeyValueRow: View { struct SingleValueRow: View { let value: String let onDelete: (() -> Void)? - + init(value: String, onDelete: (() -> Void)? = nil) { self.value = value self.onDelete = onDelete } - + var body: some View { HStack { Text(value) .font(.body) - + Spacer() - + if let onDelete = onDelete { Button(action: onDelete) { Image(systemName: "trash") @@ -95,13 +95,13 @@ struct InfoRow: View { let label: String let value: String let isMonospaced: Bool - + init(label: String, value: String, isMonospaced: Bool = false) { self.label = label self.value = value self.isMonospaced = isMonospaced } - + var body: some View { HStack { Text(label) @@ -119,7 +119,7 @@ struct InfoRow: View { /// A placeholder row for empty lists struct EmptyListRow: View { let message: String - + var body: some View { Text(message) .foregroundColor(.secondary) @@ -140,17 +140,17 @@ struct EmptyListRow: View { item: KeyValueItem(key: "subscription_tier", value: "premium") ) } - + Section("Single Values") { SingleValueRow(value: "user@example.com", onDelete: {}) SingleValueRow(value: "+1234567890") } - + Section("Info Rows") { InfoRow(label: "App ID", value: "77e32082-ea27-42e3-a898-c72e141824ef", isMonospaced: true) InfoRow(label: "Status", value: "Active") } - + Section("Empty") { EmptyListRow(message: "No items added") } diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift index 35dd290a1..9a54202eb 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/NotificationGrid.swift @@ -30,12 +30,12 @@ import SwiftUI /// A grid of notification type buttons struct NotificationTypeGrid: View { let onSelect: (NotificationType) -> Void - + private let columns = [ GridItem(.flexible(), spacing: 12), GridItem(.flexible(), spacing: 12) ] - + var body: some View { LazyVGrid(columns: columns, spacing: 12) { ForEach(NotificationType.allCases) { type in @@ -51,12 +51,12 @@ struct NotificationTypeGrid: View { /// A grid of in-app message type buttons struct InAppMessageTypeGrid: View { let onSelect: (InAppMessageType) -> Void - + private let columns = [ GridItem(.flexible(), spacing: 12), GridItem(.flexible(), spacing: 12) ] - + var body: some View { LazyVGrid(columns: columns, spacing: 12) { ForEach(InAppMessageType.allCases) { type in @@ -73,7 +73,7 @@ struct InAppMessageTypeGrid: View { struct NotificationTypeButton: View { let type: NotificationType let action: () -> Void - + var body: some View { Button(action: action) { VStack(spacing: 8) { @@ -97,7 +97,7 @@ struct NotificationTypeButton: View { struct InAppMessageTypeButton: View { let type: InAppMessageType let action: () -> Void - + var body: some View { Button(action: action) { VStack(spacing: 8) { @@ -125,7 +125,7 @@ struct InAppMessageTypeButton: View { NotificationTypeGrid(onSelect: { type in print("Selected: \(type.rawValue)") }) - + Text("Send In-App Message") .font(.headline) InAppMessageTypeGrid(onSelect: { type in diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift index a0e298b63..eede3ce4d 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift @@ -30,7 +30,7 @@ import SwiftUI /// A toast notification view that appears at the bottom of the screen struct ToastView: View { let message: String - + var body: some View { Text(message) .font(.subheadline) @@ -46,11 +46,11 @@ struct ToastView: View { /// A view modifier that overlays a toast message struct ToastModifier: ViewModifier { @Binding var message: String? - + func body(content: Content) -> some View { ZStack { content - + if let message = message { VStack { Spacer() diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift index 1e383f694..80a7db871 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/ContentView.swift @@ -30,7 +30,7 @@ import SwiftUI /// Main content view composing all sections struct ContentView: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { NavigationStack { List { diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift index d16c71ffa..4bab9ebc2 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/AppInfoSection.swift @@ -30,7 +30,7 @@ import SwiftUI /// Section displaying app information and consent management struct AppInfoSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { Section { // App ID @@ -43,7 +43,7 @@ struct AppInfoSection: View { .textSelection(.enabled) } .padding(.vertical, 4) - + // Revoke Consent Button Button(role: .destructive) { viewModel.revokeConsent() diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift index a3eda8e0c..2d89f9077 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/LocationSection.swift @@ -30,7 +30,7 @@ import SwiftUI /// Section for location sharing and permissions struct LocationSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { Section { Toggle(isOn: Binding( @@ -44,7 +44,7 @@ struct LocationSection: View { .foregroundColor(.secondary) } } - + Button { viewModel.promptLocation() } label: { diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift index 1e38356ca..ef21501a0 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/MessagingSection.swift @@ -33,7 +33,7 @@ struct MessagingSection: View { @State private var showingOutcomeSheet = false @State private var outcomeName = "" @State private var outcomeValue = "" - + var body: some View { // Outcome Events Section Section { @@ -50,7 +50,7 @@ struct MessagingSection: View { } header: { Text("Outcome Events") } - + // In-App Messaging Section Section { Toggle(isOn: Binding( @@ -67,7 +67,7 @@ struct MessagingSection: View { } header: { Text("In-App Messaging") } - + // Triggers Section Section { if viewModel.triggers.isEmpty { @@ -79,7 +79,7 @@ struct MessagingSection: View { } } } - + Button { viewModel.showAddSheet(for: .trigger) } label: { @@ -115,16 +115,16 @@ struct MessagingSection: View { struct OutcomeSheet: View { let onSend: (String, Double?) -> Void let onCancel: () -> Void - + @State private var outcomeName = "" @State private var outcomeValue = "" @State private var includeValue = false @FocusState private var focusedField: Field? - + private enum Field { case name, value } - + var body: some View { NavigationStack { Form { @@ -134,10 +134,10 @@ struct OutcomeSheet: View { .textInputAutocapitalization(.never) .autocorrectionDisabled() } - + Section { Toggle("Include Value", isOn: $includeValue) - + if includeValue { TextField("Value", text: $outcomeValue) .focused($focusedField, equals: .value) @@ -153,7 +153,7 @@ struct OutcomeSheet: View { onCancel() } } - + ToolbarItem(placement: .confirmationAction) { Button("Send") { let value = includeValue ? Double(outcomeValue) : nil diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift index f30b436a2..92b7bcdfd 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/NotificationSection.swift @@ -30,7 +30,7 @@ import SwiftUI /// Section for sending test push notifications and in-app messages struct NotificationSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { // Send Push Notification Section Section { @@ -40,7 +40,7 @@ struct NotificationSection: View { } header: { Text("Send Push Notification") } - + // Send In-App Message Section Section { InAppMessageTypeGrid { type in diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift index 675ad8390..24ea67eca 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/SubscriptionSection.swift @@ -30,7 +30,7 @@ import SwiftUI /// Section for push subscription, email, and SMS management struct SubscriptionSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { // Push Section Section { @@ -44,7 +44,7 @@ struct SubscriptionSection: View { .textSelection(.enabled) } .padding(.vertical, 4) - + // Enabled Toggle Toggle("Enabled", isOn: Binding( get: { viewModel.isPushEnabled }, @@ -53,7 +53,7 @@ struct SubscriptionSection: View { } header: { Text("Push") } - + // Emails Section Section { if viewModel.emails.isEmpty { @@ -65,7 +65,7 @@ struct SubscriptionSection: View { } } } - + Button { viewModel.showAddSheet(for: .email) } label: { @@ -79,7 +79,7 @@ struct SubscriptionSection: View { } header: { Text("Emails") } - + // SMS Section Section { if viewModel.smsNumbers.isEmpty { @@ -91,7 +91,7 @@ struct SubscriptionSection: View { } } } - + Button { viewModel.showAddSheet(for: .sms) } label: { diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift index 4de6de523..586169e30 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/TagsSection.swift @@ -30,7 +30,7 @@ import SwiftUI /// Section for managing user tags struct TagsSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { Section { if viewModel.tags.isEmpty { @@ -42,7 +42,7 @@ struct TagsSection: View { } } } - + Button { viewModel.showAddSheet(for: .tag) } label: { diff --git a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift index c8fcaab88..77b8c130b 100644 --- a/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift +++ b/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Sections/UserSection.swift @@ -30,7 +30,7 @@ import SwiftUI /// Section for user login/logout and alias management struct UserSection: View { @EnvironmentObject var viewModel: OneSignalViewModel - + var body: some View { // Login/Logout Section Section { @@ -45,7 +45,7 @@ struct UserSection: View { Spacer() } } - + // Logout Button Button(role: .destructive) { viewModel.logout() @@ -58,7 +58,7 @@ struct UserSection: View { } } .disabled(viewModel.externalUserId == nil) - + // Current User Info if let userId = viewModel.externalUserId { InfoRow(label: "External User ID", value: userId) @@ -66,7 +66,7 @@ struct UserSection: View { } header: { Text("User") } - + // Aliases Section Section { if viewModel.aliases.isEmpty { @@ -78,7 +78,7 @@ struct UserSection: View { } } } - + Button { viewModel.showAddSheet(for: .alias) } label: { diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift index bbe2bc5d9..50e0f1bad 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/ViewModels/OneSignalViewModel.swift @@ -444,7 +444,19 @@ final class OneSignalViewModel: ObservableObject { showToast("Live Activity '\(id)' exited") } - // MARK: - Notifications + // MARK: - Observers + + private func setupObservers() { + observers.viewModel = self + service.addPushSubscriptionObserver(observers) + service.addUserObserver(observers) + service.addPermissionObserver(observers) + } +} + +// MARK: - Notifications + +extension OneSignalViewModel { func clearAllNotifications() { service.clearAllNotifications() @@ -504,8 +516,11 @@ final class OneSignalViewModel: ObservableObject { service.addTrigger(key: "iam_type", value: triggerValue) showToast("Sent In-App Message: \(type.rawValue)") } +} - // MARK: - Add Sheet +// MARK: - Sheet Handling + +extension OneSignalViewModel { func showAddSheet(for type: AddItemType) { addItemType = type @@ -567,27 +582,20 @@ final class OneSignalViewModel: ObservableObject { } showingRemoveMultiSheet = false } +} + +// MARK: - Toast - // MARK: - Toast +extension OneSignalViewModel { func showToast(_ message: String) { toastMessage = message - // Auto-dismiss after 2 seconds Task { try? await Task.sleep(nanoseconds: 2_000_000_000) toastMessage = nil } } - - // MARK: - Observers - - private func setupObservers() { - observers.viewModel = self - service.addPushSubscriptionObserver(observers) - service.addUserObserver(observers) - service.addPermissionObserver(observers) - } } // MARK: - Observer Classes diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift index 1df25d143..62ee72211 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/AddItemSheet.swift @@ -32,15 +32,15 @@ struct AddItemSheet: View { let itemType: AddItemType let onAdd: (String, String) -> Void let onCancel: () -> Void - + @State private var keyText: String = "" @State private var valueText: String = "" @FocusState private var focusedField: Field? - + private enum Field { case key, value } - + var body: some View { NavigationStack { VStack(spacing: 24) { @@ -49,7 +49,7 @@ struct AddItemSheet: View { .font(.title2) .fontWeight(.semibold) .frame(maxWidth: .infinity, alignment: .leading) - + // Input Fields if itemType.requiresKeyValue { VStack(alignment: .leading, spacing: 8) { @@ -62,7 +62,7 @@ struct AddItemSheet: View { .textInputAutocapitalization(.never) .autocorrectionDisabled() } - + VStack(alignment: .leading, spacing: 8) { Text("Value") .font(.caption) @@ -87,18 +87,18 @@ struct AddItemSheet: View { .keyboardType(itemType.keyboardType) } } - + Spacer() - + // Action Buttons HStack(spacing: 24) { Spacer() - + Button("CANCEL") { onCancel() } .foregroundColor(.accentColor) - + Button(itemType == .externalUserId ? "LOGIN" : "ADD") { onAdd(keyText, valueText) } @@ -115,7 +115,7 @@ struct AddItemSheet: View { .presentationDetents([.medium]) .presentationDragIndicator(.visible) } - + private var singleFieldLabel: String { switch itemType { case .email: return "New Email" @@ -124,7 +124,7 @@ struct AddItemSheet: View { default: return "Value" } } - + private var isValid: Bool { if itemType.requiresKeyValue { return !keyText.trimmingCharacters(in: .whitespaces).isEmpty && @@ -137,12 +137,13 @@ struct AddItemSheet: View { /// A text field style with an underline instead of a border struct UnderlineTextFieldStyle: TextFieldStyle { + // swiftlint:disable:next identifier_name func _body(configuration: TextField) -> some View { VStack(spacing: 0) { configuration .font(.system(size: 17)) .padding(.vertical, 8) - + Rectangle() .fill(Color(.separator)) .frame(height: 1) diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift index a0e298b63..eede3ce4d 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Views/Components/ToastView.swift @@ -30,7 +30,7 @@ import SwiftUI /// A toast notification view that appears at the bottom of the screen struct ToastView: View { let message: String - + var body: some View { Text(message) .font(.subheadline) @@ -46,11 +46,11 @@ struct ToastView: View { /// A view modifier that overlays a toast message struct ToastModifier: ViewModifier { @Binding var message: String? - + func body(content: Content) -> some View { ZStack { content - + if let message = message { VStack { Spacer()