diff --git a/DemoApp/Sources/Components/Stats/DemoVideoCallParticipantOptionsModifier.swift b/DemoApp/Sources/Components/Stats/DemoVideoCallParticipantOptionsModifier.swift index 5b6a04b5f..f5f61614f 100644 --- a/DemoApp/Sources/Components/Stats/DemoVideoCallParticipantOptionsModifier.swift +++ b/DemoApp/Sources/Components/Stats/DemoVideoCallParticipantOptionsModifier.swift @@ -56,7 +56,7 @@ public struct DemoVideoCallParticipantOptionsModifier: ViewModifier { Image(systemName: "ellipsis") .foregroundColor(.white) .padding(8) - .background(Color.black.opacity(0.6)) + .background(appearance.colors.participantInfoBackgroundColor) .clipShape(Circle()) } diff --git a/DemoApp/Sources/Models/Reaction.swift b/DemoApp/Sources/Models/Reaction.swift index 939b0a6ee..b3aa3e5c1 100644 --- a/DemoApp/Sources/Models/Reaction.swift +++ b/DemoApp/Sources/Models/Reaction.swift @@ -4,7 +4,7 @@ import Foundation -struct Reaction: Identifiable, Codable { +struct Reaction: Identifiable, Codable, Equatable { enum ID: String, Codable { case fireworks = ":fireworks:" diff --git a/DemoApp/Sources/Views/CallTopView/DemoCallTopView.swift b/DemoApp/Sources/Views/CallTopView/DemoCallTopView.swift index 218ec8477..c71f0dd76 100644 --- a/DemoApp/Sources/Views/CallTopView/DemoCallTopView.swift +++ b/DemoApp/Sources/Views/CallTopView/DemoCallTopView.swift @@ -23,68 +23,35 @@ struct DemoCallTopView: View { } var body: some View { - HStack { - Menu { - Button { - viewModel.toggleSpeaker() - } label: { - HStack { - Text("Speaker") - if viewModel.callSettings.speakerOn { - Image(systemName: "checkmark") - } - } - } - - Button { - if appState.audioFilter == nil { - appState.audioFilter = RobotVoiceFilter(pitchShift: 0.8) - } else { - appState.audioFilter = nil - } - } label: { - HStack { - Text("Robot voice") - if appState.audioFilter != nil { - Image(systemName: "checkmark") - } - } + HStack(spacing: 0) { + HStack { + if viewModel.callParticipants.count > 1 { + LayoutMenuView(viewModel: viewModel) + .opacity(hideLayoutMenu ? 0 : 1) + .accessibility(identifier: "viewMenu") } + ToggleCameraIconView(viewModel: viewModel) - reactionsList() - } label: { - Image(systemName: "ellipsis") - .foregroundColor(.white) - .font(fonts.bodyBold) - .padding() + Spacer() } + .frame(maxWidth: .infinity) - if viewModel.recordingState == .recording { - RecordingView() - .accessibility(identifier: "recordingLabel") + HStack(alignment: .center) { + CallDurationView(viewModel) } + .frame(height: 44) + .frame(maxWidth: .infinity) - Spacer() - - - if #available(iOS 14, *) { - HStack(spacing: 16) { - LayoutMenuView(viewModel: viewModel) - .opacity(hideLayoutMenu ? 0 : 1) - .accessibility(identifier: "viewMenu") - - Button { - viewModel.participantsShown.toggle() - } label: { - images.participants - .foregroundColor(.white) - } - .accessibility(identifier: "participantMenu") - } - .padding(.horizontal) + HStack { + Spacer() + HangUpIconView(viewModel: viewModel) } + .frame(maxWidth: .infinity) } + .padding(.horizontal, 16) + .padding(.top) + .frame(maxWidth: .infinity) .overlay( viewModel.call?.state.isCurrentUserScreensharing == true ? SharingIndicator( diff --git a/DemoApp/Sources/Views/CallView/DemoCallView.swift b/DemoApp/Sources/Views/CallView/DemoCallView.swift index a38acba97..eb8ac1475 100644 --- a/DemoApp/Sources/Views/CallView/DemoCallView.swift +++ b/DemoApp/Sources/Views/CallView/DemoCallView.swift @@ -14,6 +14,7 @@ struct DemoCallView: View { var microphoneChecker: MicrophoneChecker + @ObservedObject var appState: AppState = .shared @ObservedObject var viewModel: CallViewModel @ObservedObject var reactionsHelper: ReactionsHelper = AppState.shared.reactionsHelper @@ -75,6 +76,7 @@ struct DemoCallView: View { .onAppear { updateMicrophoneChecker() } + .presentsMoreControls(viewModel: viewModel) .chat(viewModel: viewModel, chatViewModel: chatViewModel) } diff --git a/DemoApp/Sources/Views/CallView/DemoChatModifier.swift b/DemoApp/Sources/Views/CallView/DemoChatModifier.swift index 19d11afc1..4d8554a5f 100644 --- a/DemoApp/Sources/Views/CallView/DemoChatModifier.swift +++ b/DemoApp/Sources/Views/CallView/DemoChatModifier.swift @@ -4,22 +4,45 @@ import Foundation import SwiftUI +import StreamVideo import StreamVideoSwiftUI struct ChatModifier: ViewModifier { + @Injected(\.images) var images + @Injected(\.fonts) var fonts + @Injected(\.colors) var colors + @ObservedObject var viewModel: CallViewModel @ObservedObject var chatViewModel: DemoChatViewModel func body(content: Content) -> some View { content - .halfSheetIfAvailable( - isPresented: $chatViewModel.isChatVisible, - onDismiss: {} + .sheet( + isPresented: $chatViewModel.isChatVisible ) { if let channelController = chatViewModel.channelController { - VStack { - ChatControlsHeader(viewModel: viewModel) + VStack(spacing: 0) { + VStack(alignment: .center) { + DragHandleView() + .padding(.top) + }.frame(maxWidth: .infinity) + + HStack { + Text("Chat") + .font(fonts.title3) + .fontWeight(.medium) + + Spacer() + + ModalButton(image: images.xmark) { + chatViewModel.isChatVisible = false + } + } + .foregroundColor(.white) + .padding(.bottom, 24) + .padding(.top) + .padding(.horizontal) ChatView( channelController: channelController, chatViewModel: chatViewModel, diff --git a/DemoApp/Sources/Views/CallView/DemoQRCodeScannerButton.swift b/DemoApp/Sources/Views/CallView/DemoQRCodeScannerButton.swift index c97f16460..e93076c05 100644 --- a/DemoApp/Sources/Views/CallView/DemoQRCodeScannerButton.swift +++ b/DemoApp/Sources/Views/CallView/DemoQRCodeScannerButton.swift @@ -34,7 +34,7 @@ struct DemoQRCodeScannerButton: View { .foregroundColor(.init(appearance.colors.textLowEmphasis)) } .padding(.trailing) - .halfSheetIfAvailable(isPresented: $isQRScannerPresented) { + .sheet(isPresented: $isQRScannerPresented) { CodeScannerView(codeTypes: [.qr]) { result in switch result { case .success(let scanResult): diff --git a/DemoApp/Sources/Views/Controls/DemoControls.swift b/DemoApp/Sources/Views/Controls/DemoControls.swift index 772bc0216..7c3c71e98 100644 --- a/DemoApp/Sources/Views/Controls/DemoControls.swift +++ b/DemoApp/Sources/Views/Controls/DemoControls.swift @@ -29,35 +29,64 @@ struct AppControlsWithChat: View { } var body: some View { - HStack(alignment: .center) { - if let chatViewModel, chatViewModel.isChatEnabled { - ChatIconView(viewModel: chatViewModel) + HStack { + if viewModel.callParticipants.count > 1 { + MoreControlsIconView(viewModel: viewModel) } - VideoIconView(viewModel: viewModel) - MicrophoneIconView(viewModel: viewModel) - ToggleCameraIconView(viewModel: viewModel) - #if !targetEnvironment(simulator) + +#if !targetEnvironment(simulator) if !ProcessInfo.processInfo.isiOSAppOnMac { BroadcastIconView( viewModel: viewModel, preferredExtension: "io.getstream.iOS.VideoDemoApp.ScreenSharing" ) } - #endif - HangUpIconView(viewModel: viewModel) +#endif + VideoIconView(viewModel: viewModel) + MicrophoneIconView(viewModel: viewModel) + + Spacer() + + ParticipantsListButton(viewModel: viewModel) + + if let chatViewModel, chatViewModel.isChatEnabled { + ChatIconView(viewModel: chatViewModel) + } } - .padding() - .frame(maxWidth: .infinity) - .cornerRadius( - cornerRadius, - corners: [.topLeft, .topRight], - backgroundColor: colors.callControlsBackground, - extendToSafeArea: true - ) + .padding(.horizontal, 16) + .padding(.bottom) .onReceive(viewModel.$call) { reactionsHelper.call = $0 } } } +struct MoreControlsIconView: View { + + @ObservedObject var viewModel: CallViewModel + let size: CGFloat + + init(viewModel: CallViewModel, size: CGFloat = 44) { + self.viewModel = viewModel + self.size = size + } + + var body: some View { + Button( + action: { + viewModel.moreControlsShown.toggle() + }, + label: { + CallIconView( + icon: Image(systemName: "ellipsis"), + size: size, + iconStyle: viewModel.moreControlsShown ? .secondaryActive : .secondary + ) + .rotationEffect(.degrees(90)) + } + ) + .accessibility(identifier: "moreControlsToggle") + } +} + struct ChatControlsHeader: View { @Injected(\.streamVideo) var streamVideo @@ -87,7 +116,7 @@ struct ChatIconView: View { @ObservedObject var viewModel: DemoChatViewModel let size: CGFloat - init(viewModel: DemoChatViewModel, size: CGFloat = 50) { + init(viewModel: DemoChatViewModel, size: CGFloat = 44) { self.viewModel = viewModel self.size = size } @@ -99,26 +128,12 @@ struct ChatIconView: View { }, label: { CallIconView( - icon: viewModel.isChatVisible ? .init(systemName: "message.fill") : .init(systemName: "message"), + icon: .init(systemName: "bubble.left.and.bubble.right.fill"), size: size, - iconStyle: viewModel.isChatVisible ? .primary : .transparent + iconStyle: viewModel.isChatVisible ? .secondaryActive : .secondary ).overlay( - VStack { - HStack { - Spacer() - if viewModel.unreadCount > 0 { - Text("\(viewModel.unreadCount)") - .font(.caption.monospacedDigit()) - .foregroundColor(colors.text) - .padding([.leading, .trailing], 4) - .padding([.top, .bottom], 2) - .background(Color.red) - .clipShape(Capsule()) - .clipped() - } - } - Spacer() - } + ControlBadgeView("\(viewModel.unreadCount)") + .opacity(viewModel.unreadCount > 0 ? 1 : 0) ) } ) @@ -144,23 +159,3 @@ struct ChatView: View { } } } - -extension View { - - @ViewBuilder - func halfSheetIfAvailable( - isPresented: Binding, - onDismiss: (() -> Void)? = nil, - @ViewBuilder content: @escaping () -> Content - ) -> some View where Content : View { - if #available(iOS 16.0, *) { - sheet(isPresented: isPresented, onDismiss: onDismiss) { - content() - .presentationDetents([.large]) - .presentationDragIndicator(.visible) - } - } else { - sheet(isPresented: isPresented, onDismiss: onDismiss) { content() } - } - } -} diff --git a/DemoApp/Sources/Views/Controls/DemoMoreControls/DemoMoreControlListButtonView.swift b/DemoApp/Sources/Views/Controls/DemoMoreControls/DemoMoreControlListButtonView.swift new file mode 100644 index 000000000..c2c79679d --- /dev/null +++ b/DemoApp/Sources/Views/Controls/DemoMoreControls/DemoMoreControlListButtonView.swift @@ -0,0 +1,78 @@ +// +// Copyright © 2024 Stream.io Inc. All rights reserved. +// + +import Foundation +import SwiftUI +import StreamVideo +import StreamVideoSwiftUI + +struct DemoMoreControlListButtonView: View { + + @Injected(\.colors) var colors + + var centered: Bool = false + var action: () -> Void + var label: String + var icon: () -> Image + + var body: some View { + Button { + action() + } label: { + HStack { + Label( + title: { Text(label) }, + icon: { icon() } + ) + + if !centered { + Spacer() + } + } + .frame(maxWidth: .infinity) + .padding(.horizontal) + } + .frame(height: 40) + .buttonStyle(.plain) + .background(Color(colors.participantBackground)) + .clipShape(Capsule()) + .frame(maxWidth: .infinity) + } +} + + +@MainActor +struct DemoRaiseHandToggleButtonView: View { + + @ObservedObject var reactionsHelper = AppState.shared.reactionsHelper + @ObservedObject var viewModel: CallViewModel + + init(viewModel: CallViewModel) { + self.viewModel = viewModel + } + + var body: some View { + DemoMoreControlListButtonView( + centered: true, + action: { reactionsHelper.send(reaction: .raiseHand) }, + label: currentUserHasRaisedHand ? "Lower Hand" : "Raise Hand" + ) { + Image( + systemName: currentUserHasRaisedHand + ? Reaction.lowerHand.iconName + : Reaction.raiseHand.iconName + ) + } + } + + private var currentUserHasRaisedHand: Bool { + guard let userId = viewModel.localParticipant?.userId else { + return false + } + + return reactionsHelper + .activeReactions[userId]? + .first(where: { $0.id == .raiseHand }) != nil + } +} diff --git a/DemoApp/Sources/Views/Controls/DemoMoreControls/DemoReactionButton.swift b/DemoApp/Sources/Views/Controls/DemoMoreControls/DemoReactionButton.swift new file mode 100644 index 000000000..aa5305e20 --- /dev/null +++ b/DemoApp/Sources/Views/Controls/DemoMoreControls/DemoReactionButton.swift @@ -0,0 +1,78 @@ +// +// Copyright © 2024 Stream.io Inc. All rights reserved. +// + +import Foundation +import SwiftUI +import StreamVideo +import StreamVideoSwiftUI + +struct DemoReactionSelectorView: View { + + var reactions: [Reaction] = [ + .like, + .fireworks, + .dislike, + .heart, + .hello + ] + + var body: some View { + + HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { + ForEach(reactions) { reaction in + DemoReactionButton(reaction: reaction) + } + } + } +} + +@MainActor +struct DemoReactionButton: View { + + @Injected(\.colors) var colors + + var reaction: Reaction + var reactionsHelper = AppState.shared.reactionsHelper + + var body: some View { + Button { + reactionsHelper.send(reaction: reaction) + } label: { + Circle() + .fill(Color(colors.participantBackground)) + .overlay( + reaction + .emojiView + .font(.body) + .aspectRatio(contentMode: .fit) + .padding(10) + ) + .frame(width: 44, height: 44) + } + .buttonStyle(.plain) + } +} + +extension Reaction { + + @ViewBuilder + var emojiView: some View { + switch self { + case .fireworks: + Text("🎉") + case .like: + Text("👍") + case .dislike: + Text("👎") + case .heart: + Text("❤️") + case .smile: + Text("😃") + case .hello: + Text("👋") + default: + EmptyView() + } + } +} diff --git a/DemoApp/Sources/Views/Login/LoginView.swift b/DemoApp/Sources/Views/Login/LoginView.swift index b280a11dd..4f1929372 100644 --- a/DemoApp/Sources/Views/Login/LoginView.swift +++ b/DemoApp/Sources/Views/Login/LoginView.swift @@ -96,7 +96,7 @@ struct LoginView: View { .sheet(isPresented: $addUserShown, onDismiss: {}) { AddUserView() } - .halfSheetIfAvailable(isPresented: $showJoinCallPopup) { + .halfSheet(isPresented: $showJoinCallPopup) { JoinCallView(viewModel: viewModel, completion: completion) } .navigationTitle("Select a user") diff --git a/DemoApp/Sources/Views/CallModifier/DemoCallModifier.swift b/DemoApp/Sources/Views/ViewModifiers/CallModifier/DemoCallModifier.swift similarity index 100% rename from DemoApp/Sources/Views/CallModifier/DemoCallModifier.swift rename to DemoApp/Sources/Views/ViewModifiers/CallModifier/DemoCallModifier.swift diff --git a/DemoApp/Sources/Views/ChangeEnvironmentViewModifier/ChangeEnvironmentViewModifier.swift b/DemoApp/Sources/Views/ViewModifiers/ChangeEnvironmentViewModifier/ChangeEnvironmentViewModifier.swift similarity index 100% rename from DemoApp/Sources/Views/ChangeEnvironmentViewModifier/ChangeEnvironmentViewModifier.swift rename to DemoApp/Sources/Views/ViewModifiers/ChangeEnvironmentViewModifier/ChangeEnvironmentViewModifier.swift diff --git a/DemoApp/Sources/Views/LongPressToFocusViewModifier/LongPressToFocusViewModifier.swift b/DemoApp/Sources/Views/ViewModifiers/LongPressToFocusViewModifier/LongPressToFocusViewModifier.swift similarity index 100% rename from DemoApp/Sources/Views/LongPressToFocusViewModifier/LongPressToFocusViewModifier.swift rename to DemoApp/Sources/Views/ViewModifiers/LongPressToFocusViewModifier/LongPressToFocusViewModifier.swift diff --git a/DemoApp/Sources/Views/ViewModifiers/MoreControls/DemoMoreControlsViewModifier.swift b/DemoApp/Sources/Views/ViewModifiers/MoreControls/DemoMoreControlsViewModifier.swift new file mode 100644 index 000000000..98a868d51 --- /dev/null +++ b/DemoApp/Sources/Views/ViewModifiers/MoreControls/DemoMoreControlsViewModifier.swift @@ -0,0 +1,59 @@ +// +// Copyright © 2024 Stream.io Inc. All rights reserved. +// + +import Foundation +import SwiftUI +import StreamVideoSwiftUI + +fileprivate struct DemoMoreControlsViewModifier: ViewModifier { + + @ObservedObject var appState: AppState = .shared + @ObservedObject var viewModel: CallViewModel + + fileprivate func body(content: Content) -> some View { + content + .halfSheet(isPresented: $viewModel.moreControlsShown) { + ScrollView(showsIndicators: false) { + VStack(spacing: 32) { + VStack { + DemoReactionSelectorView() + DemoRaiseHandToggleButtonView(viewModel: viewModel) + } + + VStack { + DemoMoreControlListButtonView( + action: { viewModel.toggleSpeaker() }, + label: viewModel.callSettings.speakerOn ? "Disable Speaker" : "Speaker" + ) { Image( + systemName: viewModel.callSettings.speakerOn + ? "speaker.wave.3.fill" + : "speaker.fill" + ) + } + + DemoMoreControlListButtonView( + action: { + if appState.audioFilter == nil { + appState.audioFilter = RobotVoiceFilter(pitchShift: 0.8) + } else { + appState.audioFilter = nil + } + }, + label: appState.audioFilter == nil ? "Robot Voice" : "Disable Robot Voice" + ) { Image(systemName: "waveform") } + } + } + } + .padding(.horizontal) + } + } +} + +extension View { + + @ViewBuilder + func presentsMoreControls(viewModel: CallViewModel) -> some View { + modifier(DemoMoreControlsViewModifier(viewModel: viewModel)) + } +} diff --git a/DemoApp/Sources/Views/ThermalStateViewModifier/ThermalStateViewModifier.swift b/DemoApp/Sources/Views/ViewModifiers/ThermalStateViewModifier/ThermalStateViewModifier.swift similarity index 100% rename from DemoApp/Sources/Views/ThermalStateViewModifier/ThermalStateViewModifier.swift rename to DemoApp/Sources/Views/ViewModifiers/ThermalStateViewModifier/ThermalStateViewModifier.swift diff --git a/DemoApp/Sources/Views/VideoCallParticipantModifier/DemoLocalViewModifier.swift b/DemoApp/Sources/Views/ViewModifiers/VideoCallParticipantModifier/DemoLocalViewModifier.swift similarity index 100% rename from DemoApp/Sources/Views/VideoCallParticipantModifier/DemoLocalViewModifier.swift rename to DemoApp/Sources/Views/ViewModifiers/VideoCallParticipantModifier/DemoLocalViewModifier.swift diff --git a/DemoApp/Sources/Views/VideoCallParticipantModifier/DemoVideoCallParticipantModifier.swift b/DemoApp/Sources/Views/ViewModifiers/VideoCallParticipantModifier/DemoVideoCallParticipantModifier.swift similarity index 100% rename from DemoApp/Sources/Views/VideoCallParticipantModifier/DemoVideoCallParticipantModifier.swift rename to DemoApp/Sources/Views/ViewModifiers/VideoCallParticipantModifier/DemoVideoCallParticipantModifier.swift diff --git a/DemoApp/Sources/Views/WaitingLocalUserView/DemoWaitingLocalUserView.swift b/DemoApp/Sources/Views/WaitingLocalUserView/DemoWaitingLocalUserView.swift index de0259651..f12c8b611 100644 --- a/DemoApp/Sources/Views/WaitingLocalUserView/DemoWaitingLocalUserView.swift +++ b/DemoApp/Sources/Views/WaitingLocalUserView/DemoWaitingLocalUserView.swift @@ -15,6 +15,8 @@ struct DemoWaitingLocalUserView: View { @State private var isSharePresented = false @State private var isChatVisible = false + @State private var isSharePromptVisible = true + @State private var isInviteViewVisible = false private let viewFactory: Factory @@ -27,85 +29,139 @@ struct DemoWaitingLocalUserView: View { } var body: some View { - ZStack { - DefaultBackgroundGradient() - .edgesIgnoringSafeArea(.all) - - VStack(spacing: 16) { - VStack { - if let localParticipant = viewModel.localParticipant { - GeometryReader { proxy in - LocalVideoView( - viewFactory: viewFactory, - participant: localParticipant, - idSuffix: "waiting", - callSettings: viewModel.callSettings, - call: viewModel.call, - availableFrame: proxy.frame(in: .local) - ) - .modifier(viewFactory.makeLocalParticipantViewModifier( - localParticipant: localParticipant, - callSettings: .init(get: { viewModel.callSettings }, set: { _ in }), - call: viewModel.call - )) - } - } else { - Spacer() + VStack { + viewFactory.makeCallTopView(viewModel: viewModel) + + Group { + if let localParticipant = viewModel.localParticipant { + GeometryReader { proxy in + LocalVideoView( + viewFactory: viewFactory, + participant: localParticipant, + idSuffix: "waiting", + callSettings: viewModel.callSettings, + call: viewModel.call, + availableFrame: proxy.frame(in: .local) + ) + .modifier(viewFactory.makeLocalParticipantViewModifier( + localParticipant: localParticipant, + callSettings: .init(get: { viewModel.callSettings }, set: { _ in }), + call: viewModel.call + )) } + .overlay(sharePromptView) + } else { + Spacer() + } + } + .padding(.horizontal) - ZStack { - Color(appearance.colors.participantBackground) + viewFactory.makeCallControlsView(viewModel: viewModel) + } + .chat(viewModel: viewModel, chatViewModel: chatViewModel) + .background(Color(appearance.colors.callBackground).edgesIgnoringSafeArea(.all)) + } - VStack { - ZStack { - Circle() - .fill(appearance.colors.lightGray) + @ViewBuilder + private var sharePromptView: some View { + if isSharePromptVisible { + VStack { + Spacer() - Image(systemName: "person.fill.badge.plus") - } - .frame(maxHeight: 50) + Group { + VStack(spacing: 16) { + HStack { + Text("Your Meeting is live!") - Text("Share link to add participants") - .font(appearance.fonts.body.weight(.medium)) + Spacer() Button { - isSharePresented = true + isSharePromptVisible = false } label: { - Image(systemName: "square.and.arrow.up") - Text("Share") + Text(Image(systemName: "xmark")) } - .padding([.leading, .trailing]) - .padding([.top, .bottom], 4) - .background(appearance.colors.primaryButtonBackground) - .clipShape(Capsule()) - .sheet(isPresented: $isSharePresented) { - if let url = URL(string: callLink) { - ShareActivityView(activityItems: [url]) - } else { - EmptyView() + } + .foregroundColor(appearance.colors.text) + .font(appearance.fonts.title3.bold()) + + Button { + isInviteViewVisible = true + } label: { + HStack { + Label( + title: { Text("Add Others") }, + icon: { Image(systemName: "person.fill.badge.plus") } + ) + } + .frame(maxWidth: .infinity) + .padding(.horizontal) + } + .frame(height: 40) + .buttonStyle(.plain) + .foregroundColor(appearance.colors.text) + .background(appearance.colors.accentBlue) + .clipShape(Capsule()) + .frame(maxWidth: .infinity) + + Text("Or share this call ID with the others you want in the meeting") + .font(.body) + .foregroundColor(Color(appearance.colors.textLowEmphasis)) + .frame(maxWidth: .infinity, alignment: .leading) + + if !callId.isEmpty { + HStack { + Text("Call ID:") + .foregroundColor(Color(appearance.colors.textLowEmphasis)) + + Button { + UIPasteboard.general.string = callLink + } label: { + HStack { + + Text("\(callId)") + .foregroundColor(appearance.colors.onlineIndicatorColor) + .lineLimit(1) + .minimumScaleFactor(0.5) + + Text(Image(systemName: "doc.on.clipboard")) + .foregroundColor(Color(appearance.colors.textLowEmphasis)) + + Spacer() + } } + .buttonStyle(.plain) } + .font(.body) } } - .cornerRadius(16) - .foregroundColor(Color.white) + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } + .background(Color(appearance.colors.participantBackground)) + .clipShape(RoundedRectangle(cornerRadius: 16)) + .sheet(isPresented: $isInviteViewVisible) { + NavigationView { + InviteParticipantsView( + inviteParticipantsShown: $isInviteViewVisible, + currentParticipants: viewModel.participants, + call: viewModel.call + ) + } + .navigationViewStyle(.stack) } - .padding([.leading, .trailing], 8) - - viewFactory.makeCallControlsView(viewModel: viewModel) - .opacity(viewModel.callingState == .reconnecting ? 0 : 1) } + .alignedToReadableContentGuide() + } else { + EmptyView() } - .chat(viewModel: viewModel, chatViewModel: chatViewModel) } private var callLink: String { AppEnvironment .baseURL .url - .appendingPathComponent("video") - .appendingPathComponent("demos") - .addQueryParameter("id", value: callId) + .appendingPathComponent("join") + .appendingPathComponent(callId) .addQueryParameter("type", value: callType) .absoluteString } diff --git a/StreamVideo.xcodeproj/project.pbxproj b/StreamVideo.xcodeproj/project.pbxproj index 8fd310b29..fb162c249 100644 --- a/StreamVideo.xcodeproj/project.pbxproj +++ b/StreamVideo.xcodeproj/project.pbxproj @@ -83,6 +83,7 @@ 407AF7162B61602B00E9E3E7 /* ControlBadgeView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 407AF7152B61602B00E9E3E7 /* ControlBadgeView_Tests.swift */; }; 407AF7182B61619900E9E3E7 /* ParticipantListButton_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 407AF7172B61619900E9E3E7 /* ParticipantListButton_Tests.swift */; }; 407AF71A2B6163DD00E9E3E7 /* StreamMediaDurationFormatter_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 407AF7192B6163DD00E9E3E7 /* StreamMediaDurationFormatter_Tests.swift */; }; + 407AF71D2B61651600E9E3E7 /* ReadableContentGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 407AF71C2B61651600E9E3E7 /* ReadableContentGuide.swift */; }; 407D5D3D2ACEF0C500B5044E /* VisibilityThresholdModifier_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 407D5D3C2ACEF0C500B5044E /* VisibilityThresholdModifier_Tests.swift */; }; 407F29FF2AA6011500C3EAF8 /* MemoryLogViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4093861E2AA0A21800FF5AF4 /* MemoryLogViewer.swift */; }; 407F2A002AA6011B00C3EAF8 /* LogQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4093861B2AA0A11500FF5AF4 /* LogQueue.swift */; }; @@ -110,11 +111,13 @@ 40B499CE2AC1AA0900A53B60 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4030E59F2A9DF5BD003E8CBA /* AppEnvironment.swift */; }; 40B713692A275F1400D1FE67 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8456E6C5287EB55F004E180E /* AppState.swift */; }; 40C7B82C2B612D6000FB9DB2 /* ParticipantsListViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C7B82B2B612D6000FB9DB2 /* ParticipantsListViewModifier.swift */; }; + 40C7B82F2B612E6C00FB9DB2 /* DemoMoreControlsViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C7B82E2B612E6C00FB9DB2 /* DemoMoreControlsViewModifier.swift */; }; 40C7B8322B61325500FB9DB2 /* ModalButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C7B8312B61325500FB9DB2 /* ModalButton.swift */; }; 40C7B8342B613A8200FB9DB2 /* ControlBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C7B8332B613A8200FB9DB2 /* ControlBadgeView.swift */; }; 40C7B8362B613C7800FB9DB2 /* ParticipantsListButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C7B8352B613C7800FB9DB2 /* ParticipantsListButton.swift */; }; 40D1657E2B5FE82200C6D951 /* HalfSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D1657D2B5FE82200C6D951 /* HalfSheetView.swift */; }; - 40D1657F2B5FE8AB00C6D951 /* ReadableContentGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409BFA422A9F7BBB003341EF /* ReadableContentGuide.swift */; }; + 40D165822B5FF2B100C6D951 /* DemoReactionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D165812B5FF2B100C6D951 /* DemoReactionButton.swift */; }; + 40D165842B5FF6FE00C6D951 /* DemoMoreControlListButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D165832B5FF6FE00C6D951 /* DemoMoreControlListButtonView.swift */; }; 40D6ADDD2ACDB51C00EF5336 /* VideoRenderer_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D6ADDC2ACDB51C00EF5336 /* VideoRenderer_Tests.swift */; }; 40D7E7962AB1C9590017095E /* ThermalStateViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D7E7952AB1C9580017095E /* ThermalStateViewModifier.swift */; }; 40D946412AA5ECEF00C8861B /* CodeScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D946402AA5ECEF00C8861B /* CodeScanner.swift */; }; @@ -1031,6 +1034,7 @@ 407AF7152B61602B00E9E3E7 /* ControlBadgeView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlBadgeView_Tests.swift; sourceTree = ""; }; 407AF7172B61619900E9E3E7 /* ParticipantListButton_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantListButton_Tests.swift; sourceTree = ""; }; 407AF7192B6163DD00E9E3E7 /* StreamMediaDurationFormatter_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamMediaDurationFormatter_Tests.swift; sourceTree = ""; }; + 407AF71C2B61651600E9E3E7 /* ReadableContentGuide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadableContentGuide.swift; sourceTree = ""; }; 407D5D3C2ACEF0C500B5044E /* VisibilityThresholdModifier_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibilityThresholdModifier_Tests.swift; sourceTree = ""; }; 40901A5E2AE693B100B6831D /* LongPressToFocusViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongPressToFocusViewModifier.swift; sourceTree = ""; }; 40901A682AE8018900B6831D /* mock-profile-image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "mock-profile-image.jpg"; sourceTree = ""; }; @@ -1041,7 +1045,6 @@ 4093861B2AA0A11500FF5AF4 /* LogQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogQueue.swift; sourceTree = ""; }; 4093861E2AA0A21800FF5AF4 /* MemoryLogViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryLogViewer.swift; sourceTree = ""; }; 409BFA3F2A9F79D2003341EF /* View+OptionalPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+OptionalPublisher.swift"; sourceTree = ""; }; - 409BFA422A9F7BBB003341EF /* ReadableContentGuide.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableContentGuide.swift; sourceTree = ""; }; 40A9416D2B4D959F006D6965 /* StreamPictureInPictureAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamPictureInPictureAdapter.swift; sourceTree = ""; }; 40A9416F2B4D96E6006D6965 /* StreamPictureInPictureController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamPictureInPictureController.swift; sourceTree = ""; }; 40A941712B4D9750006D6965 /* StreamAVPictureInPictureVideoCallViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamAVPictureInPictureVideoCallViewController.swift; sourceTree = ""; }; @@ -1055,10 +1058,13 @@ 40B499C92AC1A5E100A53B60 /* OSLogDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLogDestination.swift; sourceTree = ""; }; 40B499CB2AC1A90F00A53B60 /* DeeplinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeeplinkTests.swift; sourceTree = ""; }; 40C7B82B2B612D6000FB9DB2 /* ParticipantsListViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantsListViewModifier.swift; sourceTree = ""; }; + 40C7B82E2B612E6C00FB9DB2 /* DemoMoreControlsViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoMoreControlsViewModifier.swift; sourceTree = ""; }; 40C7B8312B61325500FB9DB2 /* ModalButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalButton.swift; sourceTree = ""; }; 40C7B8332B613A8200FB9DB2 /* ControlBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlBadgeView.swift; sourceTree = ""; }; 40C7B8352B613C7800FB9DB2 /* ParticipantsListButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantsListButton.swift; sourceTree = ""; }; 40D1657D2B5FE82200C6D951 /* HalfSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HalfSheetView.swift; sourceTree = ""; }; + 40D165812B5FF2B100C6D951 /* DemoReactionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoReactionButton.swift; sourceTree = ""; }; + 40D165832B5FF6FE00C6D951 /* DemoMoreControlListButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoMoreControlListButtonView.swift; sourceTree = ""; }; 40D6ADDC2ACDB51C00EF5336 /* VideoRenderer_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRenderer_Tests.swift; sourceTree = ""; }; 40D7E7952AB1C9580017095E /* ThermalStateViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThermalStateViewModifier.swift; sourceTree = ""; }; 40D946402AA5ECEF00C8861B /* CodeScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeScanner.swift; sourceTree = ""; }; @@ -1942,19 +1948,15 @@ 4030E5952A9DF488003E8CBA /* Views */ = { isa = PBXGroup; children = ( - 40F874502B35A73C00574F5D /* ChangeEnvironmentViewModifier */, - 40901A602AE6A44F00B6831D /* LongPressToFocusViewModifier */, - 40D7E7942AB1C9470017095E /* ThermalStateViewModifier */, + 40C7B82D2B612E5D00FB9DB2 /* ViewModifiers */, 40F445FC2A9E2B3A004BE3DA /* WaitingLocalUserView */, 40F445FB2A9E2B32004BE3DA /* LinkInfoView */, 40F445F82A9E2AF3004BE3DA /* ShareActivityView */, 40F445EE2A9E2A42004BE3DA /* LoadingView */, 40F445EB2A9E2A12004BE3DA /* Controls */, 4030E59D2A9DF4D0003E8CBA /* CallsView */, - 40F445E32A9E2779004BE3DA /* VideoCallParticipantModifier */, 40F445DC2A9E22AE004BE3DA /* CallButtonView */, 40F445D92A9E226D004BE3DA /* CallView */, - 40F445D62A9E21A8004BE3DA /* CallModifier */, 40F445D52A9E2157004BE3DA /* CallTopView */, 40F445CD2A9E1FDB004BE3DA /* Reactions */, 847B47B62A260CF0000714CE /* CustomCallView.swift */, @@ -2040,6 +2042,14 @@ path = CallState; sourceTree = ""; }; + 407AF71B2B61651600E9E3E7 /* ReadableContentGuide */ = { + isa = PBXGroup; + children = ( + 407AF71C2B61651600E9E3E7 /* ReadableContentGuide.swift */, + ); + path = ReadableContentGuide; + sourceTree = ""; + }; 40901A602AE6A44F00B6831D /* LongPressToFocusViewModifier */ = { isa = PBXGroup; children = ( @@ -2068,14 +2078,6 @@ path = MemoryLogDestination; sourceTree = ""; }; - 409BFA412A9F7BAA003341EF /* ReadableContentGuide */ = { - isa = PBXGroup; - children = ( - 409BFA422A9F7BBB003341EF /* ReadableContentGuide.swift */, - ); - path = ReadableContentGuide; - sourceTree = ""; - }; 409BFA442A9F7E92003341EF /* CallingView */ = { isa = PBXGroup; children = ( @@ -2129,12 +2131,33 @@ 40C7B82A2B612D5100FB9DB2 /* ViewModifiers */ = { isa = PBXGroup; children = ( - 409BFA412A9F7BAA003341EF /* ReadableContentGuide */, + 407AF71B2B61651600E9E3E7 /* ReadableContentGuide */, 40C7B82B2B612D6000FB9DB2 /* ParticipantsListViewModifier.swift */, ); path = ViewModifiers; sourceTree = ""; }; + 40C7B82D2B612E5D00FB9DB2 /* ViewModifiers */ = { + isa = PBXGroup; + children = ( + 40F445E32A9E2779004BE3DA /* VideoCallParticipantModifier */, + 40D7E7942AB1C9470017095E /* ThermalStateViewModifier */, + 40F874502B35A73C00574F5D /* ChangeEnvironmentViewModifier */, + 40901A602AE6A44F00B6831D /* LongPressToFocusViewModifier */, + 40F445D62A9E21A8004BE3DA /* CallModifier */, + 40C7B8302B612EF400FB9DB2 /* MoreControls */, + ); + path = ViewModifiers; + sourceTree = ""; + }; + 40C7B8302B612EF400FB9DB2 /* MoreControls */ = { + isa = PBXGroup; + children = ( + 40C7B82E2B612E6C00FB9DB2 /* DemoMoreControlsViewModifier.swift */, + ); + path = MoreControls; + sourceTree = ""; + }; 40C7B8372B613CBE00FB9DB2 /* Controls */ = { isa = PBXGroup; children = ( @@ -2144,6 +2167,15 @@ path = Controls; sourceTree = ""; }; + 40D165802B5FF29600C6D951 /* DemoMoreControls */ = { + isa = PBXGroup; + children = ( + 40D165812B5FF2B100C6D951 /* DemoReactionButton.swift */, + 40D165832B5FF6FE00C6D951 /* DemoMoreControlListButtonView.swift */, + ); + path = DemoMoreControls; + sourceTree = ""; + }; 40D7E7942AB1C9470017095E /* ThermalStateViewModifier */ = { isa = PBXGroup; children = ( @@ -2276,6 +2308,7 @@ 40F445EB2A9E2A12004BE3DA /* Controls */ = { isa = PBXGroup; children = ( + 40D165802B5FF29600C6D951 /* DemoMoreControls */, 40F445EC2A9E2A24004BE3DA /* DemoControls.swift */, ); path = Controls; @@ -4283,6 +4316,7 @@ 8456E6C6287EB55F004E180E /* AppState.swift in Sources */, 40901A612AE6A48C00B6831D /* LongPressToFocusViewModifier.swift in Sources */, 40D946412AA5ECEF00C8861B /* CodeScanner.swift in Sources */, + 40C7B82F2B612E6C00FB9DB2 /* DemoMoreControlsViewModifier.swift in Sources */, 40F445AE2A9DFC34004BE3DA /* UserState.swift in Sources */, 40F445F52A9E2AA1004BE3DA /* ReactionOverlayView.swift in Sources */, 8456E6C2287EB405004E180E /* LoginView.swift in Sources */, @@ -4291,12 +4325,14 @@ 84093811288A90390089A35B /* DetailedCallingView.swift in Sources */, 84ED240D286C9515002A3186 /* DemoCallContainerView.swift in Sources */, 8403C0AD2897CF4D0092BD43 /* CallKitService.swift in Sources */, + 40D165842B5FF6FE00C6D951 /* DemoMoreControlListButtonView.swift in Sources */, 40F445B22A9DFFBB004BE3DA /* User+Demo.swift in Sources */, 8430FD1E2AB085D9007AA3E6 /* ParticipantStatsViewModel.swift in Sources */, 40F445B02A9DFC58004BE3DA /* CallKitState.swift in Sources */, 40D946452AA5F67E00C8861B /* DemoCallingTopView.swift in Sources */, 847B47B72A260CF1000714CE /* CustomCallView.swift in Sources */, 40F445EA2A9E297B004BE3DA /* CallStateResponseFields+Identifiable.swift in Sources */, + 40D165822B5FF2B100C6D951 /* DemoReactionButton.swift in Sources */, 40F445CC2A9E1FC9004BE3DA /* DemoChatViewFactory.swift in Sources */, 404A5CFB2AD5648100EF1C62 /* DemoChatModifier.swift in Sources */, 842D8BDA2865B37800801910 /* DemoApp.swift in Sources */, @@ -4867,7 +4903,6 @@ 84DC382F29A8BB8D00946713 /* CallParticipantsInfoViewModel.swift in Sources */, 8277D1762AE81CC800BC4ECF /* ImageCaching.swift in Sources */, 846FBE9128AAF52600147F6E /* SelectedParticipantView.swift in Sources */, - 40D1657F2B5FE8AB00C6D951 /* ReadableContentGuide.swift in Sources */, 8277D1472AE81CC800BC4ECF /* OperationTask.swift in Sources */, 848A73C02926314F0089AA6E /* MinimizedCallView.swift in Sources */, 8434C525289AA2E20001490A /* Fonts.swift in Sources */, @@ -4922,6 +4957,7 @@ 40E110472B5A9DF4007DF492 /* CallDurationView.swift in Sources */, 8277D1502AE81CC800BC4ECF /* ImageProcessors+GaussianBlur.swift in Sources */, 40A9416E2B4D959F006D6965 /* StreamPictureInPictureAdapter.swift in Sources */, + 407AF71D2B61651600E9E3E7 /* ReadableContentGuide.swift in Sources */, 846FBE8B28AAD84A00147F6E /* InviteParticipantsViewModel.swift in Sources */, 40E110492B5A9F03007DF492 /* Formatters.swift in Sources */, 40C7B8322B61325500FB9DB2 /* ModalButton.swift in Sources */,