Skip to content

Commit e6edce1

Browse files
committed
Put playback notification into Live Activity
1 parent 6f5c07e commit e6edce1

15 files changed

Lines changed: 522 additions & 74 deletions

ios-app/ClickTrack.xcodeproj/project.pbxproj

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
/* Begin PBXBuildFile section */
1010
4F3ABD9D293187EA00DB6023 /* ClickTrackApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3ABD9C293187EA00DB6023 /* ClickTrackApp.swift */; };
1111
4F3ABD9F293187EA00DB6023 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3ABD9E293187EA00DB6023 /* ContentView.swift */; };
12+
AA000001000000000000001 /* LiveActivityBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000001000000000000011 /* LiveActivityBridge.swift */; };
13+
AA000001000000000000002 /* LiveActivityNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000001000000000000012 /* LiveActivityNotification.swift */; };
14+
AA000001000000000000003 /* ClickTrackActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000001000000000000013 /* ClickTrackActivityAttributes.swift */; };
15+
AA000001000000000000004 /* ClickTrackActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000001000000000000013 /* ClickTrackActivityAttributes.swift */; };
16+
AA000001000000000000005 /* ClickTrackLiveActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000001000000000000014 /* ClickTrackLiveActivityView.swift */; };
17+
AA000001000000000000007 /* ClickTrackLiveActivityBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000001000000000000016 /* ClickTrackLiveActivityBundle.swift */; };
18+
AA000001000000000000008 /* ClickTrackLiveActivityExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = AA000001000000000000020 /* ClickTrackLiveActivityExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
1219
4F3ABDA1293187EA00DB6023 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F3ABDA0293187EA00DB6023 /* Assets.xcassets */; };
1320
4F3ABDA4293187EA00DB6023 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F3ABDA3293187EA00DB6023 /* Preview Assets.xcassets */; };
1421
4F5ED37F2F1421700038A196 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 4F5ED37E2F1421700038A196 /* AppIcon.icon */; };
@@ -28,6 +35,13 @@
2835
973DBEDB5C7BE0B4F40880B3 /* Pods-ClickTrack.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClickTrack.debug.xcconfig"; path = "Target Support Files/Pods-ClickTrack/Pods-ClickTrack.debug.xcconfig"; sourceTree = "<group>"; };
2936
C1D53B0EA9F03701E67DF125 /* Pods_ClickTrack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ClickTrack.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3037
CADECB568532290FC852942B /* Pods-ClickTrack.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClickTrack.release.xcconfig"; path = "Target Support Files/Pods-ClickTrack/Pods-ClickTrack.release.xcconfig"; sourceTree = "<group>"; };
38+
AA000001000000000000011 /* LiveActivityBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityBridge.swift; sourceTree = "<group>"; };
39+
AA000001000000000000012 /* LiveActivityNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityNotification.swift; sourceTree = "<group>"; };
40+
AA000001000000000000013 /* ClickTrackActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickTrackActivityAttributes.swift; sourceTree = "<group>"; };
41+
AA000001000000000000014 /* ClickTrackLiveActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickTrackLiveActivityView.swift; sourceTree = "<group>"; };
42+
AA000001000000000000016 /* ClickTrackLiveActivityBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickTrackLiveActivityBundle.swift; sourceTree = "<group>"; };
43+
AA000001000000000000017 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
44+
AA000001000000000000020 /* ClickTrackLiveActivityExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ClickTrackLiveActivityExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
3145
/* End PBXFileReference section */
3246

3347
/* Begin PBXFrameworksBuildPhase section */
@@ -39,6 +53,13 @@
3953
);
4054
runOnlyForDeploymentPostprocessing = 0;
4155
};
56+
AA000001000000000000030 /* Frameworks (Extension) */ = {
57+
isa = PBXFrameworksBuildPhase;
58+
buildActionMask = 2147483647;
59+
files = (
60+
);
61+
runOnlyForDeploymentPostprocessing = 0;
62+
};
4263
/* End PBXFrameworksBuildPhase section */
4364

