diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 7f208fadba..aadb639b75 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -5639,7 +5639,7 @@ Any member of this group will be able to see messages in the channel."; "PrivacySettings.AutoArchive" = "Archive and Mute"; "PrivacySettings.AutoArchiveInfo" = "Automatically archive and mute new chats, groups and channels from non-contacts."; -"Call.RemoteVideoPaused" = "%@'s video paused"; +"Call.RemoteVideoPaused" = "%@'s video is paused"; "Settings.SetProfilePhotoOrVideo" = "Set Photo or Video"; "Settings.SetNewProfilePhotoOrVideo" = "Set New Photo or Video"; diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index 4e30bbe5a7..f398905112 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -42,15 +42,6 @@ public struct PresentationCallState: Equatable { case reconnecting(Double, Int32?, Data) case terminating case terminated(CallId?, CallSessionTerminationReason?, Bool) - - public var isOrWasActive: Bool { - switch self { - case .active, .terminating, .terminated: - return true - default: - return false - } - } } public enum VideoState: Equatable { diff --git a/submodules/TelegramCallsUI/Sources/CallController.swift b/submodules/TelegramCallsUI/Sources/CallController.swift index 59d5711934..53a2ce7865 100644 --- a/submodules/TelegramCallsUI/Sources/CallController.swift +++ b/submodules/TelegramCallsUI/Sources/CallController.swift @@ -167,6 +167,9 @@ public final class CallController: ViewController { let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) var items: [ActionSheetItem] = [] for output in availableOutputs { + if hasMute, case .builtin = output { + continue + } let title: String var icon: UIImage? switch output { diff --git a/submodules/TelegramCallsUI/Sources/CallControllerKeyPreviewNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerKeyPreviewNode.swift index eaa034766f..8671bc6b2b 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerKeyPreviewNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerKeyPreviewNode.swift @@ -86,7 +86,7 @@ final class CallControllerKeyPreviewNode: ASDisplayNode { self.keyTextNode.layer.animatePosition(from: self.keyTextNode.layer.position, to: CGPoint(x: rect.midX + 2.0, y: rect.midY), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in completion() }) - self.keyTextNode.layer.animateScale(from: 1.0, to: rect.size.width / self.keyTextNode.frame.size.width, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.keyTextNode.layer.animateScale(from: 1.0, to: rect.size.width / (self.keyTextNode.frame.size.width - 2.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) self.infoTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift index 4a1cdd1293..221048ae35 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift @@ -263,7 +263,7 @@ private final class CallVideoNode: ASDisplayNode { if withBackground { self.backgroundColor = .black } - UIView.transition(with: self.videoTransformContainer.view, duration: 0.4, options: [.transitionFlipFromLeft, .curveEaseOut], animations: { + UIView.transition(with: withBackground ? self.videoTransformContainer.view : self.view, duration: 0.4, options: [.transitionFlipFromLeft, .curveEaseOut], animations: { UIView.performWithoutAnimation { self.updateIsBlurred(isBlurred: true, light: true, animated: false) } @@ -315,6 +315,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro private var isRequestingVideo: Bool = false private var animateRequestedVideoOnce: Bool = false + private var displayedCameraConfirmation: Bool = false private var displayedCameraTooltip: Bool = false private var expandedVideoNode: CallVideoNode? @@ -365,6 +366,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro var present: ((ViewController) -> Void)? private var toastContent: CallControllerToastContent? + private var displayToastsAfterTimestamp: Double? + private var buttonsMode: CallControllerButtonsMode? private var isUIHidden: Bool = false @@ -460,7 +463,10 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro } self.buttonsNode.speaker = { [weak self] in - self?.beginAudioOuputSelection?(true) + guard let strongSelf = self else { + return + } + strongSelf.beginAudioOuputSelection?(strongSelf.hasVideoNodes) } self.buttonsNode.acceptOrEnd = { [weak self] in @@ -491,6 +497,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro case .active: if strongSelf.outgoingVideoNodeValue == nil { let proceed = { + strongSelf.displayedCameraConfirmation = true switch callState.videoState { case .inactive: strongSelf.isRequestingVideo = true @@ -501,9 +508,13 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro strongSelf.call.requestVideo() } - strongSelf.present?(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Call_CameraConfirmationText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Call_CameraConfirmationConfirm, action: { + if strongSelf.displayedCameraConfirmation { proceed() - })])) + } else { + strongSelf.present?(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Call_CameraConfirmationText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Call_CameraConfirmationConfirm, action: { + proceed() + })])) + } } else { strongSelf.call.disableVideo() } @@ -517,8 +528,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro return } strongSelf.disableActionsUntilTimestamp = CACurrentMediaTime() + 1.0 - if let outgoingVideoNode = strongSelf.outgoingVideoNodeValue, let (layout, _) = strongSelf.validLayout { - outgoingVideoNode.flip(withBackground: outgoingVideoNode.frame.width == layout.size.width) + if let outgoingVideoNode = strongSelf.outgoingVideoNodeValue { + outgoingVideoNode.flip(withBackground: outgoingVideoNode !== strongSelf.minimizedVideoNode) } strongSelf.call.switchVideoCamera() if let _ = strongSelf.outgoingVideoNodeValue { @@ -924,22 +935,32 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro } } - var toastContent: CallControllerToastContent = [] - if callState.state.isOrWasActive { - if self.hasVideoNodes && [.inactive, .paused].contains(callState.remoteVideoState) { - toastContent.insert(.camera) + if case .terminating = callState.state { + } else if case .terminated = callState.state { + } else { + var toastContent: CallControllerToastContent = [] + if case .active = callState.state { + if let displayToastsAfterTimestamp = self.displayToastsAfterTimestamp { + if CACurrentMediaTime() > displayToastsAfterTimestamp { + if case .inactive = callState.remoteVideoState, self.hasVideoNodes { + toastContent.insert(.camera) + } + if case .muted = callState.remoteAudioState { + toastContent.insert(.microphone) + } + if case .low = callState.remoteBatteryLevel { + toastContent.insert(.battery) + } + } + } else { + self.displayToastsAfterTimestamp = CACurrentMediaTime() + 2.0 + } } - if case .muted = callState.remoteAudioState { - toastContent.insert(.microphone) - } - if case .low = callState.remoteBatteryLevel { - toastContent.insert(.battery) + if self.isMuted, let (availableOutputs, _) = self.audioOutputState, availableOutputs.count > 2 { + toastContent.insert(.mute) } + self.toastContent = toastContent } - if self.isMuted, let (availableOutputs, _) = self.audioOutputState, availableOutputs.count > 2 { - toastContent.insert(.mute) - } - self.toastContent = toastContent self.updateButtonsMode() self.updateDimVisibility() @@ -947,7 +968,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro if self.incomingVideoViewRequested && self.outgoingVideoViewRequested { self.displayedCameraTooltip = true } - if self.incomingVideoViewRequested && !self.outgoingVideoViewRequested && !self.displayedCameraTooltip && toastContent.isEmpty { + if self.incomingVideoViewRequested && !self.outgoingVideoViewRequested && !self.displayedCameraTooltip && (self.toastContent?.isEmpty ?? true) { self.displayedCameraTooltip = true Queue.mainQueue().after(2.0) { self.displayCameraTooltip() @@ -1098,21 +1119,19 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro } private func calculatePreviewVideoRect(layout: ContainerViewLayout, navigationHeight: CGFloat) -> CGRect { - var uiDisplayTransition: CGFloat = self.isUIHidden ? 0.0 : 1.0 - uiDisplayTransition *= 1.0 - self.pictureInPictureTransitionFraction - let buttonsHeight: CGFloat = self.buttonsNode.bounds.height let toastHeight: CGFloat = self.toastNode.bounds.height + let toastInset = (toastHeight > 0.0 ? toastHeight + 22.0 : 0.0) var fullInsets = layout.insets(options: .statusBar) - + var cleanInsets = fullInsets - cleanInsets.bottom = layout.intrinsicInsets.bottom + cleanInsets.bottom = max(layout.intrinsicInsets.bottom, 20.0) + toastInset cleanInsets.left = 20.0 cleanInsets.right = 20.0 fullInsets.top += 44.0 + 8.0 - fullInsets.bottom = buttonsHeight + toastHeight + 27.0 + fullInsets.bottom = buttonsHeight + 22.0 + toastInset fullInsets.left = 20.0 fullInsets.right = 20.0 @@ -1179,8 +1198,17 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.validLayout = (layout, navigationBarHeight) - var uiDisplayTransition: CGFloat = self.isUIHidden ? 0.0 : 1.0 - uiDisplayTransition *= 1.0 - self.pictureInPictureTransitionFraction + var isUIHidden = self.isUIHidden + switch self.callState?.state { + case .terminated, .terminating: + isUIHidden = false + default: + break + } + + var uiDisplayTransition: CGFloat = isUIHidden ? 0.0 : 1.0 + let pipTransitionAlpha: CGFloat = 1.0 - self.pictureInPictureTransitionFraction + uiDisplayTransition *= pipTransitionAlpha let previousVideoButtonFrame = self.buttonsNode.videoButtonFrame().flatMap { frame -> CGRect in return self.buttonsNode.view.convert(frame, to: self.view) @@ -1193,15 +1221,22 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro buttonsHeight = 0.0 } let defaultButtonsOriginY = layout.size.height - buttonsHeight - let buttonsOriginY = interpolate(from: layout.size.height + 10.0, to: defaultButtonsOriginY, value: uiDisplayTransition) + let buttonsCollapsedOriginY = self.pictureInPictureTransitionFraction > 0.0 ? layout.size.height + 30.0 : layout.size.height + 10.0 + let buttonsOriginY = interpolate(from: buttonsCollapsedOriginY, to: defaultButtonsOriginY, value: uiDisplayTransition) let toastHeight = self.toastNode.updateLayout(strings: self.presentationData.strings, content: self.toastContent, constrainedWidth: layout.size.width, bottomInset: layout.intrinsicInsets.bottom + buttonsHeight, transition: transition) + let toastSpacing: CGFloat = 22.0 + let toastCollapsedOriginY = self.pictureInPictureTransitionFraction > 0.0 ? layout.size.height : layout.size.height - max(layout.intrinsicInsets.bottom, 20.0) - toastHeight + let toastOriginY = interpolate(from: toastCollapsedOriginY, to: defaultButtonsOriginY - toastSpacing - toastHeight, value: uiDisplayTransition) + var overlayAlpha: CGFloat = uiDisplayTransition + var toastAlpha: CGFloat = pipTransitionAlpha switch self.callState?.state { case .terminated, .terminating: overlayAlpha *= 0.5 + toastAlpha *= 0.5 default: break } @@ -1229,15 +1264,17 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro apply() let navigationOffset: CGFloat = max(20.0, layout.safeInsets.top) + let topOriginY = interpolate(from: -20.0, to: navigationOffset, value: uiDisplayTransition) let backSize = self.backButtonNode.measure(CGSize(width: 320.0, height: 100.0)) if let image = self.backButtonArrowNode.image { - transition.updateFrame(node: self.backButtonArrowNode, frame: CGRect(origin: CGPoint(x: 10.0, y: navigationOffset + 11.0), size: image.size)) + transition.updateFrame(node: self.backButtonArrowNode, frame: CGRect(origin: CGPoint(x: 10.0, y: topOriginY + 11.0), size: image.size)) } - transition.updateFrame(node: self.backButtonNode, frame: CGRect(origin: CGPoint(x: 29.0, y: navigationOffset + 11.0), size: backSize)) + transition.updateFrame(node: self.backButtonNode, frame: CGRect(origin: CGPoint(x: 29.0, y: topOriginY + 11.0), size: backSize)) transition.updateAlpha(node: self.backButtonArrowNode, alpha: overlayAlpha) transition.updateAlpha(node: self.backButtonNode, alpha: overlayAlpha) + transition.updateAlpha(node: self.toastNode, alpha: toastAlpha) var statusOffset: CGFloat if layout.metrics.widthClass == .regular && layout.metrics.heightClass == .regular { @@ -1265,7 +1302,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro let videoPausedSize = self.videoPausedNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: 100.0)) transition.updateFrame(node: self.videoPausedNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - videoPausedSize.width) / 2.0), y: floor((layout.size.height - videoPausedSize.height) / 2.0)), size: videoPausedSize)) - transition.updateFrame(node: self.toastNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY - toastHeight), size: CGSize(width: layout.size.width, height: toastHeight))) + transition.updateFrame(node: self.toastNode, frame: CGRect(origin: CGPoint(x: 0.0, y: toastOriginY), size: CGSize(width: layout.size.width, height: toastHeight))) transition.updateFrame(node: self.buttonsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight))) transition.updateAlpha(node: self.buttonsNode, alpha: overlayAlpha) @@ -1362,7 +1399,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro } let keyTextSize = self.keyButtonNode.frame.size - transition.updateFrame(node: self.keyButtonNode, frame: CGRect(origin: CGPoint(x: layout.size.width - keyTextSize.width - 8.0, y: navigationOffset + 8.0), size: keyTextSize)) + transition.updateFrame(node: self.keyButtonNode, frame: CGRect(origin: CGPoint(x: layout.size.width - keyTextSize.width - 8.0, y: topOriginY + 8.0), size: keyTextSize)) transition.updateAlpha(node: self.keyButtonNode, alpha: overlayAlpha) if let debugNode = self.debugNode { @@ -1719,7 +1756,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro case .collapsing: self.pictureInPictureGestureState = .none let velocity = recognizer.velocity(in: self.view).y - if abs(velocity) < 100.0 { + if abs(velocity) < 100.0 && self.pictureInPictureTransitionFraction < 0.5 { if let (layout, navigationHeight) = self.validLayout { self.pictureInPictureTransitionFraction = 0.0 diff --git a/submodules/TelegramCallsUI/Sources/CallControllerToastNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerToastNode.swift index 8ae2d1f817..377bf3073c 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerToastNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerToastNode.swift @@ -49,6 +49,7 @@ struct CallControllerToastContent: OptionSet { final class CallControllerToastContainerNode: ASDisplayNode { private var toastNodes: [ToastDescription.Key: CallControllerToastItemNode] = [:] + private var visibleToastNodes: [CallControllerToastItemNode] = [] private let strings: PresentationStrings @@ -76,7 +77,6 @@ final class CallControllerToastContainerNode: ASDisplayNode { self.appliedContent = content let spacing: CGFloat = 18.0 - let bottomSpacing: CGFloat = 22.0 var height: CGFloat = 0.0 var toasts: [ToastDescription] = [] @@ -107,6 +107,7 @@ final class CallControllerToastContainerNode: ASDisplayNode { toastNode = CallControllerToastItemNode() self.toastNodes[toast.key] = toastNode self.addSubnode(toastNode) + self.visibleToastNodes.append(toastNode) toastTransition = .immediate animateIn = transition.isAnimated } @@ -142,15 +143,16 @@ final class CallControllerToastContainerNode: ASDisplayNode { } var removedKeys: [ToastDescription.Key] = [] - for (key, toast) in self.toastNodes { + for (key, toastNode) in self.toastNodes { if !validKeys.contains(key) { removedKeys.append(key) + self.visibleToastNodes.removeAll { $0 === toastNode } if animated { - toast.animateOut(transition: transition) { [weak toast] in - toast?.removeFromSupernode() + toastNode.animateOut(transition: transition) { [weak toastNode] in + toastNode?.removeFromSupernode() } } else { - toast.removeFromSupernode() + toastNode.removeFromSupernode() } } } @@ -158,11 +160,7 @@ final class CallControllerToastContainerNode: ASDisplayNode { self.toastNodes.removeValue(forKey: key) } - guard let subnodes = self.subnodes else { - return 0.0 - } - - for case let toastNode as CallControllerToastItemNode in subnodes.reversed() { + for toastNode in self.visibleToastNodes { if let content = toastNode.currentContent, let (transition, toastHeight, animateIn) = transitions[content.key] { transition.updateFrame(node: toastNode, frame: CGRect(x: 0.0, y: height, width: width, height: toastHeight)) height += toastHeight + spacing @@ -175,7 +173,6 @@ final class CallControllerToastContainerNode: ASDisplayNode { if height > 0.0 { height -= spacing } - height += bottomSpacing return height } @@ -299,8 +296,19 @@ private class CallControllerToastItemNode: ASDisplayNode { } func animateIn() { + let targetFrame = self.clipNode.frame + let initialFrame = CGRect(x: floor((self.frame.width - 44.0) / 2.0), y: 0.0, width: 44.0, height: 28.0) + + self.clipNode.frame = initialFrame +// self.effectView.frame = CGRect(origin: CGPoint(), size: initialFrame.size) + + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45, damping: 105.0, completion: { _ in + self.clipNode.frame = targetFrame +// self.effectView.frame = CGRect(origin: CGPoint(), size: targetFrame.size) + self.clipNode.layer.animateFrame(from: initialFrame, to: targetFrame, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring) +// self.effectView.layer.animateFrame(from: initialFrame, to: targetFrame, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring) }) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 02e5f024e9..0991c128c6 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -634,13 +634,17 @@ public final class SharedAccountContextImpl: SharedAccountContext { if let strongSelf = self { let resolvedText: CallStatusText if let state = state { - switch state.state { - case .connecting, .requesting, .terminating, .ringing, .waiting: - resolvedText = .inProgress(nil) - case .terminated: - resolvedText = .none - case .active(let timestamp, _, _), .reconnecting(let timestamp, _, _): - resolvedText = .inProgress(timestamp) + if [.active, .paused].contains(state.videoState) || [.active, .paused].contains(state.remoteVideoState) { + resolvedText = .none + } else { + switch state.state { + case .connecting, .requesting, .terminating, .ringing, .waiting: + resolvedText = .inProgress(nil) + case .terminated: + resolvedText = .none + case .active(let timestamp, _, _), .reconnecting(let timestamp, _, _): + resolvedText = .inProgress(timestamp) + } } } else { resolvedText = .none diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 9e425c1fbd..085aa35ec8 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 9e425c1fbd34e544f831a612608fce1da1c73200 +Subproject commit 085aa35ec8cd688c18a7cb26f771bf9f684417d2