diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 3df609da76..230e8b1b8d 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -57,6 +57,7 @@ swift_library( "//submodules/GridMessageSelectionNode:GridMessageSelectionNode", "//submodules/ChatListFilterSettingsHeaderItem:ChatListFilterSettingsHeaderItem", "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/TelegramCallsUI:TelegramCallsUI", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index ae2d56b5dd..2858421307 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -23,6 +23,7 @@ import AppBundle import LocalizedPeerData import TelegramIntents import TooltipUI +import TelegramCallsUI private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool { if listNode.scroller.isDragging { @@ -1727,6 +1728,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController (strongSelf.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.4, curve: .spring)) }) + + if let navigationController = self.navigationController as? NavigationController { + var voiceChatOverlayController: VoiceChatOverlayController? + for controller in navigationController.globalOverlayControllers { + if let controller = controller as? VoiceChatOverlayController { + voiceChatOverlayController = controller + break + } + } + + if let controller = voiceChatOverlayController { + controller.update(hidden: true, slide: true, animated: true) + } + } } } @@ -1769,6 +1784,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.tabContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -64.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) } } + + if let navigationController = self.navigationController as? NavigationController { + var voiceChatOverlayController: VoiceChatOverlayController? + for controller in navigationController.globalOverlayControllers { + if let controller = controller as? VoiceChatOverlayController { + voiceChatOverlayController = controller + break + } + } + + if let controller = voiceChatOverlayController { + controller.update(hidden: false, slide: true, animated: true) + } + } } } diff --git a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift index cfeef09cf5..e70ae03553 100644 --- a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift @@ -26,22 +26,34 @@ private class CallStatusBarBackgroundNode: ASDisplayNode { } } - var speaking = false { + var connectingColor: UIColor = UIColor(rgb: 0xb6b6bb) { didSet { - if self.speaking != oldValue { - let initialColors = self.foregroundGradientLayer.colors - let targetColors: [CGColor] - if speaking { - targetColors = [green.cgColor, blue.cgColor] - } else { - targetColors = [blue.cgColor, lightBlue.cgColor] - } - self.foregroundGradientLayer.colors = targetColors - self.foregroundGradientLayer.animate(from: initialColors as AnyObject, to: targetColors as AnyObject, keyPath: "colors", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3) + if self.connectingColor.rgb != oldValue.rgb { + self.updateGradientColors() } } } + var speaking: Bool? = nil { + didSet { + if self.speaking != oldValue { + self.updateGradientColors() + } + } + } + + private func updateGradientColors() { + let initialColors = self.foregroundGradientLayer.colors + let targetColors: [CGColor] + if let speaking = self.speaking { + targetColors = speaking ? [green.cgColor, blue.cgColor] : [blue.cgColor, lightBlue.cgColor] + } else { + targetColors = [connectingColor.cgColor, connectingColor.cgColor] + } + self.foregroundGradientLayer.colors = targetColors + self.foregroundGradientLayer.animate(from: initialColors as AnyObject, to: targetColors as AnyObject, keyPath: "colors", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3) + } + private let hierarchyTrackingNode: HierarchyTrackingNode private var isCurrentlyInHierarchy = true @@ -145,7 +157,6 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { } private let backgroundNode: CallStatusBarBackgroundNode - private let microphoneNode: VoiceChatMicrophoneNode private let titleNode: ImmediateTextNode private let subtitleNode: ImmediateAnimatedCountLabelNode @@ -156,8 +167,9 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { private var currentSize: CGSize? private var currentContent: Content? - private var strings: PresentationStrings? - private var nameDisplayOrder: PresentationPersonNameOrder = .firstLast + private var presentationData: PresentationData? + private let presentationDataDisposable = MetaDisposable() + private var currentPeer: Peer? private var currentCallTimer: SwiftSignalKit.Timer? private var currentCallState: PresentationCallState? @@ -167,7 +179,6 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { public override init() { self.backgroundNode = CallStatusBarBackgroundNode() - self.microphoneNode = VoiceChatMicrophoneNode() self.titleNode = ImmediateTextNode() self.subtitleNode = ImmediateAnimatedCountLabelNode() self.subtitleNode.reverseAnimationDirection = true @@ -180,6 +191,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { } deinit { + self.presentationDataDisposable.dispose() self.audioLevelDisposable.dispose() self.stateDisposable.dispose() self.currentCallTimer?.invalidate() @@ -203,11 +215,10 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { let wasEmpty = (self.titleNode.attributedText?.string ?? "").isEmpty if !self.didSetupData { + self.didSetupData = true switch content { case let .call(sharedContext, account, call): - let presentationData = sharedContext.currentPresentationData.with { $0 } - self.strings = presentationData.strings - self.nameDisplayOrder = presentationData.nameDisplayOrder + self.presentationData = sharedContext.currentPresentationData.with { $0 } self.stateDisposable.set( (combineLatest( account.postbox.loadedPeerWithId(call.peerId), @@ -223,9 +234,14 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { } })) case let .groupCall(sharedContext, account, call): - let presentationData = sharedContext.currentPresentationData.with { $0 } - self.strings = presentationData.strings - self.nameDisplayOrder = presentationData.nameDisplayOrder + self.presentationData = sharedContext.currentPresentationData.with { $0 } + self.presentationDataDisposable.set((sharedContext.presentationData + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + strongSelf.presentationData = presentationData + strongSelf.update() + } + })) self.stateDisposable.set( (combineLatest( account.postbox.peerView(id: call.peerId), @@ -238,7 +254,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { strongSelf.currentGroupCallState = state var isMuted = isMuted - if let state = state, let muteState = state.callState.muteState, !muteState.canUnmute { + if let state = state, state.callState.muteState != nil { isMuted = true } strongSelf.currentIsMuted = isMuted @@ -268,7 +284,6 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { strongSelf.backgroundNode.audioLevel = effectiveLevel })) } - self.didSetupData = true } var title: String = "" @@ -277,9 +292,9 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { let textColor = UIColor.white var segments: [AnimatedCountLabelNode.Segment] = [] - if let strings = self.strings { + if let presentationData = self.presentationData { if let currentPeer = self.currentPeer { - title = currentPeer.displayTitle(strings: strings, displayOrder: self.nameDisplayOrder) + title = currentPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } var membersCount: Int32? if let groupCallState = self.currentGroupCallState { @@ -289,12 +304,12 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { } if let membersCount = membersCount { - var membersPart = strings.VoiceChat_Status_Members(membersCount) + var membersPart = presentationData.strings.VoiceChat_Status_Members(membersCount) if let startIndex = membersPart.firstIndex(of: "["), let endIndex = membersPart.firstIndex(of: "]") { membersPart.removeSubrange(startIndex ... endIndex) } - let rawTextAndRanges = strings.VoiceChat_Status_MembersFormat("\(membersCount)", membersPart) + let rawTextAndRanges = presentationData.strings.VoiceChat_Status_MembersFormat("\(membersCount)", membersPart) let (rawText, ranges) = rawTextAndRanges var textIndex = 0 @@ -326,6 +341,8 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { textIndex += 1 } } + + self.backgroundNode.connectingColor = presentationData.theme.chatList.unreadBadgeInactiveBackgroundColor } if self.subtitleNode.segments != segments { @@ -334,8 +351,6 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(13.0), textColor: .white) - let animationSize: CGFloat = 25.0 - let iconSpacing: CGFloat = 0.0 let spacing: CGFloat = 5.0 let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: size.height)) let subtitleSize = self.subtitleNode.updateLayout(size: CGSize(width: 160.0, height: size.height), animated: true) @@ -347,14 +362,10 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { let verticalOrigin: CGFloat = size.height - contentHeight let transition: ContainedViewLayoutTransition = wasEmpty ? .immediate : .animated(duration: 0.2, curve: .easeInOut) - -// transition.updateFrame(node: self.microphoneNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin + floor((contentHeight - animationSize) / 2.0)), size: CGSize(width: animationSize, height: animationSize))) -// self.microphoneNode.update(state: VoiceChatMicrophoneNode.State(muted: self.currentIsMuted, color: UIColor.white), animated: true) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin + floor((contentHeight - titleSize.height) / 2.0)), size: titleSize)) transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - subtitleSize.height) / 2.0)), size: subtitleSize)) - self.backgroundNode.speaking = self.currentIsConnected && !self.currentIsMuted + self.backgroundNode.speaking = self.currentIsConnected ? !self.currentIsMuted : nil self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 18.0)) } } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift b/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift index 30f6658836..757fdfeeb9 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift @@ -43,12 +43,21 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode { var outerColor: Signal { return outerColorPromise.get() } + + var connectingColor: UIColor = UIColor(rgb: 0xb6b6bb) { + didSet { + self.backgroundNode.connectingColor = self.connectingColor + } + } + var activeDisposable = MetaDisposable() + var isDisabled: Bool = false + var wasActiveWhenPressed = false var pressing: Bool = false { didSet { - guard let (_, _, state, _, _, _, _, snap) = self.currentParams else { + guard let (_, _, state, _, _, _, _, snap) = self.currentParams, !self.isDisabled else { return } if self.pressing { @@ -93,7 +102,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode { self.highligthedChanged = { [weak self] pressing in if let strongSelf = self { - guard let (_, _, _, _, _, _, _, snap) = strongSelf.currentParams else { + guard let (_, _, _, _, _, _, _, snap) = strongSelf.currentParams, !strongSelf.isDisabled else { return } if pressing { @@ -184,11 +193,13 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode { transition.updateTransformScale(node: self.iconNode, scale: 0.5) transition.updateAlpha(node: self.titleLabel, alpha: 0.0) transition.updateAlpha(node: self.subtitleLabel, alpha: 0.0) + transition.updateAlpha(layer: self.maskProgressLayer, alpha: 0.0) } else { transition.updateTransformScale(node: self.backgroundNode, scale: small ? 0.85 : 1.0) transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? 0.9 : 1.0) transition.updateAlpha(node: self.titleLabel, alpha: 1.0) transition.updateAlpha(node: self.subtitleLabel, alpha: 1.0) + transition.updateAlpha(layer: self.maskProgressLayer, alpha: 1.0) } let iconSize = CGSize(width: 90.0, height: 90.0) @@ -202,8 +213,9 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode { if let previous = self.currentParams { self.currentParams = (previous.size, previous.buttonSize, previous.state, previous.dark, previous.small, previous.title, previous.subtitle, snap) + self.backgroundNode.isSnap = snap self.backgroundNode.glowHidden = snap - + self.backgroundNode.updateColors() self.applyParams(animated: animated) } } @@ -212,7 +224,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode { let previous = self.currentParams let previousState = previous?.state self.currentParams = (size, buttonSize, state, dark, small, title, subtitle, previous?.snap ?? false) - + var iconMuted = true var iconColor: UIColor = .white var backgroundState: VoiceChatActionButtonBackgroundNode.State @@ -231,7 +243,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode { case .connecting: backgroundState = .connecting } - self.backgroundNode.updateColor(dark: dark) + self.backgroundNode.isDark = dark self.backgroundNode.update(state: backgroundState, animated: true) self.iconNode.update(state: VoiceChatMicrophoneNode.State(muted: iconMuted, color: iconColor), animated: true) @@ -819,6 +831,13 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode { switch self.state { case .connecting: self.updatedActive?(false) + if let transition = self.transition { + self.updateGlowScale(nil) + if case .blob = transition { + playBlobsDisappearanceAnimation() + } + self.transition = nil + } self.setupProgressAnimations() self.isActive = false case let .blob(newActive): @@ -858,10 +877,36 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode { } } - func updateColor(dark: Bool) { + var isDark: Bool = false { + didSet { + if self.isDark != oldValue { + self.updateColors() + } + } + } + + var isSnap: Bool = false { + didSet { + if self.isSnap != oldValue { + self.updateColors() + } + } + } + + var connectingColor: UIColor = UIColor(rgb: 0xb6b6bb) { + didSet { + if self.connectingColor.rgb != oldValue.rgb { + self.updateColors() + } + } + } + + fileprivate func updateColors() { let previousColor: CGColor = self.backgroundCircleLayer.fillColor ?? greyColor.cgColor let targetColor: CGColor - if dark { + if self.isSnap { + targetColor = self.connectingColor.cgColor + } else if self.isDark { targetColor = secondaryGreyColor.cgColor } else { targetColor = greyColor.cgColor diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 0158fd674f..22417cf647 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -376,6 +376,7 @@ public final class VoiceChatController: ViewController { private let context: AccountContext private let call: PresentationGroupCall private var presentationData: PresentationData + private var presentationDataDisposable: Disposable? private var darkTheme: PresentationTheme private let dimNode: ASDisplayNode @@ -832,6 +833,14 @@ public final class VoiceChatController: ViewController { } } + self.presentationDataDisposable = (sharedContext.presentationData + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + strongSelf.presentationData = presentationData + strongSelf.actionButton.connectingColor = presentationData.theme.chatList.unreadBadgeInactiveBackgroundColor + } + }) + self.memberStatesDisposable = (combineLatest(queue: .mainQueue(), self.call.state, self.call.members, @@ -1092,6 +1101,7 @@ public final class VoiceChatController: ViewController { } deinit { + self.presentationDataDisposable?.dispose() self.peerViewDisposable?.dispose() self.leaveDisposable.dispose() self.isMutedDisposable?.dispose() @@ -1184,6 +1194,9 @@ public final class VoiceChatController: ViewController { guard let callState = self.callState else { return } + if case .connecting = callState.networkState { + return + } if let muteState = callState.muteState { if !muteState.canUnmute { if case .ended = gestureRecognizer.state { @@ -1580,7 +1593,7 @@ public final class VoiceChatController: ViewController { actionButtonEnabled = false } - self.actionButton.isUserInteractionEnabled = actionButtonEnabled + self.actionButton.isDisabled = !actionButtonEnabled self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 144.0, height: 144.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, dark: self.isFullscreen, small: layout.size.width < 330.0, animated: true) if self.actionButton.supernode === self.bottomPanelNode { @@ -1975,7 +1988,7 @@ public final class VoiceChatController: ViewController { if let navigationController = self.navigationController as? NavigationController { let count = navigationController.viewControllers.count if count == 2 || navigationController.viewControllers[count - 2] is ChatController { - self.detachActionButton() + self.detachActionButton() } } } else { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index 3c75eee6b0..496e9f6065 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -159,7 +159,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { private let actionContainerNode: ASDisplayNode private var animationNode: VoiceChatMicrophoneNode? private var iconNode: ASImageNode? - private var actionButtonNode: HighlightTrackingButtonNode + private var actionButtonNode: HighlightableButtonNode private var audioLevelView: VoiceBlobView? private let audioLevelDisposable = MetaDisposable() @@ -201,7 +201,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { self.statusNode.contentsScale = UIScreen.main.scale self.actionContainerNode = ASDisplayNode() - self.actionButtonNode = HighlightTrackingButtonNode() + self.actionButtonNode = HighlightableButtonNode() self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true @@ -613,7 +613,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { } } animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, color: color), animated: true) - strongSelf.actionButtonNode.isUserInteractionEnabled = false + strongSelf.actionButtonNode.isUserInteractionEnabled = item.contextAction != nil } else if let animationNode = strongSelf.animationNode { strongSelf.animationNode = nil animationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) @@ -643,7 +643,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { } else { iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: item.presentationData.theme.list.itemAccentColor) } - strongSelf.actionButtonNode.isUserInteractionEnabled = !invited + strongSelf.actionButtonNode.isUserInteractionEnabled = false } else if let iconNode = strongSelf.iconNode { strongSelf.iconNode = nil iconNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) @@ -734,8 +734,8 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { } @objc private func actionButtonPressed() { - if let item = self.layoutParams?.0 { - item.action?() + if let item = self.layoutParams?.0, let contextAction = item.contextAction { + contextAction(self.contextSourceNode, nil) } } diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index b21e39473c..a1426c628d 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -1245,11 +1245,7 @@ final class SharedApplicationContext { |> map { loggedOutAccountPeerIds -> (AccountManager, Set) in return (sharedContext.sharedContext.accountManager, loggedOutAccountPeerIds) } - }).start(next: { [weak self] accountManager, loggedOutAccountPeerIds in - guard let strongSelf = self else { - return - } - + }).start(next: { accountManager, loggedOutAccountPeerIds in let _ = (updateIntentsSettingsInteractively(accountManager: accountManager) { current in var updated = current for peerId in loggedOutAccountPeerIds { @@ -1888,7 +1884,7 @@ final class SharedApplicationContext { |> take(1) |> deliverOnMainQueue).start(next: { sharedContext in let type = ApplicationShortcutItemType(rawValue: shortcutItem.type) - var immediately = type == .account + let immediately = type == .account let proceed: () -> Void = { let _ = (self.context.get() |> take(1) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift index e2ed66dfc4..3b86d1d263 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift @@ -234,9 +234,11 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { if !animated { placeholderNode.removeFromSupernode() } else { + placeholderNode.allowsGroupOpacity = true placeholderNode.alpha = 0.0 placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in placeholderNode?.removeFromSupernode() + placeholderNode?.allowsGroupOpacity = false }) } }