Skip to content

Commit 037afb2

Browse files
authored
[Enhancement]Remove IdleTimer handling from UI (#853)
1 parent a9a57d4 commit 037afb2

File tree

6 files changed

+120
-6
lines changed

6 files changed

+120
-6
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
44

55
# Upcoming
66

7+
### ✅ Added
8+
- `UIApplication.shared.isIdleTimerDisabled` handling is now happening on the SDK, removing the need to do it on UI level. [#853](https://github.com/GetStream/stream-video-swift/pull/853)
9+
710
### 🔄 Changed
811
- Improved behavior in bad-network conditions. [#852](https://github.com/GetStream/stream-video-swift/pull/852)
912

Sources/StreamVideo/StreamVideo.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
102102
private let environment: Environment
103103
private let pushNotificationsConfig: PushNotificationsConfig
104104

105+
private lazy var idleTimerAdapter = IdleTimerAdapter(self)
106+
105107
/// Initializes a new instance of `StreamVideo` with the specified parameters.
106108
/// - Parameters:
107109
/// - apiKey: The API key.
@@ -195,6 +197,7 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
195197

196198
// Warm up
197199
_ = eventNotificationCenter
200+
_ = idleTimerAdapter
198201

199202
if user.type != .anonymous {
200203
let userAuth = UserAuth { [weak self] in
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
#if canImport(UIKit)
7+
import UIKit
8+
#endif
9+
10+
final class IdleTimerAdapter: @unchecked Sendable {
11+
12+
private(set) var isIdleTimerDisabled: Bool = false
13+
private let disposableBag = DisposableBag()
14+
15+
init(_ activeCallProvider: StreamActiveCallProviding) {
16+
#if canImport(UIKit)
17+
Task { @MainActor in
18+
self.isIdleTimerDisabled = UIApplication.shared.isIdleTimerDisabled
19+
}
20+
activeCallProvider
21+
.hasActiveCallPublisher
22+
.sinkTask(storeIn: disposableBag) { @MainActor [weak self] in self?.didUpdate(hasActiveCall: $0) }
23+
.store(in: disposableBag)
24+
#endif
25+
}
26+
27+
// MARK: - Private helpers
28+
29+
@MainActor
30+
private func didUpdate(hasActiveCall: Bool) {
31+
#if canImport(UIKit)
32+
UIApplication.shared.isIdleTimerDisabled = hasActiveCall
33+
isIdleTimerDisabled = hasActiveCall
34+
#endif
35+
}
36+
}

Sources/StreamVideoSwiftUI/CallView/CallView.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,6 @@ public struct CallView<Factory: ViewFactory>: View {
4343
}
4444
.background(Color(colors.callBackground).edgesIgnoringSafeArea(.all))
4545
.frame(maxWidth: .infinity, maxHeight: .infinity)
46-
.onAppear {
47-
UIApplication.shared.isIdleTimerDisabled = true
48-
}
49-
.onDisappear {
50-
UIApplication.shared.isIdleTimerDisabled = false
51-
}
5246
.enablePictureInPicture(viewModel.isPictureInPictureEnabled)
5347
.presentParticipantListView(viewModel: viewModel, viewFactory: viewFactory)
5448
}

StreamVideo.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@
251251
406303422AD848000091AE77 /* CallParticipant_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406303412AD848000091AE77 /* CallParticipant_Mock.swift */; };
252252
406303462AD9432D0091AE77 /* GoogleSignInSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 406303442AD942ED0091AE77 /* GoogleSignInSwift */; };
253253
406303472AD943B60091AE77 /* GoogleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844ADA682AD3F78F00769F6A /* GoogleHelper.swift */; };
254+
406568872E0426FD00A67EAC /* IdleTimerAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406568862E0426FD00A67EAC /* IdleTimerAdapter.swift */; };
255+
4065688A2E04275F00A67EAC /* IdleTimerAdapter_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406568892E04275F00A67EAC /* IdleTimerAdapter_Tests.swift */; };
254256
4065688B2E042B2E00A67EAC /* MockStreamCallAudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406B3C0D2C8F865F00FC93A1 /* MockStreamCallAudioRecorder.swift */; };
255257
4065838A2B87695500B4F979 /* BlurBackgroundVideoFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406583852B87694B00B4F979 /* BlurBackgroundVideoFilter.swift */; };
256258
4065838B2B87695500B4F979 /* VideoFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406583872B87694B00B4F979 /* VideoFilters.swift */; };
@@ -1832,6 +1834,8 @@
18321834
4061288A2CF33088007F5CDC /* SupportedPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedPrefix.swift; sourceTree = "<group>"; };
18331835
4063033E2AD847EC0091AE77 /* CallState_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallState_Tests.swift; sourceTree = "<group>"; };
18341836
406303412AD848000091AE77 /* CallParticipant_Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallParticipant_Mock.swift; sourceTree = "<group>"; };
1837+
406568862E0426FD00A67EAC /* IdleTimerAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdleTimerAdapter.swift; sourceTree = "<group>"; };
1838+
406568892E04275F00A67EAC /* IdleTimerAdapter_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdleTimerAdapter_Tests.swift; sourceTree = "<group>"; };
18351839
406583852B87694B00B4F979 /* BlurBackgroundVideoFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurBackgroundVideoFilter.swift; sourceTree = "<group>"; };
18361840
406583872B87694B00B4F979 /* VideoFilters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoFilters.swift; sourceTree = "<group>"; };
18371841
4065838E2B877A0000B4F979 /* ImageBackgroundVideoFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageBackgroundVideoFilter.swift; sourceTree = "<group>"; };
@@ -3693,6 +3697,22 @@
36933697
path = CallState;
36943698
sourceTree = "<group>";
36953699
};
3700+
406568852E0426F600A67EAC /* IdleTimerAdapter */ = {
3701+
isa = PBXGroup;
3702+
children = (
3703+
406568862E0426FD00A67EAC /* IdleTimerAdapter.swift */,
3704+
);
3705+
path = IdleTimerAdapter;
3706+
sourceTree = "<group>";
3707+
};
3708+
406568882E04275700A67EAC /* IdleTimerAdapter */ = {
3709+
isa = PBXGroup;
3710+
children = (
3711+
406568892E04275F00A67EAC /* IdleTimerAdapter_Tests.swift */,
3712+
);
3713+
path = IdleTimerAdapter;
3714+
sourceTree = "<group>";
3715+
};
36963716
406583832B87694B00B4F979 /* VideoFilters */ = {
36973717
isa = PBXGroup;
36983718
children = (
@@ -5585,6 +5605,7 @@
55855605
842747F429EEDACB00E063AD /* Utils */ = {
55865606
isa = PBXGroup;
55875607
children = (
5608+
406568882E04275700A67EAC /* IdleTimerAdapter */,
55885609
40FAAC872DDC9F83007BF93A /* ConsumableBucket */,
55895610
40FAAC852DDC9B2D007BF93A /* AnyEncodable.swift */,
55905611
40B3E5392DBBAF8B00DE8F50 /* Proximity */,
@@ -6092,6 +6113,7 @@
60926113
84AF64D3287C79220012A503 /* Utils */ = {
60936114
isa = PBXGroup;
60946115
children = (
6116+
406568852E0426F600A67EAC /* IdleTimerAdapter */,
60956117
404098C72DDF45DA00D7BEC5 /* SelectiveEncodable */,
60966118
4028FEA72DC536BB001F9DC3 /* AnyEncodable.swift */,
60976119
4028FE962DC4F62A001F9DC3 /* ConsumableBucket */,
@@ -7612,6 +7634,7 @@
76127634
84DC38DB29ADFCFD00946713 /* JSONDataEncoding.swift in Sources */,
76137635
40FB15112BF77D5800D5E580 /* StreamStateMachineStage.swift in Sources */,
76147636
8496A9A629CC500F00F15FF1 /* StreamVideoCaptureHandler.swift in Sources */,
7637+
406568872E0426FD00A67EAC /* IdleTimerAdapter.swift in Sources */,
76157638
84CD12162C73831000056640 /* CallRtmpBroadcastStartedEvent.swift in Sources */,
76167639
8411925E28C5E5D00074EF88 /* RTCConfiguration+Default.swift in Sources */,
76177640
8409465929AF4EEC007AF5BF /* SendReactionResponse.swift in Sources */,
@@ -8208,6 +8231,7 @@
82088231
40382F3D2C89C11D00C2D00F /* MockRTCPeerConnectionCoordinatorFactory.swift in Sources */,
82098232
4013A8EF2D81E98C00F81C15 /* WebRTCCoordinatorStateMachine_BlockedStageTests.swift in Sources */,
82108233
40AB31262A49838000C270E1 /* EventTests.swift in Sources */,
8234+
4065688A2E04275F00A67EAC /* IdleTimerAdapter_Tests.swift in Sources */,
82118235
84F58B7C29EE979F00010C4C /* VirtualTime.swift in Sources */,
82128236
40B3E5492DBBD2CA00DE8F50 /* SpeakerProximityPolicy_Tests.swift in Sources */,
82138237
40F0173E2BBEB86800E89FD1 /* TestsAuthenticationProvider.swift in Sources */,
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
@testable import StreamVideo
7+
import XCTest
8+
9+
final class IdleTimerAdapter_Tests: XCTestCase, @unchecked Sendable {
10+
11+
private var mockActiveCallProvider: MockActiveCallProvider! = .init()
12+
private lazy var subject: IdleTimerAdapter! = .init(mockActiveCallProvider)
13+
14+
override func setUp() {
15+
super.setUp()
16+
_ = subject
17+
}
18+
19+
override func tearDown() {
20+
subject = nil
21+
mockActiveCallProvider = nil
22+
super.tearDown()
23+
}
24+
25+
// MARK: - hasActiveCall
26+
27+
func test_hasActiveCall_isTrue_IdleTimerIsDisabled() async {
28+
mockActiveCallProvider.subject.send(true)
29+
30+
await fulfilmentInMainActor { self.subject.isIdleTimerDisabled == true }
31+
}
32+
33+
func test_hasActiveCall_isFalse_IdleTimerIsEnabled() async {
34+
mockActiveCallProvider.subject.send(false)
35+
36+
await fulfilmentInMainActor { self.subject.isIdleTimerDisabled == false }
37+
}
38+
39+
func test_hasActiveCall_changesFromFalseToTrue_firstIsEnabledThenDisabled() async {
40+
mockActiveCallProvider.subject.send(false)
41+
await fulfilmentInMainActor { self.subject.isIdleTimerDisabled == false }
42+
43+
mockActiveCallProvider.subject.send(true)
44+
await fulfilmentInMainActor { self.subject.isIdleTimerDisabled == true }
45+
}
46+
47+
func test_hasActiveCall_changesFromTrueToFalse_firstIsDisabledThenEnabled() async {
48+
mockActiveCallProvider.subject.send(true)
49+
await fulfilmentInMainActor { self.subject.isIdleTimerDisabled == true }
50+
51+
mockActiveCallProvider.subject.send(false)
52+
await fulfilmentInMainActor { self.subject.isIdleTimerDisabled == false }
53+
}
54+
}

0 commit comments

Comments
 (0)