diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 160256a2ed..ce9e20e2d9 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1353,7 +1353,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { case let .peer(_, renderedPeer, _, _, presence, _ ,_ ,_, _, _, displayAsMessage, _): if !displayAsMessage { if let peer = renderedPeer.peer as? TelegramUser, let presence = presence as? TelegramUserPresence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != item.context.account.peerId { - var updatedPresence = TelegramUserPresence(status: presence.status, lastActivity: 0) + let updatedPresence = TelegramUserPresence(status: presence.status, lastActivity: 0) let relativeStatus = relativeUserPresenceStatus(updatedPresence, relativeTo: timestamp) if case .online = relativeStatus { online = true @@ -1361,10 +1361,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { animateOnline = true } else if let channel = renderedPeer.peer as? TelegramChannel { onlineIsVoiceChat = true - if channel.flags.contains(.hasVoiceChat) { + if channel.flags.contains(.hasVoiceChat) && item.interaction.searchTextHighightState == nil { online = true - animateOnline = true } + animateOnline = true } } diff --git a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift index 88f2e5775d..8a87ac809a 100644 --- a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift @@ -47,7 +47,15 @@ private final class Curve { let minSpeed: CGFloat let maxSpeed: CGFloat - var size: CGSize + var size: CGSize { + didSet { + if self.size != oldValue { + self.fromPoints = nil + self.toPoints = nil + self.animateToNewShape() + } + } + } let alpha: CGFloat var currentOffset: CGFloat = 1.0 var minOffset: CGFloat = 0.0 @@ -177,7 +185,7 @@ private final class Curve { private func generateNextCurve(for size: CGSize) -> [CGPoint] { let randomness = minRandomness + (maxRandomness - minRandomness) * speedLevel return curve(pointsCount: pointsCount, randomness: randomness).map { - return CGPoint(x: $0.x * CGFloat(size.width), y: size.height - 14.0 + $0.y * 12.0) + return CGPoint(x: $0.x * CGFloat(size.width), y: size.height - 17.0 + $0.y * 12.0) } } @@ -545,6 +553,6 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { self.subtitleNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin + animationSize + iconSpacing + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - subtitleSize.height) / 2.0)), size: subtitleSize) self.backgroundNode.speaking = !self.currentIsMuted - self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 14.0)) + self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 17.0)) } } diff --git a/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift b/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift index 919cd8dfe5..0eadfb7230 100644 --- a/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift +++ b/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift @@ -71,6 +71,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode { let titleNode: ImmediateTextNode let textNode: ImmediateTextNode + private var textIsActive = false private let muteIconNode: ASImageNode private let avatarsContext: AnimatedAvatarSetContext @@ -229,6 +230,22 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode { self.muteIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(presentationData.theme) } + private func animateTextChange() { + if let snapshotView = self.textNode.view.snapshotContentTree() { + let offset: CGFloat = self.textIsActive ? -7.0 : 7.0 + self.textNode.view.superview?.insertSubview(snapshotView, belowSubview: self.textNode.view) + + snapshotView.frame = self.textNode.frame + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -offset), duration: 0.2, removeOnCompletion: false, additive: true) + + self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.textNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.2, additive: true) + } + } + public func update(data: GroupCallPanelData) { let previousData = self.currentData self.currentData = data @@ -271,6 +288,11 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode { membersTextIsActive = false } + if strongSelf.textIsActive != membersTextIsActive { + strongSelf.textIsActive = membersTextIsActive + strongSelf.animateTextChange() + } + strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: membersTextIsActive ? strongSelf.theme.chat.inputPanel.panelControlAccentColor : strongSelf.theme.chat.inputPanel.secondaryTextColor) strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }, animated: false) @@ -368,6 +390,11 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode { membersTextIsActive = false } + if self.textIsActive != membersTextIsActive { + self.textIsActive = membersTextIsActive + self.animateTextChange() + } + self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: membersTextIsActive ? self.theme.chat.inputPanel.panelControlAccentColor : self.theme.chat.inputPanel.secondaryTextColor) self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false) @@ -481,7 +508,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode { let foregroundFrame = self.micButtonForegroundNode.view.convert(self.micButtonForegroundNode.bounds, to: nil) let backgroundView = UIView() - backgroundView.backgroundColor = UIColor(rgb: 0x30b251) + backgroundView.backgroundColor = (self.micButtonBackgroundNodeIsMuted ?? true) ? UIColor(rgb: 0xb6b6bb) : UIColor(rgb: 0x30b251) backgroundView.frame = backgroundFrame backgroundView.layer.cornerRadius = backgroundFrame.height / 2.0 diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift b/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift index be3cac8124..2905e1deee 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift @@ -576,7 +576,9 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode { self.animator = animator } animator.isPaused = false - animator.frameInterval = state.frameInterval + if self.transition == nil { + animator.frameInterval = state.frameInterval + } } else { self.animator?.isPaused = true } @@ -619,7 +621,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode { var appearanceProgress: CGFloat = 1.0 var glowScale: CGFloat = 0.75 - if let transition = parameters.transition, transition.previousState == .connecting { + if let transition = parameters.transition, transition.previousState == .connecting || transition.previousState == .disabled { appearanceProgress = transition.transition } @@ -751,15 +753,18 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode { } } - var clearInside: CGFloat? + var clearInsideTransition: CGFloat? if parameters.state is VoiceChatActionButtonBackgroundNodeBlobState { let path = CGMutablePath() path.addEllipse(in: buttonRect.insetBy(dx: -lineWidth / 2.0, dy: -lineWidth / 2.0)) context.addPath(path) context.clip() - if let transition = parameters.transition, transition.previousState == .connecting || transition.previousState == .disabled, transition.transition > 0.5 { - let progress = (transition.transition - 0.5) / 0.5 - clearInside = progress + if let transition = parameters.transition { + if transition.previousState == .connecting, transition.transition > 0.5 { + clearInsideTransition = (transition.transition - 0.5) / 0.5 + } else if transition.previousState == .disabled { + clearInsideTransition = transition.transition + } } drawGradient = true @@ -774,9 +779,9 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode { } } - if let clearInside = clearInside { + if let transition = clearInsideTransition { context.setFillColor(greyColor.cgColor) - context.fillEllipse(in: buttonRect.insetBy(dx: clearInside * radius, dy: clearInside * radius)) + context.fillEllipse(in: buttonRect.insetBy(dx: transition * radius, dy: transition * radius)) } } } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 40f2209422..26a1a1a5dc 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -89,7 +89,7 @@ private final class VoiceChatControllerTitleView: UIView { } public final class VoiceChatController: ViewController { - private final class Node: ViewControllerTracingNode { + private final class Node: ViewControllerTracingNode, UIGestureRecognizerDelegate { private struct ListTransition { let deletions: [ListViewDeleteItem] let insertions: [ListViewInsertItem] @@ -412,7 +412,7 @@ public final class VoiceChatController: ViewController { } } - if let callState = strongSelf.callState, (callState.canManageCall && !callState.adminIds.contains(strongSelf.context.account.peerId)) { + if let callState = strongSelf.callState, (callState.canManageCall && !callState.adminIds.contains(peer.id)) { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor) }, action: { [weak self] _, f in @@ -523,15 +523,19 @@ public final class VoiceChatController: ViewController { return } - if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel { - if !(channel.addressName ?? "").isEmpty || (channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers)) { - strongSelf.optionsButton.isHidden = false - } else { - strongSelf.optionsButton.isHidden = true - } - } - if !strongSelf.didSetDataReady { + if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel { + let addressName = channel.addressName ?? "" + if !addressName.isEmpty || (channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers)) { + if addressName.isEmpty { + let _ = ensuredExistingPeerExportedInvitation(account: strongSelf.context.account, peerId: call.peerId).start() + } + } else { + strongSelf.optionsButton.isUserInteractionEnabled = false + strongSelf.optionsButton.alpha = 0.0 + } + } + strongSelf.didSetDataReady = true strongSelf.controller?.dataReady.set(true) } @@ -675,8 +679,6 @@ public final class VoiceChatController: ViewController { optionsButtonItem.target = self optionsButtonItem.action = #selector(self.rightNavigationButtonAction) self.controller?.navigationItem.setRightBarButton(optionsButtonItem, animated: false) - - let _ = ensuredExistingPeerExportedInvitation(account: self.context.account, peerId: call.peerId).start() } deinit { @@ -701,6 +703,7 @@ public final class VoiceChatController: ViewController { let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.actionButtonPressGesture(_:))) longTapRecognizer.minimumPressDuration = 0.001 + longTapRecognizer.delegate = self self.actionButton.view.addGestureRecognizer(longTapRecognizer) let panRecognizer = CallPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) @@ -714,7 +717,9 @@ public final class VoiceChatController: ViewController { } @objc private func rightNavigationButtonAction() { - self.optionsButton.contextAction?(self.optionsButton.containerNode, nil) + if self.optionsButton.isUserInteractionEnabled { + self.optionsButton.contextAction?(self.optionsButton.containerNode, nil) + } } @objc private func leavePressed() { @@ -726,6 +731,14 @@ public final class VoiceChatController: ViewController { private var actionButtonPressGestureStartTime: Double = 0.0 + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if let callState = self.callState, case .connected = callState.networkState, let muteState = callState.muteState, !muteState.canUnmute { + return false + } else { + return true + } + } + @objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) { guard let callState = self.callState else { return @@ -1010,7 +1023,7 @@ public final class VoiceChatController: ViewController { } } - if let (backgroundView, foregroundView) = sourcePanel.rightButtonSnapshotViews(), !self.optionsButton.isHidden { + if let (backgroundView, foregroundView) = sourcePanel.rightButtonSnapshotViews(), self.optionsButton.isUserInteractionEnabled { self.view.addSubview(backgroundView) self.view.addSubview(foregroundView) @@ -1190,6 +1203,7 @@ public final class VoiceChatController: ViewController { memberState = .speaking } else { memberState = .listening + memberMuteState = member.muteState } } else { memberState = speakingPeers.contains(member.peer.id) ? .speaking : .listening diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatOptionsButton.swift b/submodules/TelegramCallsUI/Sources/VoiceChatOptionsButton.swift index c060e23953..efa3c2ba90 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatOptionsButton.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatOptionsButton.swift @@ -14,14 +14,14 @@ func optionsButtonImage() -> UIImage? { }) } -final class VoiceChatOptionsButton: HighlightableButtonNode { +final class VoiceChatOptionsButton: ASDisplayNode { let extractedContainerNode: ContextExtractedContentContainingNode let containerNode: ContextControllerSourceNode private let iconNode: ASImageNode var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? - init() { + override init() { self.extractedContainerNode = ContextExtractedContentContainingNode() self.containerNode = ContextControllerSourceNode() self.containerNode.isGestureEnabled = false diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index 569c3ef2d8..4320b350dd 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -379,8 +379,8 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { let verticalOffset: CGFloat = 0.0 let avatarSize: CGFloat = 40.0 - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - rightInset - 25.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - 25.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let insets = UIEdgeInsets() @@ -622,11 +622,16 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { animationNode = VoiceChatMicrophoneNode() strongSelf.animationNode = animationNode strongSelf.actionButtonNode.addSubnode(animationNode) + if let _ = strongSelf.iconNode { + animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + animationNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2) + } } animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, color: color), animated: true) strongSelf.actionButtonNode.isUserInteractionEnabled = false } else if let animationNode = strongSelf.animationNode { strongSelf.animationNode = nil + animationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) animationNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false, completion: { [weak animationNode] _ in animationNode?.removeFromSupernode() }) @@ -641,6 +646,11 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { iconNode.contentMode = .center strongSelf.iconNode = iconNode strongSelf.actionButtonNode.addSubnode(iconNode) + + if let _ = strongSelf.animationNode { + iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + iconNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2) + } } if invited { @@ -651,6 +661,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { strongSelf.actionButtonNode.isUserInteractionEnabled = !invited } else if let iconNode = strongSelf.iconNode { strongSelf.iconNode = nil + iconNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) iconNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false, completion: { [weak iconNode] _ in iconNode?.removeFromSupernode() }) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 03eb63f4b1..1b821f51ee 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -496,37 +496,35 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G break } } - case let .groupPhoneCall(callId, accessHash, duration): + case let .groupPhoneCall(callId, accessHash, nil), let .inviteToGroupPhoneCall(callId, accessHash, _): let peerId = message.id.peerId - if duration == nil { - let callResult = strongSelf.context.sharedContext.callManager?.joinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: callId, accessHash: accessHash), endCurrentIfAny: false, sourcePanel: nil) - if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { - if currentPeerId == peerId { - strongSelf.context.sharedContext.navigateToCurrentCall(sourcePanel: nil) - } else { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in - return (transaction.getPeer(peerId), currentPeerId.flatMap(transaction.getPeer)) - } - |> deliverOnMainQueue).start(next: { [weak self] peer, current in - guard let strongSelf = self else { - return - } - guard let peer = peer else { - return - } - if let current = current { - strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { - if let strongSelf = self { - let _ = strongSelf.context.sharedContext.callManager?.joinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: callId, accessHash: accessHash), endCurrentIfAny: true, sourcePanel: nil) - } - })]), in: .window(.root)) - } else { - strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { - })]), in: .window(.root)) - } - }) + let callResult = strongSelf.context.sharedContext.callManager?.joinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: callId, accessHash: accessHash), endCurrentIfAny: false, sourcePanel: nil) + if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { + if currentPeerId == peerId { + strongSelf.context.sharedContext.navigateToCurrentCall(sourcePanel: nil) + } else { + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in + return (transaction.getPeer(peerId), currentPeerId.flatMap(transaction.getPeer)) } + |> deliverOnMainQueue).start(next: { [weak self] peer, current in + guard let strongSelf = self else { + return + } + guard let peer = peer else { + return + } + if let current = current { + strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { + if let strongSelf = self { + let _ = strongSelf.context.sharedContext.callManager?.joinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: callId, accessHash: accessHash), endCurrentIfAny: true, sourcePanel: nil) + } + })]), in: .window(.root)) + } else { + strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { + })]), in: .window(.root)) + } + }) } } break