Skip to content

Commit

Permalink
Push to start live activities (#1377)
Browse files Browse the repository at this point in the history
Update SDK to support Live Activities PushToStart and add a concept of a "Default" Live Activity to facilitate easier wrapper SDK adoption.

Push to Start Live Activities
Starting with iOS 17.2, Live Activities can now be started via push notification.  This change enhances the OneSignal SDK to provide application's access to the full suite of Live Activity functionality.

Default Live Activity
The concept of a "Default" Live Activity has been established in the SDK, which eliminates the need for a customer app to define and manage their own `ActivityAttributes`.  This is most beneficial for wrapper-SDKs, as they will no longer need to create their own cross-platform <-> native iOS bridge to establish this management.

---------

Co-authored-by: emawby <emawby@gmail.com>
Co-authored-by: Nan <nanftw@gmail.com>
  • Loading branch information
3 people authored May 8, 2024
1 parent 5e3d8e4 commit 0ba13bf
Show file tree
Hide file tree
Showing 88 changed files with 4,988 additions and 1,146 deletions.
1 change: 1 addition & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ jobs:
codesign --timestamp -v --sign "Apple Distribution: OneSignal, Inc. (J3J28YJX9L)" OneSignal_Outcomes/OneSignalOutcomes.xcframework
codesign --timestamp -v --sign "Apple Distribution: OneSignal, Inc. (J3J28YJX9L)" OneSignal_User/OneSignalUser.xcframework
codesign --timestamp -v --sign "Apple Distribution: OneSignal, Inc. (J3J28YJX9L)" OneSignal_XCFramework/OneSignalFramework.xcframework
codesign --timestamp -v --sign "Apple Distribution: OneSignal, Inc. (J3J28YJX9L)" OneSignal_LiveActivities/OneSignalLiveActivities.xcframework
shell: bash
- name: Update Swift Package
run: |
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ jobs:
runs-on: macos-latest-large

steps:
- name: Select Xcode Version
run: |
sudo xcode-select -s /Applications/Xcode_15.2.app
- name: Checkout OneSignal-iOS-SDK
uses: actions/checkout@v3
- name: Set Default Scheme
Expand Down
8 changes: 8 additions & 0 deletions OneSignal.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ Pod::Spec.new do |s|
ss.vendored_frameworks = 'iOS_SDK/OneSignalSDK/OneSignal_User/OneSignalUser.xcframework'
end

s.subspec 'OneSignalLiveActivities' do |ss|
ss.dependency 'OneSignal/OneSignalCore'
ss.dependency 'OneSignal/OneSignalOSCore'
ss.dependency 'OneSignal/OneSignalUser'
ss.vendored_frameworks = 'iOS_SDK/OneSignalSDK/OneSignal_LiveActivities/OneSignalLiveActivities.xcframework'
end

s.subspec 'OneSignalLocation' do |ss|
ss.dependency 'OneSignal/OneSignalCore'
ss.dependency 'OneSignal/OneSignalOSCore'
Expand All @@ -70,6 +77,7 @@ Pod::Spec.new do |s|
ss.dependency 'OneSignal/OneSignalExtension'
ss.dependency 'OneSignal/OneSignalNotifications'
ss.dependency 'OneSignal/OneSignalUser'
ss.dependency 'OneSignal/OneSignalLiveActivities'
ss.ios.vendored_frameworks = 'iOS_SDK/OneSignalSDK/OneSignal_XCFramework/OneSignalFramework.xcframework'
end

Expand Down
Empty file.
Empty file.
8 changes: 8 additions & 0 deletions OneSignalXCFramework.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ Pod::Spec.new do |s|
ss.vendored_frameworks = 'iOS_SDK/OneSignalSDK/OneSignal_User/OneSignalUser.xcframework'
end

s.subspec 'OneSignalLiveActivities' do |ss|
ss.dependency 'OneSignalXCFramework/OneSignalCore'
ss.dependency 'OneSignalXCFramework/OneSignalOSCore'
ss.dependency 'OneSignalXCFramework/OneSignalUser'
ss.vendored_frameworks = 'iOS_SDK/OneSignalSDK/OneSignal_LiveActivities/OneSignalLiveActivities.xcframework'
end

s.subspec 'OneSignalLocation' do |ss|
ss.dependency 'OneSignalXCFramework/OneSignalCore'
ss.dependency 'OneSignalXCFramework/OneSignalOSCore'
Expand All @@ -70,6 +77,7 @@ Pod::Spec.new do |s|
ss.dependency 'OneSignalXCFramework/OneSignalExtension'
ss.dependency 'OneSignalXCFramework/OneSignalNotifications'
ss.dependency 'OneSignalXCFramework/OneSignalUser'
ss.dependency 'OneSignalXCFramework/OneSignalLiveActivities'
ss.ios.vendored_frameworks = 'iOS_SDK/OneSignalSDK/OneSignal_XCFramework/OneSignalFramework.xcframework'
end

Expand Down
14 changes: 14 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ let package = Package(
"OneSignalFramework",
"OneSignalUser",
"OneSignalNotifications",
"OneSignalLiveActivities",
"OneSignalExtension",
"OneSignalOutcomes",
"OneSignalOSCore",
Expand Down Expand Up @@ -103,6 +104,14 @@ let package = Package(
],
path: "OneSignalOSCoreWrapper"
),
.target(
name: "OneSignalLiveActivitiesWrapper",
dependencies: [
"OneSignalUser",
"OneSignalCore"
],
path: "OneSignalLiveActivitiesWrapper"
),
.binaryTarget(
name: "OneSignalFramework",
url: "https://github.com/OneSignal/OneSignal-iOS-SDK/releases/download/5.1.6/OneSignalFramework.xcframework.zip",
Expand Down Expand Up @@ -147,6 +156,11 @@ let package = Package(
name: "OneSignalCore",
url: "https://github.com/OneSignal/OneSignal-iOS-SDK/releases/download/5.1.6/OneSignalCore.xcframework.zip",
checksum: "46be3f316ed4506ab88d787821476572c9b016d7778beaa60a59a607c6c223e7"
),
.binaryTarget(
name: "OneSignalLiveActivities",
url: "https://github.com/OneSignal/OneSignal-iOS-SDK/releases/download/5.1.0/OneSignalLiveActivities.xcframework.zip",
checksum: ""
)
]
)
1 change: 1 addition & 0 deletions default
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
UnitTestApp
9 changes: 9 additions & 0 deletions iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#import "AppDelegate.h"
#import "ViewController.h"
#import "OneSignalExample-Swift.h"

