From 60d128f4f25f2817ec6663d1d99aff3d932c25c2 Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Mon, 5 Feb 2024 16:54:27 +0100 Subject: [PATCH] Call view changes --- .../lock_simple.imageset/Contents.json | 21 + .../lock_simple.imageset/lock_simple.svg | 3 + Linphone/Contacts/ContactsManager.swift | 4 +- Linphone/Localizable.xcstrings | 6 +- Linphone/UI/Call/CallView.swift | 968 +++++++++--------- 5 files changed, 519 insertions(+), 483 deletions(-) create mode 100644 Linphone/Assets.xcassets/lock_simple.imageset/Contents.json create mode 100644 Linphone/Assets.xcassets/lock_simple.imageset/lock_simple.svg diff --git a/Linphone/Assets.xcassets/lock_simple.imageset/Contents.json b/Linphone/Assets.xcassets/lock_simple.imageset/Contents.json new file mode 100644 index 000000000..2f3e708ff --- /dev/null +++ b/Linphone/Assets.xcassets/lock_simple.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "lock_simple.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/lock_simple.imageset/lock_simple.svg b/Linphone/Assets.xcassets/lock_simple.imageset/lock_simple.svg new file mode 100644 index 000000000..dcd3fb3e9 --- /dev/null +++ b/Linphone/Assets.xcassets/lock_simple.imageset/lock_simple.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/Contacts/ContactsManager.swift b/Linphone/Contacts/ContactsManager.swift index 84efe85fd..6808a6b49 100644 --- a/Linphone/Contacts/ContactsManager.swift +++ b/Linphone/Contacts/ContactsManager.swift @@ -143,8 +143,8 @@ final class ContactsManager: ObservableObject { func textToImage(firstName: String, lastName: String) -> UIImage { let lblNameInitialize = UILabel() - lblNameInitialize.frame.size = CGSize(width: 100.0, height: 100.0) - lblNameInitialize.font = UIFont(name: "NotoSans-ExtraBold", size: 40) + lblNameInitialize.frame.size = CGSize(width: 200.0, height: 200.0) + lblNameInitialize.font = UIFont(name: "NotoSans-ExtraBold", size: 80) lblNameInitialize.textColor = UIColor(Color.grayMain2c600) var textToDisplay = "" diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index c48bf298e..589a8fa68 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -170,6 +170,9 @@ }, "Appel" : { + }, + "Appel chiffré de bout en bout" : { + }, "assistant_account_login" : { "extractionState" : "manual", @@ -436,9 +439,6 @@ }, "Other actions" : { - }, - "Outgoing call" : { - }, "password" : { "extractionState" : "manual", diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index 88c61e481..0a587abae 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -40,20 +40,18 @@ struct CallView: View { @State var audioRouteSheet: Bool = false @State var hideButtonsSheet: Bool = false @State var options: Int = 1 - @State var imageAudioRoute: String = "" - @State var angleDegree = 0.0 - @State var showingDialer = false + @State var minBottomSheetHeight: CGFloat = 0.16 + @State var maxBottomSheetHeight: CGFloat = 0.5 + @State private var pointingUp: CGFloat = 0.0 + @State private var currentOffset: CGFloat = 0.0 @Binding var fullscreenVideo: Bool @Binding var isShowCallsListFragment: Bool @Binding var isShowStartCallFragment: Bool - @State private var pointingUp: CGFloat = 0.0 - @State private var currentOffset: CGFloat = 0.0 - var body: some View { GeometryReader { geo in ZStack { @@ -113,18 +111,6 @@ struct CallView: View { CallsListFragment(callViewModel: callViewModel, isShowCallsListFragment: $isShowCallsListFragment) .zIndex(4) .transition(.move(edge: .bottom)) - /* - .sheet(isPresented: $showingDialer) { - DialerBottomSheet( - startCallViewModel: startCallViewModel, - showingDialer: $showingDialer, - currentCall: nil - ) - .presentationDetents([.medium]) - // .interactiveDismissDisabled() - .presentationBackgroundInteraction(.enabled(upThrough: .medium)) - } - */ } if callViewModel.zrtpPopupDisplayed == true { @@ -265,318 +251,349 @@ struct CallView: View { @ViewBuilder // swiftlint:disable:next cyclomatic_complexity func innerView(geometry: GeometryProxy) -> some View { - VStack { - if !fullscreenVideo || (fullscreenVideo && telecomManager.isPausedByRemote) { - if #available(iOS 16.0, *) { - Rectangle() - .foregroundColor(Color.orangeMain500) - .edgesIgnoringSafeArea(.top) - .frame(height: 0) - } else if idiom != .pad && !(orientation == .landscapeLeft || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { - Rectangle() - .foregroundColor(Color.orangeMain500) - .edgesIgnoringSafeArea(.top) - .frame(height: 1) - } - - HStack { - if callViewModel.direction == .Outgoing { - Image("outgoing-call") - .resizable() - .frame(width: 15, height: 15) - .padding(.horizontal) - - Text("Outgoing call") - .foregroundStyle(.white) - } else { - Image("incoming-call") - .resizable() - .frame(width: 15, height: 15) - .padding(.horizontal) - - Text("Incoming call") - .foregroundStyle(.white) + ZStack { + VStack { + if !fullscreenVideo || (fullscreenVideo && telecomManager.isPausedByRemote) { + if #available(iOS 16.0, *) { + Rectangle() + .foregroundColor(Color.orangeMain500) + .edgesIgnoringSafeArea(.top) + .frame(height: 0) + } else if idiom != .pad && !(orientation == .landscapeLeft || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { + Rectangle() + .foregroundColor(Color.orangeMain500) + .edgesIgnoringSafeArea(.top) + .frame(height: 1) } - - if !telecomManager.outgoingCallStarted && telecomManager.callInProgress { - Text("|") - .foregroundStyle(.white) - - ZStack { - Text(callViewModel.timeElapsed.convertDurationToString()) - .onReceive(callViewModel.timer) { _ in - callViewModel.timeElapsed = callViewModel.currentCall?.duration ?? 0 - } - .foregroundStyle(.white) - .if(callViewModel.isPaused || telecomManager.isPausedByRemote) { view in - view.hidden() + ZStack { + HStack { + Button { + withAnimation { + telecomManager.callDisplayed = false } + } label: { + Image("caret-left") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + } - if callViewModel.isPaused { - Text("Paused") - .foregroundStyle(.white) - } else if telecomManager.isPausedByRemote { - Text("Paused by remote") + Text(callViewModel.displayName) + .default_text_style_white_800(styleSize: 16) + + if !telecomManager.outgoingCallStarted && telecomManager.callInProgress { + Text("|") + .default_text_style_white_800(styleSize: 16) + + ZStack { + Text(callViewModel.timeElapsed.convertDurationToString()) + .onReceive(callViewModel.timer) { _ in + callViewModel.timeElapsed = callViewModel.currentCall?.duration ?? 0 + } + .default_text_style_white_800(styleSize: 16) + .if(callViewModel.isPaused || telecomManager.isPausedByRemote) { view in + view.hidden() + } + + if callViewModel.isPaused { + Text("Paused") + .default_text_style_white_800(styleSize: 16) + } else if telecomManager.isPausedByRemote { + Text("Paused by remote") + .default_text_style_white_800(styleSize: 16) + } + } + } + + Spacer() + + Button { + } label: { + Image("cell-signal-full") + .renderingMode(.template) + .resizable() .foregroundStyle(.white) + .frame(width: 30, height: 30) + .padding(.all, 10) + } + + if telecomManager.remoteVideo { + Button { + callViewModel.switchCamera() + } label: { + Image("camera-rotate") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 30, height: 30) + .padding(.horizontal) + } } } + .frame(height: 40) + .zIndex(1) + + if callViewModel.isMediaEncrypted { + HStack { + Image("lock_simple") + .resizable() + .frame(width: 15, height: 15, alignment: .leading) + .padding(.leading, 50) + .padding(.top, 35) + + Text("Appel chiffré de bout en bout") + .foregroundStyle(Color.blueInfo500) + .default_text_style_white(styleSize: 12) + .padding(.top, 35) + + Spacer() + } + .onTapGesture { + callViewModel.showZrtpSasDialogIfPossible() + } + .frame(height: 40) + .zIndex(1) + } + } + } + + ZStack { + VStack { + Spacer() + ZStack { + + if callViewModel.isRemoteDeviceTrusted { + Circle() + .fill(Color.blueInfo500) + .frame(width: 206, height: 206) + } + + if callViewModel.remoteAddress != nil { + let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.remoteAddress!) + + let contactAvatarModel = addressFriend != nil + ? ContactsManager.shared.avatarListModel.first(where: { + ($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy) + && $0.friend!.name == addressFriend!.name + && $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly() + }) + : ContactAvatarModel(friend: nil, withPresence: false) + + if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty { + if contactAvatarModel != nil { + Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 200, hidePresence: true) + } + } else { + if callViewModel.remoteAddress!.displayName != nil { + Image(uiImage: contactsManager.textToImage( + firstName: callViewModel.remoteAddress!.displayName!, + lastName: callViewModel.remoteAddress!.displayName!.components(separatedBy: " ").count > 1 + ? callViewModel.remoteAddress!.displayName!.components(separatedBy: " ")[1] + : "")) + .resizable() + .frame(width: 200, height: 200) + .clipShape(Circle()) + + } else { + Image(uiImage: contactsManager.textToImage( + firstName: callViewModel.remoteAddress!.username ?? "Username Error", + lastName: callViewModel.remoteAddress!.username!.components(separatedBy: " ").count > 1 + ? callViewModel.remoteAddress!.username!.components(separatedBy: " ")[1] + : "")) + .resizable() + .frame(width: 200, height: 200) + .clipShape(Circle()) + } + + } + } else { + Image("profil-picture-default") + .resizable() + .frame(width: 200, height: 200) + .clipShape(Circle()) + } + + if callViewModel.isRemoteDeviceTrusted { + VStack { + Spacer() + HStack { + Image("trusted") + .resizable() + .frame(width: 25, height: 25) + .padding(.all, 15) + Spacer() + } + } + .frame(width: 200, height: 200) + } + } + + Text(callViewModel.displayName) + .padding(.top) + .default_text_style_white(styleSize: 22) + + Text(callViewModel.remoteAddressString) + .default_text_style_white_300(styleSize: 16) + + Spacer() } - Spacer() - - if callViewModel.isMediaEncrypted { - Button { - callViewModel.showZrtpSasDialogIfPossible() - } label: { - Image(callViewModel.isZrtpPq ? "media-encryption-zrtp-pq" : "media-encryption-srtp") - .resizable() - .frame(width: 30, height: 30) - .padding(.horizontal) + LinphoneVideoViewHolder { view in + coreContext.doOnCoreQueue { core in + core.nativeVideoWindow = view + } + } + .frame( + width: + angleDegree == 0 + ? (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8) + : (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom), + height: + angleDegree == 0 + ? (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom) + : (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8) + ) + .scaledToFill() + .clipped() + .onTapGesture { + if telecomManager.remoteVideo { + fullscreenVideo.toggle() } } if telecomManager.remoteVideo { - Button { - callViewModel.switchCamera() - } label: { - Image("camera-rotate") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 30, height: 30) - .padding(.horizontal) - } - } - } - .frame(height: 40) - .zIndex(1) - } - - ZStack { - VStack { - Spacer() - ZStack { - - if callViewModel.isRemoteDeviceTrusted { - Circle() - .fill(Color.blueInfo500) - .frame(width: 105, height: 105) - } - - if callViewModel.remoteAddress != nil { - let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.remoteAddress!) - - let contactAvatarModel = addressFriend != nil - ? ContactsManager.shared.avatarListModel.first(where: { - ($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy) - && $0.friend!.name == addressFriend!.name - && $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly() - }) - : ContactAvatarModel(friend: nil, withPresence: false) - - if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty { - if contactAvatarModel != nil { - Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 100, hidePresence: true) - } - } else { - if callViewModel.remoteAddress!.displayName != nil { - Image(uiImage: contactsManager.textToImage( - firstName: callViewModel.remoteAddress!.displayName!, - lastName: callViewModel.remoteAddress!.displayName!.components(separatedBy: " ").count > 1 - ? callViewModel.remoteAddress!.displayName!.components(separatedBy: " ")[1] - : "")) - .resizable() - .frame(width: 100, height: 100) - .clipShape(Circle()) - - } else { - Image(uiImage: contactsManager.textToImage( - firstName: callViewModel.remoteAddress!.username ?? "Username Error", - lastName: callViewModel.remoteAddress!.username!.components(separatedBy: " ").count > 1 - ? callViewModel.remoteAddress!.username!.components(separatedBy: " ")[1] - : "")) - .resizable() - .frame(width: 100, height: 100) - .clipShape(Circle()) - } - - } - } else { - Image("profil-picture-default") - .resizable() - .frame(width: 100, height: 100) - .clipShape(Circle()) - } - - if callViewModel.isRemoteDeviceTrusted { + HStack { + Spacer() VStack { Spacer() - HStack { - Image("trusted") - .resizable() - .frame(width: 25, height: 25) - Spacer() + LinphoneVideoViewHolder { view in + coreContext.doOnCoreQueue { core in + core.nativePreviewWindow = view + } } + .frame(width: angleDegree == 0 ? 120*1.2 : 160*1.2, height: angleDegree == 0 ? 160*1.2 : 120*1.2) + .cornerRadius(20) + .padding(10) + .padding(.trailing, abs(angleDegree/2)) } - .frame(width: 100, height: 100) } + .frame( + maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8, + maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom + ) } - Text(callViewModel.displayName) - .padding(.top) - .foregroundStyle(.white) + if callViewModel.isRecording { + HStack { + VStack { + Image("record-fill") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.redDanger500) + .frame(width: 32, height: 32) + .padding(10) + .if(fullscreenVideo && !telecomManager.isPausedByRemote) { view in + view.padding(.top, 30) + } + Spacer() + } + Spacer() + } + .frame( + maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8, + maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom + ) + } - Text(callViewModel.remoteAddressString) - .foregroundStyle(.white) - - Spacer() - } - - LinphoneVideoViewHolder { view in - coreContext.doOnCoreQueue { core in - core.nativeVideoWindow = view + if telecomManager.outgoingCallStarted { + VStack { + ActivityIndicator() + .frame(width: 20, height: 20) + .padding(.top, 60) + + Text(callViewModel.counterToMinutes()) + .onAppear { + callViewModel.timeElapsed = 0 + } + .onReceive(callViewModel.timer) { _ in + callViewModel.timeElapsed = callViewModel.currentCall?.duration ?? 0 + + } + .onDisappear { + callViewModel.timeElapsed = 0 + } + .padding(.top) + .foregroundStyle(.white) + + Spacer() + } + .background(.clear) + .frame( + maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8, + maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom + ) } } .frame( - width: - angleDegree == 0 - ? 120 * ((geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) / 160) - : 120 * ((geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) / 120), - height: - angleDegree == 0 - ? 160 * ((geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) / 160) - : 160 * ((geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) / 120) + maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8, + maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom ) - .scaledToFill() - .clipped() - .onTapGesture { - if telecomManager.remoteVideo { - fullscreenVideo.toggle() - } - } - - if telecomManager.remoteVideo { - HStack { - Spacer() - VStack { - Spacer() - LinphoneVideoViewHolder { view in - coreContext.doOnCoreQueue { core in - core.nativePreviewWindow = view - } - } - .frame(width: angleDegree == 0 ? 120*1.2 : 160*1.2, height: angleDegree == 0 ? 160*1.2 : 120*1.2) - .cornerRadius(20) - .padding(10) - .padding(.trailing, abs(angleDegree/2)) + .background(Color.gray900) + .cornerRadius(20) + .padding(.horizontal, fullscreenVideo && !telecomManager.isPausedByRemote ? 0 : 4) + .onRotate { newOrientation in + let oldOrientation = orientation + orientation = newOrientation + if orientation == .portrait || orientation == .portraitUpsideDown { + angleDegree = 0 + } else { + if orientation == .landscapeLeft { + angleDegree = -90 + } else if orientation == .landscapeRight { + angleDegree = 90 } } - .frame( - maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8, - maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (0.18 * geometry.size.height) - geometry.safeAreaInsets.bottom - ) - } - - if callViewModel.isRecording { - HStack { - VStack { - Image("record-fill") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.redDanger500) - .frame(width: 32, height: 32) - .padding(10) - .if(fullscreenVideo && !telecomManager.isPausedByRemote) { view in - view.padding(.top, 30) - } - Spacer() + + if (oldOrientation != orientation && oldOrientation != .faceUp) || (oldOrientation == .faceUp && (orientation == .landscapeLeft || orientation == .landscapeRight)) { + telecomManager.callStarted = false + + DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { + telecomManager.callStarted = true } - Spacer() } - .frame( - maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8, - maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (0.18 * geometry.size.height) - geometry.safeAreaInsets.bottom - ) + + callViewModel.orientationUpdate(orientation: orientation) } - - if telecomManager.outgoingCallStarted { - VStack { - ActivityIndicator() - .frame(width: 20, height: 20) - .padding(.top, 100) - - Text(callViewModel.counterToMinutes()) - .onAppear { - callViewModel.timeElapsed = 0 - } - .onReceive(callViewModel.timer) { _ in - callViewModel.timeElapsed = callViewModel.currentCall?.duration ?? 0 - - } - .onDisappear { - callViewModel.timeElapsed = 0 - } - .padding(.top) - .foregroundStyle(.white) - - Spacer() + .onAppear { + if orientation == .portrait && orientation == .portraitUpsideDown { + angleDegree = 0 + } else { + if orientation == .landscapeLeft { + angleDegree = -90 + } else if orientation == .landscapeRight { + angleDegree = 90 + } } - .frame( - maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8, - maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (0.18 * geometry.size.height) - geometry.safeAreaInsets.bottom - ) - .background(.clear) - } - } - .frame( - maxWidth: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.width : geometry.size.width - 8, - maxHeight: fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (0.18 * geometry.size.height) - geometry.safeAreaInsets.bottom - ) - .background(Color.gray900) - .cornerRadius(20) - .padding(.horizontal, fullscreenVideo && !telecomManager.isPausedByRemote ? 0 : 4) - .onRotate { newOrientation in - let oldOrientation = orientation - orientation = newOrientation - if orientation == .portrait || orientation == .portraitUpsideDown { - angleDegree = 0 - } else { - if orientation == .landscapeLeft { - angleDegree = -90 - } else if orientation == .landscapeRight { - angleDegree = 90 - } - } - - if (oldOrientation != orientation && oldOrientation != .faceUp) || (oldOrientation == .faceUp && (orientation == .landscapeLeft || orientation == .landscapeRight)) { + telecomManager.callStarted = false DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { telecomManager.callStarted = true } + + callViewModel.orientationUpdate(orientation: orientation) } - callViewModel.orientationUpdate(orientation: orientation) + Spacer() } - .onAppear { - if orientation == .portrait && orientation == .portraitUpsideDown { - angleDegree = 0 - } else { - if orientation == .landscapeLeft { - angleDegree = -90 - } else if orientation == .landscapeRight { - angleDegree = 90 - } - } - - telecomManager.callStarted = false - - DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { - telecomManager.callStarted = true - } - - callViewModel.orientationUpdate(orientation: orientation) + .frame(height: geometry.size.height) + .frame(maxWidth: .infinity) + .background(Color.gray900) + .if(fullscreenVideo && !telecomManager.isPausedByRemote) { view in + view.ignoresSafeArea(.all) } if !fullscreenVideo || (fullscreenVideo && telecomManager.isPausedByRemote) { @@ -586,74 +603,19 @@ struct CallView: View { BottomSheetView( content: bottomSheetContent(geo: geometry), - minHeight: 0.18 * geometry.size.height, - maxHeight: 0.5 * geometry.size.height, + minHeight: (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78), + maxHeight: (maxBottomSheetHeight * geometry.size.height), currentOffset: $currentOffset, pointingUp: $pointingUp, bottomSafeArea: bottomInset?.bottom ?? 0 ) .onAppear { - currentOffset = 0.18 * geometry.size.height - pointingUp = 1 - ((currentOffset - 0.18 * geometry.size.height) / (0.5 * geometry.size.height - 0.18 * geometry.size.height)) + currentOffset = (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) + pointingUp = -(((currentOffset - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78)) / ((maxBottomSheetHeight * geometry.size.height) - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78))) - 0.5) * 2 } - } else { -#if targetEnvironment(simulator) - HStack(spacing: 12) { - HStack { - Spacer() - - Button { - callViewModel.terminateCall() - } label: { - Image("phone-disconnect") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - - } - .frame(width: 90, height: 60) - .background(Color.redDanger500) - .cornerRadius(40) - - Button { - callViewModel.acceptCall() - } label: { - Image("phone") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - - } - .frame(width: 90, height: 60) - .background(Color.greenSuccess500) - .cornerRadius(40) - - Spacer() - } - .frame(height: 60) - } - .padding(.horizontal, 25) - .padding(.top, 20) -#else - HStack(spacing: 12) { - HStack { - } - .frame(height: 60) - } - .padding(.horizontal, 25) - .padding(.top, 20) -#endif + .edgesIgnoringSafeArea(.bottom) } } - - Spacer() - } - .frame(maxWidth: .infinity) - .background(Color.gray900) - .if(fullscreenVideo && !telecomManager.isPausedByRemote) { view in - view.ignoresSafeArea(.all) } } @@ -662,18 +624,18 @@ struct CallView: View { VStack(spacing: 0) { Button { withAnimation { - if currentOffset < (0.5 * geo.size.height) { - currentOffset = 0.5 * geo.size.height + if currentOffset < (maxBottomSheetHeight * geo.size.height) { + currentOffset = (maxBottomSheetHeight * geo.size.height) } else { - currentOffset = 0.18 * geo.size.height + currentOffset = (minBottomSheetHeight * geo.size.height > 80 ? minBottomSheetHeight * geo.size.height : 78) } - pointingUp = 1 - ((currentOffset - 0.18 * geo.size.height) / (0.5 * geo.size.height - 0.18 * geo.size.height)) + pointingUp = -(((currentOffset - (minBottomSheetHeight * geo.size.height > 80 ? minBottomSheetHeight * geo.size.height : 78)) / ((maxBottomSheetHeight * geo.size.height) - (minBottomSheetHeight * geo.size.height > 80 ? minBottomSheetHeight * geo.size.height : 78))) - 0.5) * 2 } } label: { ChevronShape(pointingUp: pointingUp) .stroke(style: StrokeStyle(lineWidth: 4, lineCap: .round)) - .frame(width: 50, height: 10) + .frame(width: 40, height: 6) .foregroundStyle(.white) .contentShape(Rectangle()) .padding(.top, 15) @@ -699,30 +661,34 @@ struct CallView: View { Button { callViewModel.toggleVideo() } label: { - Image(telecomManager.remoteVideo ? "video-camera" : "video-camera-slash") - .renderingMode(.template) - .resizable() - .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) - .frame(width: 32, height: 32) - + HStack { + Image(telecomManager.remoteVideo ? "video-camera" : "video-camera-slash") + .renderingMode(.template) + .resizable() + .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) - .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500) + .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? .white : Color.gray500) .cornerRadius(40) .disabled(callViewModel.isPaused || telecomManager.isPausedByRemote) Button { callViewModel.toggleMuteMicrophone() } label: { - Image(callViewModel.micMutted ? "microphone-slash" : "microphone") - .renderingMode(.template) - .resizable() - .foregroundStyle(callViewModel.micMutted ? .black : .white) - .frame(width: 32, height: 32) - + HStack { + Image(callViewModel.micMutted ? "microphone-slash" : "microphone") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) - .background(callViewModel.micMutted ? .white : Color.gray500) + .background(callViewModel.micMutted ? Color.redDanger500 : Color.gray500) .cornerRadius(40) Button { @@ -743,23 +709,26 @@ struct CallView: View { } } label: { - Image(imageAudioRoute) - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - .onAppear(perform: getAudioRouteImage) - .onReceive(pub) { _ in - self.getAudioRouteImage() - } - + HStack { + Image(imageAudioRoute) + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + .onAppear(perform: getAudioRouteImage) + .onReceive(pub) { _ in + self.getAudioRouteImage() + } + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) .background(Color.gray500) .cornerRadius(40) } .frame(height: geo.size.height * 0.15) .padding(.horizontal, 20) + .padding(.top, -12) if orientation != .landscapeLeft && orientation != .landscapeRight { HStack(spacing: 0) { @@ -775,12 +744,15 @@ struct CallView: View { callViewModel.transferClicked() } } label: { - Image("phone-transfer") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) + HStack { + Image("phone-transfer") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) .background(Color.gray500) .cornerRadius(40) @@ -798,12 +770,15 @@ struct CallView: View { isShowStartCallFragment.toggle() } } label: { - Image("phone-plus") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) + HStack { + Image("phone-plus") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) .background(Color.gray500) .cornerRadius(40) @@ -822,12 +797,15 @@ struct CallView: View { isShowCallsListFragment.toggle() } } label: { - Image("phone-list") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) + HStack { + Image("phone-list") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) .background(Color.gray500) .cornerRadius(40) @@ -863,12 +841,15 @@ struct CallView: View { Button { showingDialer.toggle() } label: { - Image("dialer") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) + HStack { + Image("dialer") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) .background(Color.gray500) .cornerRadius(40) @@ -878,31 +859,26 @@ struct CallView: View { .default_text_style(styleSize: 15) } .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) - - } .frame(height: geo.size.height * 0.15) HStack(spacing: 0) { VStack { Button { - withAnimation { - telecomManager.callDisplayed = false - } } label: { - Image("chat-teardrop-text") - .renderingMode(.template) - .resizable() - //.foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) - .foregroundStyle(Color.gray500) - .frame(width: 32, height: 32) + HStack { + Image("chat-teardrop-text") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.gray500) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) - //.background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500) - .background(Color.gray600) + .background(.white) .cornerRadius(40) - //.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote) - //.disabled(true) + .disabled(true) Text("Messages") .foregroundStyle(.white) @@ -914,14 +890,17 @@ struct CallView: View { Button { callViewModel.togglePause() } label: { - Image(callViewModel.isPaused ? "play" : "pause") - .renderingMode(.template) - .resizable() - .foregroundStyle(telecomManager.isPausedByRemote ? Color.gray500 : .white) - .frame(width: 32, height: 32) + HStack { + Image(callViewModel.isPaused ? "play" : "pause") + .renderingMode(.template) + .resizable() + .foregroundStyle(telecomManager.isPausedByRemote ? Color.gray500 : .white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) - .background(telecomManager.isPausedByRemote ? Color.gray600 : (callViewModel.isPaused ? Color.greenSuccess500 : Color.gray500)) + .background(telecomManager.isPausedByRemote ? .white : (callViewModel.isPaused ? Color.greenSuccess500 : Color.gray500)) .cornerRadius(40) .disabled(telecomManager.isPausedByRemote) @@ -935,14 +914,17 @@ struct CallView: View { Button { callViewModel.toggleRecording() } label: { - Image("record-fill") - .renderingMode(.template) - .resizable() - .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) - .frame(width: 32, height: 32) + HStack { + Image("record-fill") + .renderingMode(.template) + .resizable() + .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) - .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : (callViewModel.isRecording ? Color.redDanger500 : Color.gray500)) + .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? .white : (callViewModel.isRecording ? Color.redDanger500 : Color.gray500)) .cornerRadius(40) .disabled(callViewModel.isPaused || telecomManager.isPausedByRemote) @@ -955,12 +937,15 @@ struct CallView: View { VStack { Button { } label: { - Image("video-camera") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) + HStack { + Image("video-camera") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) .background(Color.gray500) .cornerRadius(40) @@ -983,12 +968,15 @@ struct CallView: View { isShowStartCallFragment.toggle() } } label: { - Image("phone-transfer") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) + HStack { + Image("phone-transfer") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) .background(Color.gray500) .cornerRadius(40) @@ -1006,12 +994,15 @@ struct CallView: View { isShowStartCallFragment.toggle() } } label: { - Image("phone-plus") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) + HStack { + Image("phone-plus") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) .background(Color.gray500) .cornerRadius(40) @@ -1026,12 +1017,15 @@ struct CallView: View { ZStack { Button { } label: { - Image("phone-list") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) + HStack { + Image("phone-list") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) .background(Color.gray500) .cornerRadius(40) @@ -1067,12 +1061,15 @@ struct CallView: View { Button { showingDialer.toggle() } label: { - Image("dialer") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) + HStack { + Image("dialer") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) .background(Color.gray500) .cornerRadius(40) @@ -1086,18 +1083,18 @@ struct CallView: View { VStack { Button { } label: { - Image("chat-teardrop-text") - .renderingMode(.template) - .resizable() - //.foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) - .foregroundStyle(Color.gray500) - .frame(width: 32, height: 32) + HStack { + Image("chat-teardrop-text") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.gray500) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) - //.background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500) - .background(Color.gray600) + .background(.white) .cornerRadius(40) - //.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote) .disabled(true) Text("Messages") @@ -1110,14 +1107,17 @@ struct CallView: View { Button { callViewModel.togglePause() } label: { - Image(callViewModel.isPaused ? "play" : "pause") - .renderingMode(.template) - .resizable() - .foregroundStyle(telecomManager.isPausedByRemote ? Color.gray500 : .white) - .frame(width: 32, height: 32) + HStack { + Image(callViewModel.isPaused ? "play" : "pause") + .renderingMode(.template) + .resizable() + .foregroundStyle(telecomManager.isPausedByRemote ? Color.gray500 : .white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) - .background(telecomManager.isPausedByRemote ? Color.gray600 : (callViewModel.isPaused ? Color.greenSuccess500 : Color.gray500)) + .background(telecomManager.isPausedByRemote ? .white : (callViewModel.isPaused ? Color.greenSuccess500 : Color.gray500)) .cornerRadius(40) .disabled(telecomManager.isPausedByRemote) @@ -1131,14 +1131,17 @@ struct CallView: View { Button { callViewModel.toggleRecording() } label: { - Image("record-fill") - .renderingMode(.template) - .resizable() - .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) - .frame(width: 32, height: 32) + HStack { + Image("record-fill") + .renderingMode(.template) + .resizable() + .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) + .frame(width: 32, height: 32) + } } + .buttonStyle(PressedButtonStyle()) .frame(width: 60, height: 60) - .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : (callViewModel.isRecording ? Color.redDanger500 : Color.gray500)) + .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? .white : (callViewModel.isRecording ? Color.redDanger500 : Color.gray500)) .cornerRadius(40) .disabled(callViewModel.isPaused || telecomManager.isPausedByRemote) @@ -1152,6 +1155,7 @@ struct CallView: View { .padding(.horizontal, 20) .padding(.top, 30) } + Spacer() } .background(Color.gray600) @@ -1214,16 +1218,16 @@ struct BottomSheetView: View { .onChanged { value in currentOffset -= value.translation.height currentOffset = min(max(currentOffset, minHeight), maxHeight) - pointingUp = 1 - ((currentOffset - minHeight) / (maxHeight - minHeight)) + pointingUp = -(((currentOffset - minHeight) / (maxHeight - minHeight)) - 0.5) * 2 } .onEnded { _ in withAnimation { currentOffset = (currentOffset - minHeight <= maxHeight - currentOffset) ? minHeight : maxHeight - pointingUp = 1 - ((currentOffset - minHeight) / (maxHeight - minHeight)) + pointingUp = -(((currentOffset - minHeight) / (maxHeight - minHeight)) - 0.5) * 2 } } ) - .offset(y: maxHeight - currentOffset + bottomSafeArea) + .offset(y: maxHeight - currentOffset) } } } @@ -1247,7 +1251,6 @@ struct ChevronShape: Shape { let arrowTipStartingPoint = height - pointingUp * height * 0.9 path.move(to: .init(x: 0, y: height)) - //path.addLine(to: .init(x: horizontalCenter, y: arrowTipStartingPoint)) path.addLine(to: .init(x: horizontalCenter - horizontalCenterOffset, y: arrowTipStartingPoint)) path.addQuadCurve(to: .init(x: horizontalCenter + horizontalCenterOffset, y: arrowTipStartingPoint), control: .init(x: horizontalCenter, y: height * (1 - pointingUp))) @@ -1258,6 +1261,15 @@ struct ChevronShape: Shape { } } +struct PressedButtonStyle: ButtonStyle { + func makeBody(configuration: Self.Configuration) -> some View { + configuration.label + .frame(width: 60, height: 60) + .background(configuration.isPressed ? .white : .clear) + .cornerRadius(40) + } +} + #Preview { CallView(callViewModel: CallViewModel(), fullscreenVideo: .constant(false), isShowCallsListFragment: .constant(false), isShowStartCallFragment: .constant(false)) }