4465
/* Begin PBXGroup section */
@@ -54,6 +75,7 @@
5475
isa = PBXGroup;
5576
children = (
5677
4F3ABD9B293187EA00DB6023 /* ClickTrack */,
78+
AA000001000000000000040 /* ClickTrackLiveActivity */,
5779
4F3ABD9A293187EA00DB6023 /* Products */,
5880
E3F968F33D3ECA9CAB0FB4B9 /* Pods */,
5981
299C411D90C319AB59B12A8C /* Frameworks */,
@@ -64,6 +86,7 @@
6486
isa = PBXGroup;
6587
children = (
6688
4F3ABD99293187EA00DB6023 /* ClickTrack.app */,
89+
AA000001000000000000020 /* ClickTrackLiveActivityExtension.appex */,
6790
);
6891
name = Products;
6992
sourceTree = "<group>";
@@ -75,13 +98,26 @@
7598
4FDAC2302F0B0FC100888848 /* GoogleService-Info.plist */,
7699
4F3ABD9C293187EA00DB6023 /* ClickTrackApp.swift */,
77100
4F3ABD9E293187EA00DB6023 /* ContentView.swift */,
101+
AA000001000000000000011 /* LiveActivityBridge.swift */,
102+
AA000001000000000000012 /* LiveActivityNotification.swift */,
78103
4F3ABDA0293187EA00DB6023 /* Assets.xcassets */,
79104
4F5ED37E2F1421700038A196 /* AppIcon.icon */,
80105
4F3ABDA2293187EA00DB6023 /* Preview Content */,
81106
);
82107
path = ClickTrack;
83108
sourceTree = "<group>";
84109
};
110+
AA000001000000000000040 /* ClickTrackLiveActivity */ = {
111+
isa = PBXGroup;
112+
children = (
113+
AA000001000000000000013 /* ClickTrackActivityAttributes.swift */,
114+
AA000001000000000000014 /* ClickTrackLiveActivityView.swift */,
115+
AA000001000000000000016 /* ClickTrackLiveActivityBundle.swift */,
116+
AA000001000000000000017 /* Info.plist */,
117+
);
118+
path = ClickTrackLiveActivity;
119+
sourceTree = "<group>";
120+
};
85121
4F3ABDA2293187EA00DB6023 /* Preview Content */ = {
86122
isa = PBXGroup;
87123
children = (
@@ -112,17 +148,36 @@
112148
89774DA68A362F87E297C2E6 /* [CP] Embed Pods Frameworks */,
113149
0444A912D05438301350F401 /* Frameworks */,
114150
3A4F6D87E60D21205A9161CB /* [CP] Copy Pods Resources */,
151+
AA000001000000000000070 /* Embed App Extensions */,
115152
4FDAC2322F0B19C200888848 /* Upload Symbols To Crashlytics */,
116153
);
117154
buildRules = (
118155
);
119156
dependencies = (
157+
AA000001000000000000080 /* PBXTargetDependency */,
120158
);
121159
name = ClickTrack;
122160
productName = ClickTrack;
123161
productReference = 4F3ABD99293187EA00DB6023 /* ClickTrack.app */;
124162
productType = "com.apple.product-type.application";
125163
};
164+
AA000001000000000000050 /* ClickTrackLiveActivityExtension */ = {
165+
isa = PBXNativeTarget;
166+
buildConfigurationList = AA000001000000000000060 /* Build configuration list for PBXNativeTarget "ClickTrackLiveActivityExtension" */;
167+
buildPhases = (
168+
AA000001000000000000031 /* Sources */,
169+
AA000001000000000000032 /* Resources */,
170+
AA000001000000000000030 /* Frameworks (Extension) */,
171+
);
172+
buildRules = (
173+
);
174+
dependencies = (
175+
);
176+
name = ClickTrackLiveActivityExtension;
177+
productName = ClickTrackLiveActivityExtension;
178+
productReference = AA000001000000000000020 /* ClickTrackLiveActivityExtension.appex */;
179+
productType = "com.apple.product-type.app-extension";
180+
};
126181
/* End PBXNativeTarget section */
127182