@interface OneSignalNotificationCenterDelegate: NSObject<UNUserNotificationCenterDelegate>
@end
Expand Down Expand Up @@ -74,6 +75,14 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(

NSLog(@"UNUserNotificationCenter.delegate: %@", UNUserNotificationCenter.currentNotificationCenter.delegate);

#if TARGET_OS_MACCATALYST
#else
if (@available(iOS 16.1, *)) {
[LiveActivityController start];
}
#endif


return YES;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Modified MIT License
*
* Copyright 2023 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.
*/
#if targetEnvironment(macCatalyst)
#else
import ActivityKit
import OneSignalLiveActivities

/**
An example of an ActivityAttribute that is "OneSignal SDK aware". This attribute conforms
to the OneSignalActivityAttributes protocol and has a onesignal property, which contains
metadata used by the OneSignal SDK. To enable this Live Activity type for OneSignal
you only need to call `enableLiveActivities`, the SDK takes care of the rest.
*/
struct ExampleAppFirstWidgetAttributes: OneSignalLiveActivityAttributes {
public struct ContentState: OneSignalLiveActivityContentState {
// Dynamic stateful properties about your activity go here!
var message: String

var onesignal: OneSignalLiveActivityContentStateData?
}

// Fixed non-changing properties about your activity go here!
var title: String

// OneSignal Attributes to allow the SDK to do it's thing
var onesignal: OneSignalLiveActivityAttributeData
}

/**
Another example of an ActivityAttribute that is "OneSignal SDK aware". A second attributes
structure is created here because the example app has two different "types" of Live Activity
experiences. Noting that `enableLiveActivities` must be called for each "type" of
Live Activity defined.
*/
struct ExampleAppSecondWidgetAttributes: OneSignalLiveActivityAttributes {
public struct ContentState: OneSignalLiveActivityContentState {
var message: String
var status: String
var progress: Double
var bugs: Int

var onesignal: OneSignalLiveActivityContentStateData?
}

// Fixed non-changing properties about your activity go here!
var title: String

// OneSignal Attributes to allow the SDK to do it's thing
var onesignal: OneSignalLiveActivityAttributeData
}

/**
This example of an ActivityAttribute is **not** "OneSignal SDK aware". Listening
to push to start tokens and update tokens for this activity must be done by the app
itself.
*/
struct ExampleAppThirdWidgetAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
// Dynamic stateful properties about your activity go here!
var message: String
}

// Fixed non-changing properties about your activity go here!
var title: String

// Whether this LA was started via pushToStart (true), or via app (false)
var isPushToStart: Bool
}
#endif
115 changes: 95 additions & 20 deletions iOS_SDK/OneSignalDevApp/OneSignalDevApp/LiveActivityController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,43 +26,118 @@
*/

