Skip to content

Conversation

ipavlidakis
Copy link
Contributor

@ipavlidakis ipavlidakis commented Aug 19, 2025

🔗 Issue Links

Resolves https://linear.app/stream/issue/IOS-1080/recording-permission-prompt

Docs link

🎯 Goal

Prompt user when they haven't accepted the microphone access permission.

🛠 Implementation

This revision centralise permission read across the SDK. The new PermissionStore component is exposed via InjectedValues[.permissions] and offers access and reactivity around microphone, camera and push notifications permissions.

🎨 Showcase

Before After
img Simulator Screenshot - iPhone 16 Pro - 2025-08-26 at 12 39 41

🧪 Manual Testing Notes

  • From settings disable either/or microphone and camera access
  • Run the app
  • Confirm you are seeing the prompt
  • Interact with it
  • Confirm it works as expected

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change follows zero ⚠️ policy (required)
  • This change should receive manual QA
  • Changelog is updated with client-facing changes
  • New code is covered by unit tests
  • Comparison screenshots added for visual changes
  • Affected documentation updated (tutorial, CMS)

@ipavlidakis ipavlidakis self-assigned this Aug 19, 2025
@ipavlidakis ipavlidakis added the enhancement New feature or request label Aug 19, 2025
Copy link

github-actions bot commented Aug 19, 2025

1 Warning
⚠️ Big PR

Generated by 🚫 Danger

Copy link

github-actions bot commented Aug 19, 2025

Public Interface

+ extension PermissionStore: InjectionKey  
+ 
+   nonisolated public static var currentValue: PermissionStore

+ public final class PermissionStore: ObservableObject, @unchecked Sendable  
+ 
+   @Published public private var hasMicrophonePermission: Bool
+   @Published public private var hasCameraPermission: Bool
+   
+ 
+   public func requestMicrophonePermission()async throws -> Bool
+   public func requestCameraPermission()async throws -> Bool
+   public func requestPushNotificationPermission(with options: UNAuthorizationOptions)async throws -> Bool

+ public protocol URLNavigating

+ public enum Permission: Equatable, Sendable, CustomStringConvertible  
+ 
+   case unknown
+   case requesting
+   case denied
+   case granted
+   
+ 
+   public var description: String

+ public enum StoreAction: Sendable, Equatable  
+ 
+   case setMicrophonePermission(Permission)
+   case requestMicrophonePermission
+   case setCameraPermission(Permission)
+   case requestCameraPermission
+   case setPushNotificationPermission(Permission)
+   case requestPushNotificationPermission(UNAuthorizationOptions)

+ public struct StoreState: Equatable, CustomStringConvertible  
+ 
+   public var microphonePermission: Permission
+   public var cameraPermission: Permission
+   public var pushNotificationPermission: Permission
+   public var description: String

+ public struct ToggleControlStyle  
+ 
+   public var enabled: ControlStyle
+   public var disabled: ControlStyle
+   
+ 
+   public init(enabled: ControlStyle,disabled: ControlStyle)

+ public struct PermissionsPromptView: View  
+ 
+   public var body: some View
+   
+ 
+   public init(call: Call?)

+ public struct ControlStyle  
+ 
+   public var icon: Image
+   public var iconStyle: CallIconStyle
+   
+ 
+   public init(icon: Image,iconStyle: CallIconStyle)



 extension ViewFactory  
+   public func makePermissionsPromptView(call: Call?)-> some View

 public struct StatelessMicrophoneIconView: View  
-   public var body: some View
+   public var controlStyle: ToggleControlStyle
-   
+   public var body: some View
- 
+   
-   @MainActor public init(call: Call?,size: CGFloat = 44,actionHandler: ActionHandler? = nil)
+ 
+   @MainActor public init(call: Call?,callSettings: CallSettings = .init(),size: CGFloat = 44,controlStyle: ToggleControlStyle = .init(
+             enabled: .init(icon: Appearance.default.images.micTurnOn, iconStyle: .transparent),
+             disabled: .init(icon: Appearance.default.images.micTurnOff, iconStyle: .disabled)
+         ),actionHandler: ActionHandler? = nil)

 extension InjectedValues  
-   public var pictureInPictureAdapter: StreamPictureInPictureAdapter
+   public var urlNavigator: URLNavigating

 public struct StatelessVideoIconView: View  