128183
/* Begin PBXProject section */
@@ -136,6 +191,9 @@
136191
4F3ABD98293187EA00DB6023 = {
137192
CreatedOnToolsVersion = 13.2.1;
138193
};
194+
AA000001000000000000050 = {
195+
CreatedOnToolsVersion = 13.2.1;
196+
};
139197
};
140198
};
141199
buildConfigurationList = 4F3ABD94293187EA00DB6023 /* Build configuration list for PBXProject "ClickTrack" */;
@@ -152,6 +210,7 @@
152210
projectRoot = "";
153211
targets = (
154212
4F3ABD98293187EA00DB6023 /* ClickTrack */,
213+
AA000001000000000000050 /* ClickTrackLiveActivityExtension */,
155214
);
156215
};
157216
/* End PBXProject section */
@@ -170,6 +229,38 @@
170229
};
171230
/* End PBXResourcesBuildPhase section */
172231

232+
/* Begin PBXCopyFilesBuildPhase section */
233+
AA000001000000000000070 /* Embed App Extensions */ = {
234+
isa = PBXCopyFilesBuildPhase;
235+
buildActionMask = 2147483647;
236+
dstPath = "";
237+
dstSubfolderSpec = 13;
238+
files = (
239+
AA000001000000000000008 /* ClickTrackLiveActivityExtension.appex in Embed App Extensions */,
240+
);
241+
name = "Embed App Extensions";
242+
runOnlyForDeploymentPostprocessing = 0;
243+
};
244+
/* End PBXCopyFilesBuildPhase section */
245+
246+
/* Begin PBXTargetDependency section */
247+
AA000001000000000000080 /* PBXTargetDependency */ = {
248+
isa = PBXTargetDependency;
249+
target = AA000001000000000000050 /* ClickTrackLiveActivityExtension */;
250+
targetProxy = AA000001000000000000081 /* PBXContainerItemProxy */;
251+
};
252+
/* End PBXTargetDependency section */
253+
254+
/* Begin PBXContainerItemProxy section */
255+
AA000001000000000000081 /* PBXContainerItemProxy */ = {
256+
isa = PBXContainerItemProxy;
257+
containerPortal = 4F3ABD91293187EA00DB6023 /* Project object */;
258+
proxyType = 1;
259+
remoteGlobalIDString = AA000001000000000000050;
260+
remoteInfo = ClickTrackLiveActivityExtension;
261+
};
262+
/* End PBXContainerItemProxy section */
263+
173264
/* Begin PBXShellScriptBuildPhase section */
174265
3A4F6D87E60D21205A9161CB /* [CP] Copy Pods Resources */ = {
175266
isa = PBXShellScriptBuildPhase;
@@ -253,13 +344,36 @@
253344
};
254345
/* End PBXShellScriptBuildPhase section */
255346