import Foundation
import ActivityKit
import UserNotifications
import OneSignalFramework
#if targetEnvironment(macCatalyst)
#else
import ActivityKit
import OneSignalLiveActivities
@objc
class LiveActivityController: NSObject {

@available(iOS 16.1, *)
@objc
static func start() {
// ExampleAppFirstWidgetAttributes and ExampleAppSecondWidgetAttributes enable the OneSignal SDK to
// listen for start/update tokens, this is the only call needed.
OneSignal.LiveActivities.setup(ExampleAppFirstWidgetAttributes.self)
OneSignal.LiveActivities.setup(ExampleAppSecondWidgetAttributes.self)

struct OneSignalWidgetAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
// Dynamic stateful properties about your activity go here!
var message: String
// There is a "built in" Live Activity Widget Attributes called `DefaultLiveActivityAttributes`.
// This is mostly for cross-platform SDKs and allows OneSignal to handle everything but the
// creation of the Widget Extension.
OneSignal.LiveActivities.setupDefault()

if #available(iOS 17.2, *) {
// ExampleAppThirdWidgetAttributes is an example of how to manually set up LA.
// Setup an async task to monitor and send pushToStartToken updates to OneSignalSDK.
Task {
for try await data in Activity<ExampleAppThirdWidgetAttributes>.pushToStartTokenUpdates {
let token = data.map {String(format: "%02x", $0)}.joined()
OneSignal.LiveActivities.setPushToStartToken(ExampleAppThirdWidgetAttributes.self, withToken: token)
}
}
// Setup an async task to monitor for an activity to be started, for each started activity we
// can then set up an async task to monitor and send updateToken updates to OneSignalSDK. We
// filter out LA started in-app, because the `createActivity` function below does its own
// updateToken update monitoring. If there can be multiple instances of this activity-type,
// the activity-id (i.e. "my-activity-id") is most likely passed down as an attribute within
// ExampleAppThirdWidgetAttributes.
Task {
for await activity in Activity<ExampleAppThirdWidgetAttributes>.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)
}
}
}
}
}
}

// Fixed non-changing properties about your activity go here!
var title: String
}
@objc
class LiveActivityController: NSObject {
// To aid in testing
static var counter = 0
/**
An example of starting a Live Activity whose attributes are "OneSignal SDK aware". The SDK will handle listening for update tokens on behalf of the app.
*/
static var counter1 = 0
@available(iOS 13.0, *)
@objc
static func createOneSignalAwareActivity(activityId: String) {
if #available(iOS 16.1, *) {
counter1 += 1
let oneSignalAttribute = OneSignalLiveActivityAttributeData.create(activityId: activityId)
let attributes = ExampleAppFirstWidgetAttributes(title: "#" + String(counter1) + " OneSignal Dev App Live Activity", onesignal: oneSignalAttribute)
let contentState = ExampleAppFirstWidgetAttributes.ContentState(message: "Update this message through push or with Activity Kit")
do {
_ = try Activity<ExampleAppFirstWidgetAttributes>.request(
attributes: attributes,
contentState: contentState,
pushType: .token)
} catch let error {
print(error.localizedDescription)
}
}
}

/**
An example of starting a Live Activity using the DefaultLiveActivityAttributes. The SDK will handle listening for update tokens on behalf of the app.
*/
@available(iOS 13.0, *)
@objc
static func createDefaultActivity(activityId: String) {
if #available(iOS 16.1, *) {
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)
}
}

/**
An example of starting a Live Activity whose attributes are **not** "OneSignal SDK aware". The app must handle listening for update tokens and notify the OneSignal SDK.
*/
static var counter2 = 0
@available(iOS 13.0, *)
@objc
static func createActivity() async -> String? {
static func createActivity(activityId: String) async {
if #available(iOS 16.1, *) {
counter += 1
let attributes = OneSignalWidgetAttributes(title: "#" + String(counter) + " OneSignal Dev App Live Activity")
let contentState = OneSignalWidgetAttributes.ContentState(message: "Update this message through push or with Activity Kit")
counter2 += 1
let attributes = ExampleAppThirdWidgetAttributes(title: "#" + String(counter2) + " OneSignal Dev App Live Activity", isPushToStart: false)
let contentState = ExampleAppThirdWidgetAttributes.ContentState(message: "Update this message through push or with Activity Kit")
do {
let activity = try Activity<OneSignalWidgetAttributes>.request(
let activity = try Activity<ExampleAppThirdWidgetAttributes>.request(
attributes: attributes,
contentState: contentState,
pushType: .token)
for await data in activity.pushTokenUpdates {
let myToken = data.map {String(format: "%02x", $0)}.joined()
return myToken
OneSignal.LiveActivities.enter(activityId, withToken: myToken)
}
} catch let error {
print(error.localizedDescription)
return nil
}
}
return nil
}
}
#endif
10 changes: 5 additions & 5 deletions iOS_SDK/OneSignalDevApp/OneSignalDevApp/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -232,19 +232,19 @@ - (IBAction)sendUniqueOutcomeEvent:(id)sender {
}

- (IBAction)startAndEnterLiveActivity:(id)sender {
#if TARGET_OS_MACCATALYST
#else
if (@available(iOS 13.0, *)) {
NSString *activityId = [self.activityId text];
// Will not make a live activity if activityId is empty
if (activityId && activityId.length) {
[LiveActivityController createActivityWithCompletionHandler:^(NSString * token) {
if(token){
[OneSignal.LiveActivities enter:activityId withToken:token];
}
}];
// [LiveActivityController createDefaultActivityWithActivityId:activityId ];
[LiveActivityController createActivityWithActivityId:activityId completionHandler:^(void) {} ];
}
} else {
NSLog(@"Must use iOS 13 or later for swift concurrency which is required for [LiveActivityController createActivityWithCompletionHandler...");
}
#endif
}
- (IBAction)exitLiveActivity:(id)sender {
if (self.activityId.text && self.activityId.text.length) {
Expand Down
Loading

0 comments on commit 0ba13bf

Please sign in to comment.