From f36bba8143bc870c8bbb7638beb601981c845104 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 12 Jun 2021 18:11:26 +0300 Subject: [PATCH] Video Chat Improvements --- .../Sources/ChatListController.swift | 2 +- .../ContextUI/Sources/ContextController.swift | 4 +- .../Sources/Items/ItemListInfoItem.swift | 34 +++--- .../ChangePhoneNumberCodeController.swift | 3 + .../ChangePhoneNumberIntroController.swift | 4 +- .../Sources/PresentationGroupCall.swift | 14 ++- .../Sources/VoiceChatController.swift | 27 ++++- .../VoiceChatFullscreenParticipantItem.swift | 21 +++- .../Sources/VoiceChatPeerProfileNode.swift | 76 +++++++----- .../Sources/VoiceChatTileItemNode.swift | 12 +- .../TelegramCore/Sources/GroupCalls.swift | 24 +++- .../TelegramCore/Sources/Suggestions.swift | 21 +++- .../TelegramUI/Sources/ChatController.swift | 2 +- .../ListItems/PeerInfoScreenInfoItem.swift | 108 ++++++++++++++++++ .../Sources/PeerInfo/PeerInfoData.swift | 7 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 50 +++++--- 16 files changed, 321 insertions(+), 88 deletions(-) create mode 100644 submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenInfoItem.swift diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 686101084e..07c1764a1f 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1217,7 +1217,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } })) - self.suggestAutoarchiveDisposable.set((getServerProvidedSuggestions(postbox: self.context.account.postbox) + self.suggestAutoarchiveDisposable.set((getServerProvidedSuggestions(account: self.context.account) |> deliverOnMainQueue).start(next: { [weak self] values in guard let strongSelf = self else { return diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index b0aea584ca..95b1ebabd7 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -857,7 +857,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false) } - let intermediateCompletion: () -> Void = { [weak contentParentNode] in + let intermediateCompletion: () -> Void = { [weak self, weak contentParentNode] in if completedEffect && completedContentNode && completedActionsNode { switch result { case .default, .custom: @@ -870,6 +870,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi break } + self?.clippingNode.view.mask = nil + completion() } } diff --git a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift index 717b8c2b51..ca15e82aa4 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift @@ -111,7 +111,7 @@ public class ItemListInfoItem: InfoListItem, ItemListItem { } } -class InfoItemNode: ListViewItemNode { +public class InfoItemNode: ListViewItemNode { private let backgroundNode: ASDisplayNode private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode @@ -128,7 +128,7 @@ class InfoItemNode: ListViewItemNode { private var item: InfoListItem? - override var canBeSelected: Bool { + public override var canBeSelected: Bool { return false } @@ -178,7 +178,7 @@ class InfoItemNode: ListViewItemNode { self.closeButton.addTarget(self, action: #selector(self.closeButtonPressed), forControlEvents: .touchUpInside) } - override func didLoad() { + public override func didLoad() { super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) @@ -204,12 +204,12 @@ class InfoItemNode: ListViewItemNode { let currentItem = self.item return { item, params, neighbors in - let leftInset: CGFloat = 15.0 + params.leftInset - let rightInset: CGFloat = 15.0 + params.rightInset + let leftInset: CGFloat = 16.0 + params.leftInset + let rightInset: CGFloat = 16.0 + params.rightInset - let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize) - let textFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize) - let textBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize) + let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize) + let textFont = Font.regular(item.presentationData.fontSize.itemListBaseLabelFontSize / 14.0 * 16.0) + let textBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseLabelFontSize / 14.0 * 16.0) let badgeFont = Font.regular(15.0) var updatedTheme: PresentationTheme? @@ -217,7 +217,7 @@ class InfoItemNode: ListViewItemNode { var updatedCloseIcon: UIImage? - let badgeDiameter: CGFloat = 20.0 + let badgeDiameter: CGFloat = 22.0 if currentItem?.presentationData.theme !== item.presentationData.theme { updatedTheme = item.presentationData.theme updatedBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.presentationData.theme.list.itemDestructiveColor) @@ -254,11 +254,11 @@ class InfoItemNode: ListViewItemNode { })) } - let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "!", font: badgeFont, textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: badgeDiameter, height: badgeDiameter), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "!", font: badgeFont, textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: CGSize(width: badgeDiameter, height: badgeDiameter), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - badgeDiameter - 8.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let contentSize = CGSize(width: params.width, height: titleLayout.size.height + textLayout.size.height + 36.0) + let contentSize = CGSize(width: params.width, height: titleLayout.size.height + textLayout.size.height + 38.0) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) return (layout, { [weak self] in @@ -338,11 +338,11 @@ class InfoItemNode: ListViewItemNode { strongSelf.closeButton.setImage(updatedCloseIcon, for: []) } - strongSelf.badgeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 15.0 + floor((titleLayout.size.height - badgeDiameter) / 2.0)), size: CGSize(width: badgeDiameter, height: badgeDiameter)) + strongSelf.badgeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 15.0), size: CGSize(width: badgeDiameter, height: badgeDiameter)) - strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.midX - labelLayout.size.width / 2.0, y: strongSelf.badgeNode.frame.minY + 1.0), size: labelLayout.size) + strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.midX - labelLayout.size.width / 2.0, y: strongSelf.badgeNode.frame.minY + 2.0 + UIScreenPixel), size: labelLayout.size) - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.maxX + 8.0, y: 15.0), size: titleLayout.size) + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.maxX + 8.0, y: 16.0), size: titleLayout.size) strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + 9.0), size: textLayout.size) @@ -352,15 +352,15 @@ class InfoItemNode: ListViewItemNode { } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + public override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + public override func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + public override func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift index 3f37c4c2a7..5cbbed6dee 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift @@ -277,6 +277,9 @@ func changePhoneNumberCodeController(context: AccountContext, phoneNumber: Strin } let presentationData = context.sharedContext.currentPresentationData.with { $0 } presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .success), nil) + + let _ = dismissServerProvidedSuggestion(account: context.account, suggestion: .validatePhoneNumber).start() + dismissImpl?() })) } diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberIntroController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberIntroController.swift index 17ea963f66..fb8ce7a756 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberIntroController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberIntroController.swift @@ -11,6 +11,7 @@ import AlertUI import PresentationDataUtils import AppBundle import Markdown +import PhoneNumberFormat private final class ChangePhoneNumberIntroControllerNode: ASDisplayNode { var presentationData: PresentationData @@ -101,7 +102,8 @@ public final class ChangePhoneNumberIntroController: ViewController { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.title = phoneNumber + let formattedPhone = formatPhoneNumber(phoneNumber) + self.title = formattedPhone self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) //self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(self.cancelPressed)) } diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 782803414c..fb4696beed 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -399,6 +399,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private var temporaryActivityRank: Int? private var temporaryRaiseHandRating: Int64? private var temporaryHasRaiseHand: Bool = false + private var temporaryJoinedVideo: Bool = true private var temporaryMuteState: GroupCallParticipantsContext.Participant.MuteState? private var internalState: InternalState = .requesting @@ -1016,7 +1017,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { activityRank: strongSelf.temporaryActivityRank, muteState: strongSelf.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false), volume: nil, - about: about + about: about, + joinedVideo: strongSelf.temporaryJoinedVideo )) participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) }) } @@ -1097,7 +1099,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { activityRank: strongSelf.temporaryActivityRank, muteState: strongSelf.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false), volume: nil, - about: about + about: about, + joinedVideo: strongSelf.temporaryJoinedVideo )) } @@ -1261,7 +1264,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { activityRank: strongSelf.temporaryActivityRank, muteState: strongSelf.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: canManageCall || !state.defaultParticipantsAreMuted.isMuted, mutedByYou: false), volume: nil, - about: about + about: about, + joinedVideo: strongSelf.temporaryJoinedVideo )) } @@ -1828,7 +1832,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { activityRank: strongSelf.temporaryActivityRank, muteState: strongSelf.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false), volume: nil, - about: about + about: about, + joinedVideo: strongSelf.temporaryJoinedVideo )) participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) }) } @@ -2213,6 +2218,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { strongSelf.temporaryRaiseHandRating = participant.raiseHandRating strongSelf.temporaryHasRaiseHand = participant.hasRaiseHand strongSelf.temporaryMuteState = participant.muteState + strongSelf.temporaryJoinedVideo = participant.joinedVideo } } strongSelf.switchToTemporaryParticipantsContext(sourceContext: participantsContext, oldMyPeerId: previousPeerId) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index e60c901e99..f37256d01f 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1441,7 +1441,7 @@ public final class VoiceChatController: ViewController { strongSelf.controller?.push(controller) }) }, peerContextAction: { [weak self] entry, sourceNode, gesture, fullscreen in - guard let strongSelf = self, let controller = strongSelf.controller, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else { + guard let strongSelf = self, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else { return } @@ -1723,14 +1723,20 @@ public final class VoiceChatController: ViewController { centerVertically = false } + var useMaskView = true + if case .fullscreen = strongSelf.displayMode { + useMaskView = false + } + let dismissPromise = ValuePromise(false) - let source = VoiceChatContextExtractedContentSource(sourceNode: sourceNode, maskView: strongSelf.transitionMaskView, keepInPlace: false, blurBackground: true, centerVertically: centerVertically, shouldBeDismissed: dismissPromise.get(), animateTransitionIn: { [weak self] in + let source = VoiceChatContextExtractedContentSource(sourceNode: sourceNode, maskView: useMaskView ? strongSelf.transitionMaskView : nil, keepInPlace: false, blurBackground: true, centerVertically: centerVertically, shouldBeDismissed: dismissPromise.get(), animateTransitionIn: { [weak self] in if let strongSelf = self { strongSelf.animatingContextMenu = true strongSelf.updateDecorationsLayout(transition: .immediate) if strongSelf.isLandscape { strongSelf.transitionMaskTopFillLayer.opacity = 1.0 } + strongSelf.transitionContainerNode.view.mask = nil strongSelf.transitionMaskBottomFillLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4, removeOnCompletion: false, completion: { [weak self] _ in Queue.mainQueue().after(0.3) { self?.transitionMaskTopFillLayer.opacity = 0.0 @@ -1748,6 +1754,7 @@ public final class VoiceChatController: ViewController { strongSelf.transitionMaskBottomFillLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, completion: { [weak self] _ in self?.animatingContextMenu = false self?.updateDecorationsLayout(transition: .immediate) + self?.transitionContainerNode.view.mask = self?.transitionMaskView }) } }) @@ -4737,6 +4744,8 @@ public final class VoiceChatController: ViewController { displayPanelVideos = self.displayPanelVideos } + var joinedVideo = true + var myEntry: VoiceChatPeerEntry? var mainEntry: VoiceChatPeerEntry? for member in callMembers.0 { @@ -4786,8 +4795,11 @@ public final class VoiceChatController: ViewController { } var memberPeer = member.peer - if member.peer.id == self.callState?.myPeerId, let user = memberPeer as? TelegramUser, let photo = self.currentUpdatingAvatar { - memberPeer = user.withUpdatedPhoto([photo]) + if member.peer.id == self.callState?.myPeerId { + joinedVideo = member.joinedVideo + if let user = memberPeer as? TelegramUser, let photo = self.currentUpdatingAvatar { + memberPeer = user.withUpdatedPhoto([photo]) + } } if let videoEndpointId = member.videoEndpointId { @@ -4929,6 +4941,13 @@ public final class VoiceChatController: ViewController { } } + if !joinedVideo && !tileItems.isEmpty || !gridTileItems.isEmpty, let peer = self.peer { + tileItems.removeAll() + gridTileItems.removeAll() + + tileItems.append(VoiceChatTileItem(account: self.context.account, peer: peer, videoEndpointId: "", videoReady: false, videoTimeouted: true, isVideoLimit: true, isPaused: false, isOwnScreencast: false, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, speaking: false, secondary: false, isTablet: false, icon: .none, text: .none, additionalText: nil, action: {}, contextAction: nil, getVideo: { _ in return nil }, getAudioLevel: nil)) + } + for member in callMembers.0 { if processedFullscreenPeerIds.contains(member.peer.id) { continue diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift index 5e9346fcee..59b7499b3f 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift @@ -358,8 +358,11 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { self.isExtracted = isExtracted if item.peer.smallProfileImage != nil { + let springDuration: Double = 0.42 + let springDamping: CGFloat = 124.0 + if isExtracted { - let profileNode = VoiceChatPeerProfileNode(context: item.context, size: extractedRect.size, peer: item.peer, text: item.text, customNode: self.videoContainerNode, additionalEntry: .single(nil), requestDismiss: { [weak self] in + let profileNode = VoiceChatPeerProfileNode(context: item.context, size: extractedRect.size, sourceSize: nonExtractedRect.size, peer: item.peer, text: item.text, customNode: self.videoContainerNode, additionalEntry: .single(nil), requestDismiss: { [weak self] in self?.contextSourceNode.requestDismiss?() }) profileNode.frame = CGRect(origin: CGPoint(), size: extractedRect.size) @@ -367,6 +370,11 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { self.contextSourceNode.contentNode.addSubnode(profileNode) profileNode.animateIn(from: self, targetRect: extractedRect, transition: transition) + var appearenceTransition = transition + if transition.isAnimated { + appearenceTransition = .animated(duration: springDuration, curve: .customSpring(damping: springDamping, initialVelocity: 0.0)) + } + appearenceTransition.updateFrame(node: profileNode, frame: extractedRect) self.contextSourceNode.contentNode.customHitTest = { [weak self] point in if let strongSelf = self, let profileNode = strongSelf.profileNode { @@ -377,9 +385,18 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { return nil } self.highlightNode.isHidden = true + self.backgroundImageNode.isHidden = true } else if let profileNode = self.profileNode { self.profileNode = nil - profileNode.animateOut(to: self, targetRect: nonExtractedRect, transition: transition) + profileNode.animateOut(to: self, targetRect: nonExtractedRect, transition: transition, completion: { [weak self] in + self?.backgroundImageNode.isHidden = false + }) + + var appearenceTransition = transition + if transition.isAnimated { + appearenceTransition = .animated(duration: 0.2, curve: .easeInOut) + } + appearenceTransition.updateFrame(node: profileNode, frame: nonExtractedRect) self.contextSourceNode.contentNode.customHitTest = nil self.highlightNode.isHidden = !item.active diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift index 08fc3989fe..ff5970a6ef 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift @@ -39,7 +39,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { private var appeared = false - init(context: AccountContext, size: CGSize, peer: Peer, text: VoiceChatParticipantItem.ParticipantText, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError>, requestDismiss: (() -> Void)?) { + init(context: AccountContext, size: CGSize, sourceSize: CGSize, peer: Peer, text: VoiceChatParticipantItem.ParticipantText, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError>, requestDismiss: (() -> Void)?) { self.context = context self.size = size self.peer = peer @@ -73,6 +73,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { } self.infoNode = ASDisplayNode() + self.infoNode.clipsToBounds = true self.titleNode = ImmediateTextNode() self.titleNode.isUserInteractionEnabled = false @@ -122,10 +123,10 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { strongSelf.avatarListNode.controlsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - self.updateInfo(size: size, animate: false) + self.updateInfo(size: size, sourceSize: sourceSize, animate: false) } - func updateInfo(size: CGSize, animate: Bool) { + func updateInfo(size: CGSize, sourceSize: CGSize, animate: Bool) { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let titleFont = Font.regular(17.0) @@ -169,7 +170,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { self.statusNode.frame = CGRect(origin: CGPoint(x: 14.0, y: titleSize.height + 3.0), size: statusLayout) let totalHeight = titleSize.height + statusLayout.height + 3.0 + 8.0 - let infoFrame = CGRect(x: 0.0, y: size.height - totalHeight, width: self.size.width, height: totalHeight) + let infoFrame = CGRect(x: 0.0, y: size.height - totalHeight, width: sourceSize.width, height: totalHeight) if animate { let springDuration: Double = !self.appeared ? 0.42 : 0.3 @@ -195,8 +196,8 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { if let sourceNode = sourceNode as? VoiceChatTileItemNode { let sourceRect = sourceNode.bounds self.backgroundImageNode.frame = sourceNode.bounds - self.updateInfo(size: sourceNode.bounds.size, animate: false) - self.updateInfo(size: targetRect.size, animate: true) + self.updateInfo(size: sourceNode.bounds.size, sourceSize: sourceNode.bounds.size, animate: false) + self.updateInfo(size: targetRect.size, sourceSize: targetRect.size, animate: true) self.backgroundImageNode.image = generateImage(CGSize(width: backgroundCornerRadius * 2.0, height: backgroundCornerRadius * 2.0), rotatedContext: { (size, context) in let bounds = CGRect(origin: CGPoint(), size: size) @@ -221,15 +222,15 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { self.avatarListContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.avatarListContainerNode.cornerRadius = targetRect.width / 2.0 - var appearenceTransition = transition + var appearanceTransition = transition if transition.isAnimated { - appearenceTransition = .animated(duration: springDuration, curve: .customSpring(damping: springDamping, initialVelocity: 0.0)) + appearanceTransition = .animated(duration: springDuration, curve: .customSpring(damping: springDamping, initialVelocity: 0.0)) } if let videoNode = sourceNode.videoNode { - videoNode.updateLayout(size: targetSize, layoutMode: .fillOrFitToSquare, transition: appearenceTransition) - appearenceTransition.updateFrame(node: videoNode, frame: CGRect(origin: CGPoint(), size: targetSize)) - appearenceTransition.updateFrame(node: sourceNode.videoContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: targetSize.width, height: targetSize.height + backgroundCornerRadius))) + videoNode.updateLayout(size: targetSize, layoutMode: .fillOrFitToSquare, transition: appearanceTransition) + appearanceTransition.updateFrame(node: videoNode, frame: CGRect(origin: CGPoint(), size: targetSize)) + appearanceTransition.updateFrame(node: sourceNode.videoContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: targetSize.width, height: targetSize.height + backgroundCornerRadius))) sourceNode.videoContainerNode.cornerRadius = backgroundCornerRadius } self.insertSubnode(sourceNode.videoContainerNode, belowSubnode: self.avatarListWrapperNode) @@ -242,11 +243,11 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { self.insertSubnode(self.videoFadeNode, aboveSubnode: sourceNode.videoContainerNode) self.view.insertSubview(snapshotView, aboveSubview: sourceNode.videoContainerNode.view) snapshotView.frame = sourceRect - appearenceTransition.updateFrame(view: snapshotView, frame: CGRect(origin: CGPoint(x: 0.0, y: targetSize.height - snapshotView.frame.size.height), size: snapshotView.frame.size)) + appearanceTransition.updateFrame(view: snapshotView, frame: CGRect(origin: CGPoint(x: 0.0, y: targetSize.height - snapshotView.frame.size.height), size: snapshotView.frame.size)) snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in snapshotView.removeFromSuperview() }) - appearenceTransition.updateFrame(node: self.videoFadeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: targetSize.height - self.videoFadeNode.frame.size.height), size: CGSize(width: targetSize.width, height: self.videoFadeNode.frame.height))) + appearanceTransition.updateFrame(node: self.videoFadeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: targetSize.height - self.videoFadeNode.frame.size.height), size: CGSize(width: targetSize.width, height: self.videoFadeNode.frame.height))) self.videoFadeNode.alpha = 0.0 self.videoFadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) } @@ -283,9 +284,9 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { } else if let sourceNode = sourceNode as? VoiceChatFullscreenParticipantItemNode { let sourceRect = sourceNode.bounds self.backgroundImageNode.frame = sourceNode.bounds - self.updateInfo(size: sourceNode.bounds.size, animate: false) - self.updateInfo(size: targetRect.size, animate: true) - + self.updateInfo(size: sourceNode.bounds.size, sourceSize: sourceNode.bounds.size, animate: false) + self.updateInfo(size: targetRect.size, sourceSize: targetRect.size, animate: true) + self.backgroundImageNode.image = generateImage(CGSize(width: backgroundCornerRadius * 2.0, height: backgroundCornerRadius * 2.0), rotatedContext: { (size, context) in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -298,8 +299,13 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { transition.updateCornerRadius(node: self.backgroundImageNode, cornerRadius: 0.0) - let initialRect = sourceNode.frame - let initialScale: CGFloat = sourceRect.width / targetRect.width + let initialRect: CGRect + if let videoNode = sourceNode.videoNode, videoNode.supernode == sourceNode.videoContainerNode, !videoNode.alpha.isZero { + initialRect = sourceRect + } else { + initialRect = sourceNode.avatarNode.frame + } + let initialScale = initialRect.width / targetRect.width let targetSize = CGSize(width: targetRect.size.width, height: targetRect.size.width) self.avatarListWrapperNode.update(size: targetSize, transition: .immediate) @@ -309,14 +315,30 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { self.avatarListContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.avatarListContainerNode.cornerRadius = targetRect.width / 2.0 - if false, let videoNode = sourceNode.videoNode { - videoNode.updateLayout(size: targetSize, layoutMode: .fillOrFitToSquare, transition: transition) - transition.updateFrame(node: videoNode, frame: CGRect(origin: CGPoint(), size: targetSize)) - transition.updateFrame(node: sourceNode.videoContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: targetSize.width, height: targetSize.height + backgroundCornerRadius))) + var appearanceTransition = transition + if transition.isAnimated { + appearanceTransition = .animated(duration: springDuration, curve: .customSpring(damping: springDamping, initialVelocity: 0.0)) + } + + if let videoNode = sourceNode.videoNode { + videoNode.updateLayout(size: targetSize, layoutMode: .fillOrFitToSquare, transition: appearanceTransition) + appearanceTransition.updateFrame(node: videoNode, frame: CGRect(origin: CGPoint(), size: targetSize)) + appearanceTransition.updateFrame(node: sourceNode.videoContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: targetSize.width, height: targetSize.height + backgroundCornerRadius))) sourceNode.videoContainerNode.cornerRadius = backgroundCornerRadius } -// self.insertSubnode(sourceNode.videoContainerNode, belowSubnode: self.avatarListWrapperNode) + self.insertSubnode(sourceNode.videoContainerNode, belowSubnode: self.avatarListWrapperNode) + let transitionNode = ASImageNode() + transitionNode.clipsToBounds = true + transitionNode.displaysAsynchronously = false + transitionNode.displayWithoutProcessing = true + transitionNode.image = sourceNode.avatarNode.unroundedImage + transitionNode.frame = CGRect(origin: CGPoint(), size: targetSize) + transitionNode.cornerRadius = targetRect.width / 2.0 + radiusTransition.updateCornerRadius(node: transitionNode, cornerRadius: 0.0) + + sourceNode.avatarNode.isHidden = true + self.avatarListWrapperNode.contentNode.insertSubnode(transitionNode, at: 0) // if let snapshotView = sourceNode.infoNode.view.snapshotView(afterScreenUpdates: false) { // self.videoFadeNode.image = sourceNode.fadeNode.image // self.videoFadeNode.frame = CGRect(x: 0.0, y: sourceRect.height - sourceNode.fadeNode.frame.height, width: sourceRect.width, height: sourceNode.fadeNode.frame.height) @@ -337,7 +359,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { self.avatarListWrapperNode.layer.animateSpring(from: NSValue(cgPoint: initialRect.center), to: NSValue(cgPoint: self.avatarListWrapperNode.position), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, completion: { [weak self] _ in if let strongSelf = self { strongSelf.avatarListNode.updateCustomItemsOnlySynchronously = false -// strongSelf.avatarListNode.currentItemNode?.addSubnode(sourceNode.videoContainerNode) + strongSelf.avatarListNode.currentItemNode?.addSubnode(sourceNode.videoContainerNode) } }) @@ -372,7 +394,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { let springDamping: CGFloat = 1000.0 if let targetNode = targetNode as? VoiceChatTileItemNode { let initialSize = self.bounds - self.updateInfo(size: targetRect.size, animate: true) + self.updateInfo(size: targetRect.size, sourceSize: targetRect.size, animate: true) transition.updateCornerRadius(node: self.backgroundImageNode, cornerRadius: backgroundCornerRadius) @@ -429,7 +451,9 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { self.infoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) } else if let targetNode = targetNode as? VoiceChatFullscreenParticipantItemNode { let initialSize = self.bounds - self.updateInfo(size: targetRect.size, animate: true) + self.updateInfo(size: targetRect.size, sourceSize: targetRect.size, animate: true) + + targetNode.avatarNode.isHidden = false transition.updateCornerRadius(node: self.backgroundImageNode, cornerRadius: backgroundCornerRadius) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift index ceddf2d60c..bbf8f17a3b 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift @@ -267,7 +267,7 @@ final class VoiceChatTileItemNode: ASDisplayNode { let springDuration: Double = 0.42 let springDamping: CGFloat = 124.0 if isExtracted { - let profileNode = VoiceChatPeerProfileNode(context: self.context, size: extractedRect.size, peer: item.peer, text: item.text, customNode: self.videoContainerNode, additionalEntry: .single(nil), requestDismiss: { [weak self] in + let profileNode = VoiceChatPeerProfileNode(context: self.context, size: extractedRect.size, sourceSize: nonExtractedRect.size, peer: item.peer, text: item.text, customNode: self.videoContainerNode, additionalEntry: .single(nil), requestDismiss: { [weak self] in self?.contextSourceNode.requestDismiss?() }) profileNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) @@ -411,7 +411,11 @@ final class VoiceChatTileItemNode: ASDisplayNode { self.videoNode?.updateIsBlurred(isBlurred: item.isPaused, light: true) var showPlaceholder = false - if item.isOwnScreencast { + if item.isVideoLimit { + self.placeholderTextNode.attributedText = NSAttributedString(string: item.strings.VoiceChat_VideoParticipantsLimitExceeded, font: Font.semibold(13.0), textColor: .white) + self.placeholderIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/Pause"), color: .white) + showPlaceholder = true + } else if item.isOwnScreencast { self.placeholderTextNode.attributedText = NSAttributedString(string: item.strings.VoiceChat_YouAreSharingScreen, font: Font.semibold(13.0), textColor: .white) self.placeholderIconNode.image = generateTintedImage(image: UIImage(bundleImageName: item.isTablet ? "Call/ScreenShareTablet" : "Call/ScreenSharePhone"), color: .white) showPlaceholder = true @@ -428,7 +432,9 @@ final class VoiceChatTileItemNode: ASDisplayNode { let titleFont = Font.semibold(13.0) let titleColor = UIColor.white var titleAttributedString: NSAttributedString? - if let user = item.peer as? TelegramUser { + if item.isVideoLimit { + titleAttributedString = nil + } else if let user = item.peer as? TelegramUser { if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() switch item.nameDisplayOrder { diff --git a/submodules/TelegramCore/Sources/GroupCalls.swift b/submodules/TelegramCore/Sources/GroupCalls.swift index 9aecd7cea3..c903f4a357 100644 --- a/submodules/TelegramCore/Sources/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/GroupCalls.swift @@ -123,7 +123,6 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) let parsedParticipants = participants.compactMap { GroupCallParticipantsContext.Participant($0, transaction: transaction) } - return GroupCallSummary( info: info, topParticipants: parsedParticipants @@ -638,6 +637,7 @@ public func joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?, cal videoDescription = nil presentationDescription = nil } + let joinedVideo = (flags & (1 << 15)) != 0 if !state.participants.contains(where: { $0.peer.id == peer.id }) { state.participants.append(GroupCallParticipantsContext.Participant( peer: peer, @@ -651,7 +651,8 @@ public func joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?, cal activityRank: nil, muteState: muteState, volume: volume, - about: about + about: about, + joinedVideo: joinedVideo )) } } @@ -869,6 +870,7 @@ public final class GroupCallParticipantsContext { public var muteState: MuteState? public var volume: Int32? public var about: String? + public var joinedVideo: Bool public init( peer: Peer, @@ -882,7 +884,8 @@ public final class GroupCallParticipantsContext { activityRank: Int?, muteState: MuteState?, volume: Int32?, - about: String? + about: String?, + joinedVideo: Bool ) { self.peer = peer self.ssrc = ssrc @@ -896,6 +899,7 @@ public final class GroupCallParticipantsContext { self.muteState = muteState self.volume = volume self.about = about + self.joinedVideo = joinedVideo } public var description: String { @@ -1130,6 +1134,7 @@ public final class GroupCallParticipantsContext { public var participationStatusChange: ParticipationStatusChange public var volume: Int32? public var about: String? + public var joinedVideo: Bool public var isMin: Bool init( @@ -1144,6 +1149,7 @@ public final class GroupCallParticipantsContext { participationStatusChange: ParticipationStatusChange, volume: Int32?, about: String?, + joinedVideo: Bool, isMin: Bool ) { self.peerId = peerId @@ -1157,6 +1163,7 @@ public final class GroupCallParticipantsContext { self.participationStatusChange = participationStatusChange self.volume = volume self.about = about + self.joinedVideo = joinedVideo self.isMin = isMin } } @@ -1686,7 +1693,6 @@ public final class GroupCallParticipantsContext { volume = previousVolume } } - let participant = Participant( peer: peer, ssrc: participantUpdate.ssrc, @@ -1699,7 +1705,8 @@ public final class GroupCallParticipantsContext { activityRank: previousActivityRank, muteState: muteState, volume: volume, - about: participantUpdate.about + about: participantUpdate.about, + joinedVideo: participantUpdate.joinedVideo ) updatedParticipants.append(participant) } @@ -2057,6 +2064,7 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate { } let isRemoved = (flags & (1 << 1)) != 0 let justJoined = (flags & (1 << 4)) != 0 + let joinedVideo = (flags & (1 << 15)) != 0 let isMin = (flags & (1 << 8)) != 0 let participationStatusChange: GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate.ParticipationStatusChange @@ -2086,6 +2094,7 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate { participationStatusChange: participationStatusChange, volume: volume, about: about, + joinedVideo: joinedVideo, isMin: isMin ) } @@ -2454,6 +2463,8 @@ extension GroupCallParticipantsContext.Participant { videoDescription = nil presentationDescription = nil } + let joinedVideo = (flags & (1 << 15)) != 0 + self.init( peer: peer, ssrc: ssrc, @@ -2466,7 +2477,8 @@ extension GroupCallParticipantsContext.Participant { activityRank: nil, muteState: muteState, volume: volume, - about: about + about: about, + joinedVideo: joinedVideo ) } } diff --git a/submodules/TelegramCore/Sources/Suggestions.swift b/submodules/TelegramCore/Sources/Suggestions.swift index e9f50cd0bb..1703b0316d 100644 --- a/submodules/TelegramCore/Sources/Suggestions.swift +++ b/submodules/TelegramCore/Sources/Suggestions.swift @@ -11,10 +11,18 @@ public enum ServerProvidedSuggestion: String { case validatePassword = "VALIDATE_PASSWORD" } -public func getServerProvidedSuggestions(postbox: Postbox) -> Signal<[ServerProvidedSuggestion], NoError> { +private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set]>([:]) +private var dismissedSuggestions: [AccountRecordId: Set] = [:] { + didSet { + dismissedSuggestionsPromise.set(dismissedSuggestions) + } +} + +public func getServerProvidedSuggestions(account: Account) -> Signal<[ServerProvidedSuggestion], NoError> { let key: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.appConfiguration])) - return postbox.combinedView(keys: [key]) - |> map { views -> [ServerProvidedSuggestion] in + return combineLatest(account.postbox.combinedView(keys: [key]), dismissedSuggestionsPromise.get()) + |> map { views, dismissedSuggestionsValue -> [ServerProvidedSuggestion] in + let dismissedSuggestions = dismissedSuggestionsValue[account.id] ?? Set() guard let view = views.views[key] as? PreferencesView else { return [] } @@ -26,12 +34,17 @@ public func getServerProvidedSuggestions(postbox: Postbox) -> Signal<[ServerProv } return list.compactMap { item -> ServerProvidedSuggestion? in return ServerProvidedSuggestion(rawValue: item) - } + }.filter { !dismissedSuggestions.contains($0) } } |> distinctUntilChanged } public func dismissServerProvidedSuggestion(account: Account, suggestion: ServerProvidedSuggestion) -> Signal { + if let _ = dismissedSuggestions[account.id] { + dismissedSuggestions[account.id]?.insert(suggestion) + } else { + dismissedSuggestions[account.id] = Set([suggestion]) + } return account.network.request(Api.functions.help.dismissSuggestion(peer: .inputPeerEmpty, suggestion: suggestion.rawValue)) |> `catch` { _ -> Signal in return .single(.boolFalse) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 5c5a53f100..ffff14825e 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -7173,7 +7173,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } - self.checksTooltipDisposable.set((getServerProvidedSuggestions(postbox: self.context.account.postbox) + self.checksTooltipDisposable.set((getServerProvidedSuggestions(account: self.context.account) |> deliverOnMainQueue).start(next: { [weak self] values in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenInfoItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenInfoItem.swift new file mode 100644 index 0000000000..5598f56e06 --- /dev/null +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenInfoItem.swift @@ -0,0 +1,108 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramPresentationData +import ItemListPeerItem +import SwiftSignalKit +import AccountContext +import Postbox +import SyncCore +import TelegramCore +import ItemListUI + +final class PeerInfoScreenInfoItem: PeerInfoScreenItem { + let id: AnyHashable + let title: String + let text: InfoListItemText + let linkAction: ((InfoListItemLinkAction) -> Void)? + + init( + id: AnyHashable, + title: String, + text: InfoListItemText, + linkAction: ((InfoListItemLinkAction) -> Void)? + ) { + self.id = id + self.title = title + self.text = text + self.linkAction = linkAction + } + + func node() -> PeerInfoScreenItemNode { + return PeerInfoScreenInfoItemNode() + } +} + +private final class PeerInfoScreenInfoItemNode: PeerInfoScreenItemNode { + private let bottomSeparatorNode: ASDisplayNode + + private var item: PeerInfoScreenInfoItem? + private var itemNode: InfoItemNode? + + override init() { + self.bottomSeparatorNode = ASDisplayNode() + self.bottomSeparatorNode.isLayerBacked = true + + super.init() + + self.addSubnode(self.bottomSeparatorNode) + } + + override func update(width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, transition: ContainedViewLayoutTransition) -> CGFloat { + guard let item = item as? PeerInfoScreenInfoItem else { + return 10.0 + } + + self.item = item + + + let sideInset: CGFloat = 16.0 + safeInsets.left + + self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + + let infoItem = InfoListItem(presentationData: ItemListPresentationData(presentationData), title: item.title, text: item.text, style: .plain, linkAction: { link in + item.linkAction?(link) + }, closeAction: nil) + let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0) + + let itemNode: InfoItemNode + if let current = self.itemNode { + itemNode = current + infoItem.updateNode(async: { $0() }, node: { + return itemNode + }, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in + let nodeFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: layout.size.height)) + + itemNode.contentSize = layout.contentSize + itemNode.insets = layout.insets + itemNode.frame = nodeFrame + + apply(ListViewItemApply(isOnScreen: true)) + }) + } else { + var itemNodeValue: ListViewItemNode? + infoItem.nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in + itemNodeValue = node + apply().1(ListViewItemApply(isOnScreen: true)) + }) + itemNode = itemNodeValue as! InfoItemNode + self.itemNode = itemNode + self.addSubnode(itemNode) + } + + let height = itemNode.contentSize.height + + transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(), size: itemNode.bounds.size)) + + var separatorInset: CGFloat = sideInset + if bottomItem != nil { + separatorInset += 49.0 + } + + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel))) + transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0) + + return height + } +} diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 06a7d7a2e2..41c0bf59fe 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -397,9 +397,10 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: PeerId, account combineLatest(context.account.viewTracker.featuredStickerPacks(), archivedStickerPacks), hasPassport, (context.watchManager?.watchAppInstalled ?? .single(false)), - context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]), + getServerProvidedSuggestions(account: context.account) ) - |> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, hasWatchApp, accountPreferences -> PeerInfoScreenData in + |> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, hasWatchApp, accountPreferences, suggestions -> PeerInfoScreenData in let (notificationExceptions, notificationsAuthorizationStatus, notificationsWarningSuppressed) = notifications let (featuredStickerPacks, archivedStickerPacks) = stickerPacks @@ -416,7 +417,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: PeerId, account enableQRLogin = true } - let globalSettings = TelegramGlobalSettings(suggestPhoneNumberConfirmation: false, accountsAndPeers: accountsAndPeers, activeSessionsContext: accountSessions?.0, webSessionsContext: accountSessions?.2, otherSessionsCount: accountSessions?.1, proxySettings: proxySettings, notificationAuthorizationStatus: notificationsAuthorizationStatus, notificationWarningSuppressed: notificationsWarningSuppressed, notificationExceptions: notificationExceptions, inAppNotificationSettings: inAppNotificationSettings, privacySettings: privacySettings, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedStickerPacks: archivedStickerPacks, hasPassport: hasPassport, hasWatchApp: hasWatchApp, enableQRLogin: enableQRLogin) + let globalSettings = TelegramGlobalSettings(suggestPhoneNumberConfirmation: suggestions.contains(.validatePhoneNumber), accountsAndPeers: accountsAndPeers, activeSessionsContext: accountSessions?.0, webSessionsContext: accountSessions?.2, otherSessionsCount: accountSessions?.1, proxySettings: proxySettings, notificationAuthorizationStatus: notificationsAuthorizationStatus, notificationWarningSuppressed: notificationsWarningSuppressed, notificationExceptions: notificationExceptions, inAppNotificationSettings: inAppNotificationSettings, privacySettings: privacySettings, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedStickerPacks: archivedStickerPacks, hasPassport: hasPassport, hasWatchApp: hasWatchApp, enableQRLogin: enableQRLogin) return PeerInfoScreenData( peer: peerView.peers[peerId], diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index c69a12f77c..e1f903fa48 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -568,6 +568,7 @@ private final class PeerInfoInteraction { let accountContextMenu: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void let updateBio: (String) -> Void let openDeletePeer: () -> Void + let openFaq: (String?) -> Void init( openUsername: @escaping (String) -> Void, @@ -605,7 +606,8 @@ private final class PeerInfoInteraction { logoutAccount: @escaping (AccountRecordId) -> Void, accountContextMenu: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void, updateBio: @escaping (String) -> Void, - openDeletePeer: @escaping () -> Void + openDeletePeer: @escaping () -> Void, + openFaq: @escaping (String?) -> Void ) { self.openUsername = openUsername self.openPhone = openPhone @@ -643,6 +645,7 @@ private final class PeerInfoInteraction { self.accountContextMenu = accountContextMenu self.updateBio = updateBio self.openDeletePeer = openDeletePeer + self.openFaq = openFaq } } @@ -692,16 +695,17 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p if let settings = data.globalSettings { if settings.suggestPhoneNumberConfirmation, let peer = data.peer as? TelegramUser { - // - // entries.append(.phoneInfo(presentationData.theme, presentationData.strings.Settings_CheckPhoneNumberTitle(phoneNumber).0, presentationData.strings.Settings_CheckPhoneNumberText)) - // entries.append(.keepPhone(presentationData.theme, presentationData.strings.Settings_KeepPhoneNumber(phoneNumber).0)) - // entries.append(.changePhone(presentationData.theme, presentationData.strings.Settings_ChangePhoneNumber)) let phoneNumber = formatPhoneNumber(peer.phone ?? "") - items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_KeepPhoneNumber(phoneNumber).0, action: { - interaction.openSettings(.addAccount) + items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: presentationData.strings.Settings_CheckPhoneNumberTitle(phoneNumber).0, text: .markdown(presentationData.strings.Settings_CheckPhoneNumberText), linkAction: { link in + if case .tap = link { + interaction.openFaq("q-i-have-a-new-phone-number-what-do-i-do") + } + })) + items[.phone]!.append(PeerInfoScreenActionItem(id: 1, text: presentationData.strings.Settings_KeepPhoneNumber(phoneNumber).0, action: { + let _ = dismissServerProvidedSuggestion(account: context.account, suggestion: .validatePhoneNumber).start() })) items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_ChangePhoneNumber, action: { - interaction.openSettings(.addAccount) + interaction.openSettings(.phoneNumber) })) } @@ -1683,6 +1687,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD }, openDeletePeer: { [weak self] in self?.openDeletePeer() + }, + openFaq: { [weak self] anchor in + self?.openFaq(anchor: anchor) } ) @@ -5421,12 +5428,24 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } private func openFaq(anchor: String? = nil) { - let controller = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil)) - self.controller?.present(controller, in: .window(.root)) + let presentationData = self.presentationData + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) + self?.controller?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + let _ = (self.cachedFaq.get() |> take(1) - |> deliverOnMainQueue).start(next: { [weak self, weak controller] resolvedUrl in - controller?.dismiss() + |> deliverOnMainQueue).start(next: { [weak self] resolvedUrl in + progressDisposable.dispose() if let strongSelf = self, let resolvedUrl = resolvedUrl { var resolvedUrl = resolvedUrl @@ -6531,14 +6550,15 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen { icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings") } - let tabBarItem: Signal<(String, UIImage?, UIImage?, String?), NoError> = combineLatest(queue: .mainQueue(), self.context.sharedContext.presentationData, notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get(), accountTabBarAvatar, accountTabBarAvatarBadge) - |> map { presentationData, notificationsAuthorizationStatus, notificationsWarningSuppressed, accountTabBarAvatar, accountTabBarAvatarBadge -> (String, UIImage?, UIImage?, String?) in + let tabBarItem: Signal<(String, UIImage?, UIImage?, String?), NoError> = combineLatest(queue: .mainQueue(), self.context.sharedContext.presentationData, notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get(), getServerProvidedSuggestions(account: self.context.account), accountTabBarAvatar, accountTabBarAvatarBadge) + |> map { presentationData, notificationsAuthorizationStatus, notificationsWarningSuppressed, suggestions, accountTabBarAvatar, accountTabBarAvatarBadge -> (String, UIImage?, UIImage?, String?) in let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed) + let phoneNumberWarning = suggestions.contains(.validatePhoneNumber) var otherAccountsBadge: String? if accountTabBarAvatarBadge > 0 { otherAccountsBadge = compactNumericCountString(Int(accountTabBarAvatarBadge), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) } - return (presentationData.strings.Settings_Title, accountTabBarAvatar?.0 ?? icon, accountTabBarAvatar?.1 ?? icon, notificationsWarning ? "!" : otherAccountsBadge) + return (presentationData.strings.Settings_Title, accountTabBarAvatar?.0 ?? icon, accountTabBarAvatar?.1 ?? icon, notificationsWarning || phoneNumberWarning ? "!" : otherAccountsBadge) } self.tabBarItemDisposable = (tabBarItem |> deliverOnMainQueue).start(next: { [weak self] title, image, selectedImage, badgeValue in