347+
/* Begin PBXResourcesBuildPhase section (Extension) */
348+
AA000001000000000000032 /* Resources */ = {
349+
isa = PBXResourcesBuildPhase;
350+
buildActionMask = 2147483647;
351+
files = (
352+
);
353+
runOnlyForDeploymentPostprocessing = 0;
354+
};
355+
/* End PBXResourcesBuildPhase section (Extension) */
356+
256357
/* Begin PBXSourcesBuildPhase section */
257358
4F3ABD95293187EA00DB6023 /* Sources */ = {
258359
isa = PBXSourcesBuildPhase;
259360
buildActionMask = 2147483647;
260361
files = (
261362
4F3ABD9F293187EA00DB6023 /* ContentView.swift in Sources */,
262363
4F3ABD9D293187EA00DB6023 /* ClickTrackApp.swift in Sources */,
364+
AA000001000000000000001 /* LiveActivityBridge.swift in Sources */,
365+
AA000001000000000000002 /* LiveActivityNotification.swift in Sources */,
366+
AA000001000000000000003 /* ClickTrackActivityAttributes.swift in Sources */,
367+
);
368+
runOnlyForDeploymentPostprocessing = 0;
369+
};
370+
AA000001000000000000031 /* Sources */ = {
371+
isa = PBXSourcesBuildPhase;
372+
buildActionMask = 2147483647;
373+
files = (
374+
AA000001000000000000004 /* ClickTrackActivityAttributes.swift in Sources */,
375+
AA000001000000000000005 /* ClickTrackLiveActivityView.swift in Sources */,
376+
AA000001000000000000007 /* ClickTrackLiveActivityBundle.swift in Sources */,
263377
);
264378
runOnlyForDeploymentPostprocessing = 0;
265379
};
@@ -461,6 +575,50 @@
461575
};
462576
name = Release;
463577
};
578+
AA000001000000000000061 /* Debug */ = {
579+
isa = XCBuildConfiguration;
580+
buildSettings = {
581+
CODE_SIGN_STYLE = Automatic;
582+
DEVELOPMENT_TEAM = 655VV3W7RL;
583+
GENERATE_INFOPLIST_FILE = NO;
584+
INFOPLIST_FILE = ClickTrackLiveActivity/Info.plist;
585+
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
586+
LD_RUNPATH_SEARCH_PATHS = (
587+
"$(inherited)",
588+
"@executable_path/Frameworks",
589+
"@executable_path/../../Frameworks",
590+
);
591+
PRODUCT_BUNDLE_IDENTIFIER = "com.vsevolodganin.clicktrack.liveactivity";
592+
PRODUCT_NAME = ClickTrackLiveActivityExtension;
593+
SKIP_INSTALL = YES;
594+
SWIFT_VERSION = 5.0;
595+
TARGETED_DEVICE_FAMILY = "1,2";
596+
};
597+
name = Debug;
598+
};
599+
AA000001000000000000062 /* Release */ = {
600+
isa = XCBuildConfiguration;
601+
buildSettings = {
602+
CODE_SIGN_STYLE = Automatic;
603+
DEVELOPMENT_TEAM = 655VV3W7RL;
604+
GENERATE_INFOPLIST_FILE = NO;
605+
INFOPLIST_FILE = ClickTrackLiveActivity/Info.plist;
606+
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
607+
LD_RUNPATH_SEARCH_PATHS = (
608+
"$(inherited)",
609+
"@executable_path/Frameworks",
610+
"@executable_path/../../Frameworks",
611+
);
612+
PRODUCT_BUNDLE_IDENTIFIER = "com.vsevolodganin.clicktrack.liveactivity";
613+
PRODUCT_NAME = ClickTrackLiveActivityExtension;
614+
SKIP_INSTALL = YES;
615+
SWIFT_COMPILATION_MODE = wholemodule;
616+
SWIFT_OPTIMIZATION_LEVEL = "-O";
617+
SWIFT_VERSION = 5.0;
618+
TARGETED_DEVICE_FAMILY = "1,2";
619+
};
620+
name = Release;
621+
};
464622
/* End XCBuildConfiguration section */
465623

466624
/* Begin XCConfigurationList section */
@@ -482,6 +640,15 @@
482640
defaultConfigurationIsVisible = 0;
483641
defaultConfigurationName = Release;
484642
};
643+
AA000001000000000000060 /* Build configuration list for PBXNativeTarget "ClickTrackLiveActivityExtension" */ = {
644+
isa = XCConfigurationList;
645+
buildConfigurations = (
646+
AA000001000000000000061 /* Debug */,
647+
AA000001000000000000062 /* Release */,
648+
);
649+
defaultConfigurationIsVisible = 0;
650+
defaultConfigurationName = Release;
651+
};
485652
/* End XCConfigurationList section */
486653
};
487654
rootObject = 4F3ABD91293187EA00DB6023 /* Project object */;