-   public var body: some View
+   public var controlStyle: ToggleControlStyle
-   
+   public var body: some View
- 
+   
-   public init(call: Call?,size: CGFloat = 44,actionHandler: ActionHandler? = nil)
+ 
+   public init(call: Call?,callSettings: CallSettings = .init(),size: CGFloat = 44,controlStyle: ToggleControlStyle = .init(
+             enabled: .init(icon: Appearance.default.images.videoTurnOn, iconStyle: .transparent),
+             disabled: .init(icon: Appearance.default.images.videoTurnOff, iconStyle: .disabled)
+         ),actionHandler: ActionHandler? = nil)

 public struct CallTopView: View  
-   public init(viewModel: CallViewModel)
+   public init(viewFactory: Factory = DefaultViewFactory.shared,viewModel: CallViewModel)

 public struct ControlBadgeView: View  
-   public var body: some View
+   case text(String, foreground: Color, background: Color)
-   
+   case image(Image, foreground: Color, background: Color)
- 
+   
-   public init(_ value: String)
+ 
+   public var body: some View
+   
+ 
+   public init(_ value: String,foreground: Color = InjectedValues[\.colors].textInverted,background: Color = InjectedValues[\.colors].onlineIndicatorColor)
+   public init(_ image: Image,foreground: Color = InjectedValues[\.colors].textInverted,background: Color = InjectedValues[\.colors].onlineIndicatorColor)

@Stream-SDK-Bot
Copy link
Collaborator

Stream-SDK-Bot commented Aug 19, 2025

SDK Size

title develop branch diff status
StreamVideo 8.43 MB 8.48 MB +43 KB 🟢
StreamVideoSwiftUI 2.29 MB 2.36 MB +67 KB 🟢
StreamVideoUIKit 2.41 MB 2.48 MB +68 KB 🟢
StreamWebRTC 9.85 MB 9.85 MB 0 KB 🟢

@ipavlidakis ipavlidakis force-pushed the enhancement/recording-permission-prompt branch 3 times, most recently from b08dc1a to c7ed68c Compare August 20, 2025 16:08
@@ -380,15 +381,23 @@ public struct VideoCallParticipantView<Factory: ViewFactory>: View {
}

private var showVideo: Bool {
participant.shouldDisplayTrack || customData["videoOn"]?.boolValue == true
if isLocalParticipant {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually improving the logic on showing the placeholder (user avatar) when video is off for localparticipant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we merge the customData change? Wasn't this handled there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from #921 was initially in this branch and had reference to it. I was waiting for a decision on #921 to know if i should rebase or revert. I have now updated this PR with develop.

@ipavlidakis ipavlidakis force-pushed the enhancement/recording-permission-prompt branch from c7ed68c to ea3a174 Compare August 26, 2025 09:36
@ipavlidakis ipavlidakis marked this pull request as ready for review August 26, 2025 09:42
@ipavlidakis ipavlidakis requested a review from a team as a code owner August 26, 2025 09:42
Copy link
Contributor

@martinmitrevski martinmitrevski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Left few small comments, most important is to check if we consider the case where we don't need camera permission (maybe we do, but might've missed it).

@@ -380,15 +381,23 @@ public struct VideoCallParticipantView<Factory: ViewFactory>: View {
}

private var showVideo: Bool {
participant.shouldDisplayTrack || customData["videoOn"]?.boolValue == true
if isLocalParticipant {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we merge the customData change? Wasn't this handled there?

@ipavlidakis ipavlidakis force-pushed the enhancement/recording-permission-prompt branch from 97af53f to ca9c696 Compare August 27, 2025 11:57
@ipavlidakis ipavlidakis force-pushed the enhancement/recording-permission-prompt branch from 3b89b1c to 1a44cba Compare August 27, 2025 14:45
Copy link

@ipavlidakis ipavlidakis merged commit 1a37c2c into develop Aug 28, 2025
12 checks passed
@ipavlidakis ipavlidakis deleted the enhancement/recording-permission-prompt branch August 28, 2025 10:44
@ipavlidakis ipavlidakis mentioned this pull request Sep 1, 2025
@Stream-SDK-Bot Stream-SDK-Bot mentioned this pull request Sep 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants