mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Video Chat Improvements
This commit is contained in:
parent
3b34ee26b7
commit
f36bba8143
@ -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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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?()
|
||||
}))
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<Bool>(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,9 +4795,12 @@ 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 {
|
||||
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 {
|
||||
peerIdToCameraEndpointId[member.peer.id] = 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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,8 +284,8 @@ 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)
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<ServerProvidedSuggestion>]>([:])
|
||||
private var dismissedSuggestions: [AccountRecordId: Set<ServerProvidedSuggestion>] = [:] {
|
||||
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<Never, NoError> {
|
||||
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<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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],
|
||||
|
||||
@ -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<Never, NoError> { [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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user