ios-app/ClickTrack/ClickTrackApp.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ struct ClickTrackApp: App {
99
var appDelegate: AppDelegate
1010

1111
private let applicationComponent = MainKt.createApplicationComponent()
12+
private let audioSessionNotification: any AudioSessionNotification = makeAudioSessionNotification()
1213

1314
var body: some Scene {
1415
WindowGroup {
1516
ContentView(
1617
applicationComponent: applicationComponent,
17-
decomposeComponentContext: ComponentContextKt.createComponentContext(stateKeeper: appDelegate.stateKeeper)
18+
decomposeComponentContext: ComponentContextKt.createComponentContext(stateKeeper: appDelegate.stateKeeper),
19+
audioSessionNotification: audioSessionNotification
1820
)
21+
.onOpenURL { url in
22+
(audioSessionNotification as? PlayerURLHandler)?.handlePlayerURL(url)
23+
}
1924
}
2025
}
2126
}
@@ -30,7 +35,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
3035
) -> Bool {
3136
FirebaseApp.configure()
3237
return true
33-
}
38+
}
3439

3540
func application(_ application: UIApplication, shouldSaveSecureApplicationState coder: NSCoder) -> Bool {
3641
StateKeeperUtilsKt.save(coder: coder, state: stateKeeper.save())

ios-app/ClickTrack/ContentView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ struct ContentView: UIViewControllerRepresentable {
55

66
let applicationComponent: ApplicationComponent
77
let decomposeComponentContext: DecomposeComponentContext
8+
let audioSessionNotification: any AudioSessionNotification
89

910
func makeUIViewController(context: Context) -> some UIViewController {
1011
return MainKt.createMainViewController(
1112
applicationComponent: applicationComponent,
1213
componentContext: decomposeComponentContext,
14+
audioSessionNotification: audioSessionNotification,
1315
)
1416
}
1517

ios-app/ClickTrack/Info.plist

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,16 @@
1313
<array>
1414
<string>audio</string>
1515
</array>
16+
<key>NSSupportsLiveActivities</key>
17+
<true/>
18+
<key>CFBundleURLTypes</key>
19+
<array>
20+
<dict>
21+
<key>CFBundleURLSchemes</key>
22+
<array>
23+
<string>clicktrack</string>
24+
</array>
25+
</dict>
26+
</array>
1627
</dict>
1728
</plist>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import ActivityKit
2+
import Foundation
3+
4+
/// Manages the Live Activity lifecycle from the main app side.
5+
/// Kotlin calls into `LiveActivityNotification` which delegates here.
6+
/// Button taps in the Live Activity send `clicktrack://player/{action}` URLs
7+
/// back to the main app, handled in `ClickTrackApp.swift`.
8+
@objc public class LiveActivityBridge: NSObject {
9+
@objc public static let shared = LiveActivityBridge()
10+
11+
private var currentActivity: Any? // Activity<ClickTrackActivityAttributes>
12+
13+
private override init() {}
14+
15+
@available(iOS 16.1, *)
16+
func show(title: String, contentText: String, isPaused: Bool) {
17+
let state = ClickTrackActivityAttributes.ContentState(
18+
contentText: contentText,
19+
isPaused: isPaused
20+
)
21+
if let activity = currentActivity as? Activity<ClickTrackActivityAttributes> {
22+
Task {
23+
await activity.update(using: state)
24+
}
25+
} else {
26+
let attributes = ClickTrackActivityAttributes(title: title)
27+
do {
28+
let activity = try Activity<ClickTrackActivityAttributes>.request(
29+
attributes: attributes,
30+
contentState: state,
31+
pushType: nil
32+
)
33+
currentActivity = activity
34+
} catch {
35+
// Live Activities not supported or denied — silently ignore
36+
}
37+
}
38+
}
39+
40+
@available(iOS 16.1, *)
41+
func hide() {
42+
guard let activity = currentActivity as? Activity<ClickTrackActivityAttributes> else { return }
43+
Task {
44+
await activity.end(using: nil, dismissalPolicy: .immediate)
45+
}
46+
currentActivity = nil
47+
}
48+
}

0 commit comments

Comments
 (0)