Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mikhail Filimonov 2025-05-23 08:13:47 +01:00
commit d1e1fffed9
41 changed files with 1180 additions and 538 deletions

View File

@ -1763,7 +1763,7 @@ private final class NotificationServiceHandler {
|> mapToSignal { content, _ -> Signal<(NotificationContent, Media?), NoError> in |> mapToSignal { content, _ -> Signal<(NotificationContent, Media?), NoError> in
return stateManager.postbox.transaction { transaction -> (NotificationContent, Media?) in return stateManager.postbox.transaction { transaction -> (NotificationContent, Media?) in
var parsedMedia: Media? var parsedMedia: Media?
if let messageId, let message = transaction.getMessage(messageId), !message.containsSecretMedia { if let messageId, let message = transaction.getMessage(messageId), !message.containsSecretMedia, !message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) {
if let media = message.media.first { if let media = message.media.first {
parsedMedia = media parsedMedia = media
} }

View File

@ -477,7 +477,7 @@ final class ComposePollScreenComponent: Component {
defer { defer {
self.isUpdating = false self.isUpdating = false
} }
var alphaTransition = transition var alphaTransition = transition
if !transition.animation.isImmediate { if !transition.animation.isImmediate {
alphaTransition = alphaTransition.withAnimation(.curve(duration: 0.25, curve: .easeInOut)) alphaTransition = alphaTransition.withAnimation(.curve(duration: 0.25, curve: .easeInOut))
@ -850,7 +850,7 @@ final class ComposePollScreenComponent: Component {
} }
} }
if self.pollOptions.count < 10, let lastOption = self.pollOptions.last { if self.pollOptions.count < component.initialData.maxPollAnswersCount, let lastOption = self.pollOptions.last {
if lastOption.textInputState.text.length != 0 { if lastOption.textInputState.text.length != 0 {
self.pollOptions.append(PollOption(id: self.nextPollOptionId)) self.pollOptions.append(PollOption(id: self.nextPollOptionId))
self.nextPollOptionId += 1 self.nextPollOptionId += 1
@ -921,7 +921,7 @@ final class ComposePollScreenComponent: Component {
contentHeight += 7.0 contentHeight += 7.0
let pollOptionsLimitReached = self.pollOptions.count >= 10 let pollOptionsLimitReached = self.pollOptions.count >= component.initialData.maxPollAnswersCount
var animatePollOptionsFooterIn = false var animatePollOptionsFooterIn = false
var pollOptionsFooterTransition = transition var pollOptionsFooterTransition = transition
if self.currentPollOptionsLimitReached != pollOptionsLimitReached { if self.currentPollOptionsLimitReached != pollOptionsLimitReached {
@ -944,7 +944,7 @@ final class ComposePollScreenComponent: Component {
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)) ))
} else { } else {
let remainingCount = 10 - self.pollOptions.count let remainingCount = component.initialData.maxPollAnswersCount - self.pollOptions.count
let rawString = environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount)) let rawString = environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount))
var pollOptionsFooterItems: [AnimatedTextComponent.Item] = [] var pollOptionsFooterItems: [AnimatedTextComponent.Item] = []
@ -1476,13 +1476,16 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
public final class InitialData { public final class InitialData {
fileprivate let maxPollTextLength: Int fileprivate let maxPollTextLength: Int
fileprivate let maxPollOptionLength: Int fileprivate let maxPollOptionLength: Int
fileprivate let maxPollAnswersCount: Int
fileprivate init( fileprivate init(
maxPollTextLength: Int, maxPollTextLength: Int,
maxPollOptionLength: Int maxPollOptionLength: Int,
maxPollAnwsersCount: Int
) { ) {
self.maxPollTextLength = maxPollTextLength self.maxPollTextLength = maxPollTextLength
self.maxPollOptionLength = maxPollOptionLength self.maxPollOptionLength = maxPollOptionLength
self.maxPollAnswersCount = maxPollAnwsersCount
} }
} }
@ -1577,9 +1580,14 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
} }
public static func initialData(context: AccountContext) -> InitialData { public static func initialData(context: AccountContext) -> InitialData {
var maxPollAnwsersCount: Int = 10
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["poll_answers_max"] as? Double {
maxPollAnwsersCount = Int(value)
}
return InitialData( return InitialData(
maxPollTextLength: Int(200), maxPollTextLength: Int(200),
maxPollOptionLength: 100 maxPollOptionLength: 100,
maxPollAnwsersCount: maxPollAnwsersCount
) )
} }

View File

@ -426,7 +426,9 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
let text = strings.Contacts_PermissionsText let text = strings.Contacts_PermissionsText
switch authorizationStatus { switch authorizationStatus {
case .limited: case .limited:
entries.append(.permissionLimited(theme, strings)) if displaySortOptions {
entries.append(.permissionLimited(theme, strings))
}
case .denied: case .denied:
entries.append(.permissionInfo(theme, title, text, suppressed)) entries.append(.permissionInfo(theme, title, text, suppressed))
entries.append(.permissionEnable(theme, strings.Permissions_ContactsAllowInSettings_v0)) entries.append(.permissionEnable(theme, strings.Permissions_ContactsAllowInSettings_v0))

View File

@ -881,16 +881,6 @@ open class NavigationBar: ASDisplayNode {
if needsLeftButton { if needsLeftButton {
if animated { if animated {
if self.leftButtonNode.view.superview != nil {
if let snapshotView = self.leftButtonNode.view.snapshotContentTree() {
snapshotView.frame = self.leftButtonNode.frame
self.leftButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.leftButtonNode.view)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
}
if self.backButtonNode.view.superview != nil { if self.backButtonNode.view.superview != nil {
if let snapshotView = self.backButtonNode.view.snapshotContentTree() { if let snapshotView = self.backButtonNode.view.snapshotContentTree() {
snapshotView.frame = self.backButtonNode.frame snapshotView.frame = self.backButtonNode.frame
@ -927,9 +917,11 @@ open class NavigationBar: ASDisplayNode {
self.badgeNode.removeFromSupernode() self.badgeNode.removeFromSupernode()
if let leftBarButtonItem = item.leftBarButtonItem { if let leftBarButtonItem = item.leftBarButtonItem {
self.leftButtonNode.updateItems([leftBarButtonItem]) self.leftButtonNode.updateItems([], animated: animated)
self.leftButtonNode.updateItems([leftBarButtonItem], animated: animated)
} else { } else {
self.leftButtonNode.updateItems([UIBarButtonItem(title: self.presentationData.strings.close, style: .plain, target: nil, action: nil)]) self.leftButtonNode.updateItems([], animated: animated)
self.leftButtonNode.updateItems([UIBarButtonItem(title: self.presentationData.strings.close, style: .plain, target: nil, action: nil)], animated: animated)
} }
if self.leftButtonNode.supernode == nil { if self.leftButtonNode.supernode == nil {
@ -994,9 +986,6 @@ open class NavigationBar: ASDisplayNode {
} }
self.updateAccessibilityElements() self.updateAccessibilityElements()
if animated {
self.hintAnimateTitleNodeOnNextLayout = true
}
} }
private func updateRightButton(animated: Bool) { private func updateRightButton(animated: Bool) {
@ -1008,23 +997,14 @@ open class NavigationBar: ASDisplayNode {
items = [rightBarButtonItem] items = [rightBarButtonItem]
} }
self.rightButtonNodeUpdated = true
if !items.isEmpty { if !items.isEmpty {
if animated, self.rightButtonNode.view.superview != nil { self.rightButtonNode.updateItems([], animated: animated)
if let snapshotView = self.rightButtonNode.view.snapshotContentTree() { self.rightButtonNode.updateItems(items, animated: animated)
snapshotView.frame = self.rightButtonNode.frame
self.rightButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.rightButtonNode.view)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
}
self.rightButtonNode.updateItems(items)
if self.rightButtonNode.supernode == nil { if self.rightButtonNode.supernode == nil {
self.buttonsContainerNode.addSubnode(self.rightButtonNode) self.buttonsContainerNode.addSubnode(self.rightButtonNode)
} }
if animated {
self.rightButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
} else { } else {
if animated, self.rightButtonNode.view.superview != nil { if animated, self.rightButtonNode.view.superview != nil {
if let snapshotView = self.rightButtonNode.view.snapshotContentTree() { if let snapshotView = self.rightButtonNode.view.snapshotContentTree() {
@ -1050,9 +1030,6 @@ open class NavigationBar: ASDisplayNode {
self.rightButtonNode.removeFromSupernode() self.rightButtonNode.removeFromSupernode()
} }
if animated {
self.hintAnimateTitleNodeOnNextLayout = true
}
self.updateAccessibilityElements() self.updateAccessibilityElements()
} }
@ -1062,6 +1039,7 @@ open class NavigationBar: ASDisplayNode {
public let backButtonArrow: ASImageNode public let backButtonArrow: ASImageNode
public let leftButtonNode: NavigationButtonNode public let leftButtonNode: NavigationButtonNode
public let rightButtonNode: NavigationButtonNode public let rightButtonNode: NavigationButtonNode
private var rightButtonNodeUpdated: Bool = false
public let additionalContentNode: SparseNode public let additionalContentNode: SparseNode
private let navigationBackgroundCutoutView: NavigationBackgroundCutoutView private let navigationBackgroundCutoutView: NavigationBackgroundCutoutView
@ -1359,7 +1337,7 @@ open class NavigationBar: ASDisplayNode {
var leftTitleInset: CGFloat = leftInset + 1.0 var leftTitleInset: CGFloat = leftInset + 1.0
var rightTitleInset: CGFloat = rightInset + 1.0 var rightTitleInset: CGFloat = rightInset + 1.0
if self.backButtonNode.supernode != nil { if self.backButtonNode.supernode != nil {
let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape) let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape, isLeftAligned: true)
leftTitleInset = backButtonSize.width + backButtonInset + 1.0 leftTitleInset = backButtonSize.width + backButtonInset + 1.0
let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5
@ -1411,7 +1389,7 @@ open class NavigationBar: ASDisplayNode {
self.badgeNode.alpha = 1.0 self.badgeNode.alpha = 1.0
} }
} else if self.leftButtonNode.supernode != nil { } else if self.leftButtonNode.supernode != nil {
let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape) let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape, isLeftAligned: true)
leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0 leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0
var transition = transition var transition = transition
@ -1428,16 +1406,18 @@ open class NavigationBar: ASDisplayNode {
transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 16.0, dy: 2.0), size: badgeSize)) transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 16.0, dy: 2.0), size: badgeSize))
if self.rightButtonNode.supernode != nil { if self.rightButtonNode.supernode != nil {
let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape) let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape, isLeftAligned: false)
rightTitleInset = rightButtonSize.width + leftButtonInset + 1.0 rightTitleInset = rightButtonSize.width + leftButtonInset + 1.0
self.rightButtonNode.alpha = 1.0 self.rightButtonNode.alpha = 1.0
var transition = transition var transition = transition
if self.rightButtonNode.frame.width.isZero { if self.rightButtonNode.frame.width.isZero || self.rightButtonNodeUpdated {
transition = .immediate transition = .immediate
} }
transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize)) transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize))
} }
self.rightButtonNodeUpdated = false
if let transitionState = self.transitionState { if let transitionState = self.transitionState {
let progress = transitionState.progress let progress = transitionState.progress
@ -1447,7 +1427,7 @@ open class NavigationBar: ASDisplayNode {
break break
case .bottom: case .bottom:
if let transitionBackButtonNode = self.transitionBackButtonNode { if let transitionBackButtonNode = self.transitionBackButtonNode {
let transitionBackButtonSize = transitionBackButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape) let transitionBackButtonSize = transitionBackButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape, isLeftAligned: true)
let initialX: CGFloat = backButtonInset + size.width * 0.3 let initialX: CGFloat = backButtonInset + size.width * 0.3
let finalX: CGFloat = floor((size.width - transitionBackButtonSize.width) / 2.0) let finalX: CGFloat = floor((size.width - transitionBackButtonSize.width) / 2.0)
@ -1592,7 +1572,7 @@ open class NavigationBar: ASDisplayNode {
node.updateManualText(self.backButtonNode.manualText) node.updateManualText(self.backButtonNode.manualText)
node.color = accentColor node.color = accentColor
if let validLayout = self.validLayout { if let validLayout = self.validLayout {
let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape) let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape, isLeftAligned: true)
node.frame = self.backButtonNode.frame node.frame = self.backButtonNode.frame
} }
return node return node
@ -1608,7 +1588,7 @@ open class NavigationBar: ASDisplayNode {
node.updateManualText(self.backButtonNode.manualText) node.updateManualText(self.backButtonNode.manualText)
node.color = accentColor node.color = accentColor
if let validLayout = self.validLayout { if let validLayout = self.validLayout {
let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape) let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape, isLeftAligned: true)
node.frame = self.backButtonNode.frame node.frame = self.backButtonNode.frame
} }
return node.view return node.view
@ -1628,10 +1608,10 @@ open class NavigationBar: ASDisplayNode {
items = [rightBarButtonItem] items = [rightBarButtonItem]
} }
} }
node.updateItems(items) node.updateItems(items, animated: false)
node.color = accentColor node.color = accentColor
if let validLayout = self.validLayout { if let validLayout = self.validLayout {
let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape) let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape, isLeftAligned: false)
node.frame = self.backButtonNode.frame node.frame = self.backButtonNode.frame
} }
return node return node

View File

@ -331,6 +331,8 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
public final class NavigationButtonNode: ContextControllerSourceNode { public final class NavigationButtonNode: ContextControllerSourceNode {
private var nodes: [NavigationButtonItemNode] = [] private var nodes: [NavigationButtonItemNode] = []
private var disappearingNodes: [(frame: CGRect, size: CGSize, node: NavigationButtonItemNode)] = []
public var singleCustomNode: ASDisplayNode? { public var singleCustomNode: ASDisplayNode? {
for node in self.nodes { for node in self.nodes {
return node.node return node.node
@ -452,7 +454,7 @@ public final class NavigationButtonNode: ContextControllerSourceNode {
} }
} }
func updateItems(_ items: [UIBarButtonItem]) { func updateItems(_ items: [UIBarButtonItem], animated: Bool) {
for i in 0 ..< items.count { for i in 0 ..< items.count {
let node: NavigationButtonItemNode let node: NavigationButtonItemNode
if self.nodes.count > i { if self.nodes.count > i {
@ -486,16 +488,41 @@ public final class NavigationButtonNode: ContextControllerSourceNode {
node.bold = items[i].style == .done node.bold = items[i].style == .done
node.isEnabled = items[i].isEnabled node.isEnabled = items[i].isEnabled
node.node = items[i].customDisplayNode node.node = items[i].customDisplayNode
if animated {
node.layer.animateAlpha(from: 0.0, to: self.manualAlpha, duration: 0.16)
node.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
}
} }
if items.count < self.nodes.count { if items.count < self.nodes.count {
for i in items.count ..< self.nodes.count { for i in items.count ..< self.nodes.count {
self.nodes[i].removeFromSupernode() let itemNode = self.nodes[i]
if animated {
disappearingNodes.append((itemNode.frame, self.bounds.size, itemNode))
itemNode.layer.animateAlpha(from: self.manualAlpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self, weak itemNode] _ in
guard let itemNode else {
return
}
itemNode.removeFromSupernode()
guard let self else {
return
}
if let index = self.disappearingNodes.firstIndex(where: { $0.node === itemNode }) {
self.disappearingNodes.remove(at: index)
}
})
itemNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
} else {
itemNode.removeFromSupernode()
}
} }
self.nodes.removeSubrange(items.count...) self.nodes.removeSubrange(items.count...)
} }
} }
public func updateLayout(constrainedSize: CGSize, isLandscape: Bool) -> CGSize { public func updateLayout(constrainedSize: CGSize, isLandscape: Bool, isLeftAligned: Bool) -> CGSize {
var nodeOrigin = CGPoint() var nodeOrigin = CGPoint()
var totalHeight: CGFloat = 0.0 var totalHeight: CGFloat = 0.0
for i in 0 ..< self.nodes.count { for i in 0 ..< self.nodes.count {
@ -520,6 +547,13 @@ public final class NavigationButtonNode: ContextControllerSourceNode {
nodeOrigin.x -= 5.0 nodeOrigin.x -= 5.0
} }
} }
if !isLeftAligned {
for disappearingNode in self.disappearingNodes {
disappearingNode.node.frame = disappearingNode.frame.offsetBy(dx: nodeOrigin.x - disappearingNode.size.width, dy: (totalHeight - disappearingNode.size.height) * 0.5)
}
}
return CGSize(width: nodeOrigin.x, height: totalHeight) return CGSize(width: nodeOrigin.x, height: totalHeight)
} }

View File

@ -484,6 +484,17 @@ public extension UIImage {
} }
return result return result
} }
func fixedOrientation() -> UIImage {
if self.imageOrientation == .up { return self }
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
self.draw(in: CGRect(origin: .zero, size: size))
let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return normalizedImage ?? self
}
} }
private func makeSubtreeSnapshot(layer: CALayer, keepPortals: Bool = false, keepTransform: Bool = false) -> UIView? { private func makeSubtreeSnapshot(layer: CALayer, keepPortals: Bool = false, keepTransform: Bool = false) -> UIView? {

View File

@ -1259,7 +1259,7 @@ public final class ShareController: ViewController {
var result: [EnginePeer.Id: EnginePeer?] = [:] var result: [EnginePeer.Id: EnginePeer?] = [:]
var requiresStars: [EnginePeer.Id: StarsAmount] = [:] var requiresStars: [EnginePeer.Id: StarsAmount] = [:]
for peerId in peerIds { for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? PeerView, let peer = peerViewMainPeer(view) { if let view = views.views[PostboxViewKey.peer(peerId: peerId, components: [])] as? PeerView, let peer = peerViewMainPeer(view) {
result[peerId] = EnginePeer(peer) result[peerId] = EnginePeer(peer)
if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView { if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView {
if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData { if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData {
@ -1913,7 +1913,7 @@ public final class ShareController: ViewController {
var result: [EnginePeer.Id: EnginePeer?] = [:] var result: [EnginePeer.Id: EnginePeer?] = [:]
var requiresStars: [EnginePeer.Id: StarsAmount] = [:] var requiresStars: [EnginePeer.Id: StarsAmount] = [:]
for peerId in peerIds { for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? PeerView, let peer = peerViewMainPeer(view) { if let view = views.views[PostboxViewKey.peer(peerId: peerId, components: [])] as? PeerView, let peer = peerViewMainPeer(view) {
result[peerId] = EnginePeer(peer) result[peerId] = EnginePeer(peer)
if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView { if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView {
if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData { if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData {
@ -2549,7 +2549,7 @@ public final class ShareController: ViewController {
if let view = views.views[.cachedPeerData(peerId: id)] as? CachedPeerDataView, let data = view.cachedPeerData as? CachedUserData { if let view = views.views[.cachedPeerData(peerId: id)] as? CachedPeerDataView, let data = view.cachedPeerData as? CachedUserData {
requiresPremiumForMessaging[id] = data.flags.contains(.premiumRequired) requiresPremiumForMessaging[id] = data.flags.contains(.premiumRequired)
requiresStars[id] = data.sendPaidMessageStars?.value requiresStars[id] = data.sendPaidMessageStars?.value
} else if let view = views.views[.basicPeer(id)] as? PeerView, let channel = peerViewMainPeer(view) as? TelegramChannel { } else if let view = views.views[.peer(peerId: id, components: [])] as? PeerView, let channel = peerViewMainPeer(view) as? TelegramChannel {
requiresStars[id] = channel.sendPaidMessageStars?.value requiresStars[id] = channel.sendPaidMessageStars?.value
} else { } else {
requiresPremiumForMessaging[id] = false requiresPremiumForMessaging[id] = false

View File

@ -116,6 +116,10 @@ public final class PresentationData: Equatable {
return PresentationData(strings: self.strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji) return PresentationData(strings: self.strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
} }
public func withUpdate(listsFontSize: PresentationFontSize) -> PresentationData {
return PresentationData(strings: self.strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: self.chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
}
public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool { public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool {
return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.autoNightModeTriggered == rhs.autoNightModeTriggered && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatFontSize == rhs.chatFontSize && lhs.chatBubbleCorners == rhs.chatBubbleCorners && lhs.listsFontSize == rhs.listsFontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.reduceMotion == rhs.reduceMotion && lhs.largeEmoji == rhs.largeEmoji return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.autoNightModeTriggered == rhs.autoNightModeTriggered && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatFontSize == rhs.chatFontSize && lhs.chatBubbleCorners == rhs.chatBubbleCorners && lhs.listsFontSize == rhs.listsFontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.reduceMotion == rhs.reduceMotion && lhs.largeEmoji == rhs.largeEmoji
} }

View File

@ -1105,7 +1105,7 @@ public struct PresentationResourcesChat {
public static func chatFreeNavigateButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { public static func chatFreeNavigateButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? {
return theme.image(PresentationResourceKey.chatFreeNavigateButtonIcon.rawValue, { _ in return theme.image(PresentationResourceKey.chatFreeNavigateButtonIcon.rawValue, { _ in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/NavigateToMessageIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper))
}) })
} }

View File

@ -265,31 +265,47 @@ public final class ChatAvatarNavigationNode: ASDisplayNode {
public final class SnapshotState { public final class SnapshotState {
fileprivate let snapshotView: UIView? fileprivate let snapshotView: UIView?
fileprivate let snapshotStatusView: UIView?
fileprivate init(snapshotView: UIView?) { fileprivate init(snapshotView: UIView?, snapshotStatusView: UIView?) {
self.snapshotView = snapshotView self.snapshotView = snapshotView
self.snapshotStatusView = snapshotStatusView
} }
} }
public func prepareSnapshotState() -> SnapshotState { public func prepareSnapshotState() -> SnapshotState {
let snapshotView = self.avatarNode.view.snapshotView(afterScreenUpdates: false) let snapshotView = self.avatarNode.view.snapshotView(afterScreenUpdates: false)
let snapshotStatusView = self.statusView.view?.snapshotView(afterScreenUpdates: false)
return SnapshotState( return SnapshotState(
snapshotView: snapshotView snapshotView: snapshotView,
snapshotStatusView: snapshotStatusView
) )
} }
public func animateFromSnapshot(_ snapshotState: SnapshotState) { public func animateFromSnapshot(_ snapshotState: SnapshotState) {
self.avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.16)
self.avatarNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true) self.avatarNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true)
self.statusView.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.16)
self.statusView.view?.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true)
if let snapshotView = snapshotState.snapshotView { if let snapshotView = snapshotState.snapshotView {
snapshotView.frame = self.frame snapshotView.frame = self.frame
self.containerNode.view.addSubview(snapshotView) self.containerNode.view.insertSubview(snapshotView, at: 0)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview() snapshotView?.removeFromSuperview()
}) })
snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
if let snapshotStatusView = snapshotState.snapshotStatusView {
snapshotStatusView.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - snapshotStatusView.bounds.width) / 2.0), y: floor((self.containerNode.bounds.height - snapshotStatusView.bounds.height) / 2.0)), size: snapshotStatusView.bounds.size)
self.containerNode.view.insertSubview(snapshotStatusView, at: 0)
snapshotStatusView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotStatusView] _ in
snapshotStatusView?.removeFromSuperview()
})
snapshotStatusView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
} }
} }

View File

@ -1926,7 +1926,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
inlineBotNameString = attribute.title inlineBotNameString = attribute.title
} }
} else if let attribute = attribute as? ReplyMessageAttribute { } else if let attribute = attribute as? ReplyMessageAttribute {
if case let .replyThread(replyThreadMessage) = item.chatLocation, Int32(clamping: replyThreadMessage.threadId) == attribute.messageId.id { if let threadId = firstMessage.threadId, Int32(clamping: threadId) == attribute.messageId.id {
} else { } else {
replyMessage = firstMessage.associatedMessages[attribute.messageId] replyMessage = firstMessage.associatedMessages[attribute.messageId]
} }
@ -2437,7 +2437,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
headerSize.height += 7.0 headerSize.height += 7.0
} }
if isSidePanelOpen { if isSidePanelOpen && incoming {
hasTitleAvatar = true hasTitleAvatar = true
} }

View File

@ -531,7 +531,11 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
avatarNode.frame = CGRect(origin: CGPoint(x: leftOffset, y: titleLayout.size.height + titleAuthorSpacing), size: avatarSize) avatarNode.frame = CGRect(origin: CGPoint(x: leftOffset, y: titleLayout.size.height + titleAuthorSpacing), size: avatarSize)
avatarNode.updateSize(size: avatarSize) avatarNode.updateSize(size: avatarSize)
if let peer { if let peer {
avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize) if peer.smallProfileImage != nil {
avatarNode.setPeerV2(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize)
} else {
avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize)
}
} else if let authorName, !authorName.isEmpty { } else if let authorName, !authorName.isEmpty {
avatarNode.setCustomLetters([String(authorName[authorName.startIndex])]) avatarNode.setCustomLetters([String(authorName[authorName.startIndex])])
} else { } else {

View File

@ -322,7 +322,9 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isForumOrMonoForum { if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isForumOrMonoForum {
if case .replyThread = chatLocation { if case .replyThread = chatLocation {
displayAuthorInfo = false if channel.isMonoForum && chatLocation.threadId != context.account.peerId.toInt64() {
displayAuthorInfo = false
}
} else { } else {
if channel.isMonoForum { if channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil {

View File

@ -38,7 +38,7 @@ public class ChatUnreadItem: ListViewItem {
Queue.mainQueue().async { Queue.mainQueue().async {
completion(node, { completion(node, {
return (nil, { _ in return (nil, { _ in
apply() apply(.None)
}) })
}) })
} }
@ -56,7 +56,7 @@ public class ChatUnreadItem: ListViewItem {
let (layout, apply) = nodeLayout(self, params, dateAtBottom) let (layout, apply) = nodeLayout(self, params, dateAtBottom)
Queue.mainQueue().async { Queue.mainQueue().async {
completion(layout, { _ in completion(layout, { _ in
apply() apply(animation)
}) })
} }
} }
@ -122,18 +122,18 @@ public class ChatUnreadItemNode: ListViewItemNode {
if let item = item as? ChatUnreadItem { if let item = item as? ChatUnreadItem {
let dateAtBottom = !chatItemsHaveCommonDateHeader(item, nextItem) let dateAtBottom = !chatItemsHaveCommonDateHeader(item, nextItem)
let (layout, apply) = self.asyncLayout()(item, params, dateAtBottom) let (layout, apply) = self.asyncLayout()(item, params, dateAtBottom)
apply() apply(.None)
self.contentSize = layout.contentSize self.contentSize = layout.contentSize
self.insets = layout.insets self.insets = layout.insets
} }
} }
public func asyncLayout() -> (_ item: ChatUnreadItem, _ params: ListViewItemLayoutParams, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, () -> Void) { public func asyncLayout() -> (_ item: ChatUnreadItem, _ params: ListViewItemLayoutParams, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
let labelLayout = TextNode.asyncLayout(self.labelNode) let labelLayout = TextNode.asyncLayout(self.labelNode)
let layoutConstants = self.layoutConstants let layoutConstants = self.layoutConstants
let currentTheme = self.theme let currentTheme = self.theme
return { item, params, dateAtBottom in return { [weak self] item, params, dateAtBottom in
var updatedBackgroundImage: UIImage? var updatedBackgroundImage: UIImage?
if currentTheme != item.presentationData.theme { if currentTheme != item.presentationData.theme {
updatedBackgroundImage = PresentationResourcesChat.chatUnreadBarBackgroundImage(item.presentationData.theme.theme) updatedBackgroundImage = PresentationResourcesChat.chatUnreadBarBackgroundImage(item.presentationData.theme.theme)
@ -144,7 +144,7 @@ public class ChatUnreadItemNode: ListViewItemNode {
let backgroundSize = CGSize(width: params.width, height: 25.0) let backgroundSize = CGSize(width: params.width, height: 25.0)
return (ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 25.0), insets: UIEdgeInsets(top: 6.0 + (dateAtBottom ? layoutConstants.timestampHeaderHeight : 0.0), left: 0.0, bottom: 5.0, right: 0.0)), { [weak self] in return (ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 25.0), insets: UIEdgeInsets(top: 6.0 + (dateAtBottom ? layoutConstants.timestampHeaderHeight : 0.0), left: 0.0, bottom: 5.0, right: 0.0)), { animation in
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
strongSelf.theme = item.presentationData.theme strongSelf.theme = item.presentationData.theme
@ -159,7 +159,10 @@ public class ChatUnreadItemNode: ListViewItemNode {
strongSelf.activateArea.accessibilityLabel = string strongSelf.activateArea.accessibilityLabel = string
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize)
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - size.size.width) / 2.0), y: floorToScreenPixels((backgroundSize.height - size.size.height) / 2.0)), size: size.size)
let labelFrame = CGRect(origin: CGPoint(x: params.leftInset + floorToScreenPixels((backgroundSize.width - params.leftInset - params.rightInset - size.size.width) / 2.0), y: floorToScreenPixels((backgroundSize.height - size.size.height) / 2.0)), size: size.size)
animation.animator.updatePosition(layer: strongSelf.labelNode.layer, position: labelFrame.center, completion: nil)
strongSelf.labelNode.bounds = CGRect(origin: CGPoint(), size: labelFrame.size)
if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
if strongSelf.backgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { if strongSelf.backgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {

View File

@ -1021,16 +1021,16 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
if activitySize.width < size.width { if activitySize.width < size.width {
activityFrame.origin.x = -clearBounds.minX + floor((size.width - activityFrame.width) / 2.0) activityFrame.origin.x = -clearBounds.minX + floor((size.width - activityFrame.width) / 2.0)
} }
self.activityNode.frame = activityFrame titleTransition.updateFrameAdditiveToCenter(node: self.activityNode, frame: activityFrame)
} }
if let image = self.titleLeftIconNode.image { if let image = self.titleLeftIconNode.image {
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size) titleTransition.updateFrame(node: self.titleLeftIconNode, frame: CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size))
} }
var nextIconX: CGFloat = titleFrame.width var nextIconX: CGFloat = titleFrame.width
self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize) titleTransition.updateFrame(view: self.titleVerifiedIconView, frame: CGRect(origin: CGPoint(x: 0.0, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize))
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
nextIconX -= titleCredibilitySize.width nextIconX -= titleCredibilitySize.width
@ -1056,7 +1056,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
titleTransition.updateFrameAdditiveToCenter(view: self.titleContainerView, frame: titleFrame) titleTransition.updateFrameAdditiveToCenter(view: self.titleContainerView, frame: titleFrame)
titleTransition.updateFrameAdditiveToCenter(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) titleTransition.updateFrameAdditiveToCenter(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size))
self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + verifiedIconWidth + statusIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize) titleTransition.updateFrameAdditiveToCenter(node: self.activityNode, frame: CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + verifiedIconWidth + statusIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize))
if let image = self.titleLeftIconNode.image { if let image = self.titleLeftIconNode.image {
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size) self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size)
@ -1069,11 +1069,11 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
nextIconX -= titleCredibilitySize.width nextIconX -= titleCredibilitySize.width
self.titleStatusIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleStatusSize.width, y: floor((titleFrame.height - titleStatusSize.height) / 2.0)), size: titleStatusSize) titleTransition.updateFrame(view: self.titleStatusIconView, frame: CGRect(origin: CGPoint(x: nextIconX - titleStatusSize.width, y: floor((titleFrame.height - titleStatusSize.height) / 2.0)), size: titleStatusSize))
nextIconX -= titleStatusSize.width nextIconX -= titleStatusSize.width
if let image = self.titleRightIconNode.image { if let image = self.titleRightIconNode.image {
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size) titleTransition.updateFrame(node: self.titleRightIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size))
} }
} }
@ -1148,7 +1148,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
self.superview?.insertSubview(snapshotState.snapshotView, belowSubview: self) self.superview?.insertSubview(snapshotState.snapshotView, belowSubview: self)
let snapshotView = snapshotState.snapshotView let snapshotView = snapshotState.snapshotView
snapshotState.snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotState.snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.14, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview() snapshotView?.removeFromSuperview()
}) })
snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -offset.x, y: -offset.y), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -offset.x, y: -offset.y), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)

View File

@ -2,17 +2,19 @@ import Foundation
import UIKit import UIKit
import Display import Display
import ComponentFlow import ComponentFlow
import SwiftSignalKit
import PlainButtonComponent import PlainButtonComponent
import MultilineTextWithEntitiesComponent import MultilineTextComponent
import BundleIconComponent import BundleIconComponent
import TextFormat import TextFormat
import AccountContext import AccountContext
import LottieComponent
public final class FilterSelectorComponent: Component { public final class FilterSelectorComponent: Component {
public struct Colors: Equatable { public struct Colors: Equatable {
public var foreground: UIColor public var foreground: UIColor
public var background: UIColor public var background: UIColor
public init( public init(
foreground: UIColor, foreground: UIColor,
background: UIColor background: UIColor
@ -24,39 +26,45 @@ public final class FilterSelectorComponent: Component {
public struct Item: Equatable { public struct Item: Equatable {
public var id: AnyHashable public var id: AnyHashable
public var index: Int
public var iconName: String? public var iconName: String?
public var title: String public var title: String
public var action: (UIView) -> Void public var action: (UIView) -> Void
public init( public init(
id: AnyHashable, id: AnyHashable,
index: Int = 0,
iconName: String? = nil, iconName: String? = nil,
title: String, title: String,
action: @escaping (UIView) -> Void action: @escaping (UIView) -> Void
) { ) {
self.id = id self.id = id
self.index = index
self.iconName = iconName self.iconName = iconName
self.title = title self.title = title
self.action = action self.action = action
} }
public static func ==(lhs: Item, rhs: Item) -> Bool { public static func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.id == rhs.id && lhs.iconName == rhs.iconName && lhs.title == rhs.title return lhs.id == rhs.id && lhs.index == rhs.index && lhs.iconName == rhs.iconName && lhs.title == rhs.title
} }
} }
public let context: AccountContext? public let context: AccountContext?
public let colors: Colors public let colors: Colors
public let items: [Item] public let items: [Item]
public let selectedItemId: AnyHashable?
public init( public init(
context: AccountContext? = nil, context: AccountContext? = nil,
colors: Colors, colors: Colors,
items: [Item] items: [Item],
selectedItemId: AnyHashable?
) { ) {
self.context = context self.context = context
self.colors = colors self.colors = colors
self.items = items self.items = items
self.selectedItemId = selectedItemId
} }
public static func ==(lhs: FilterSelectorComponent, rhs: FilterSelectorComponent) -> Bool { public static func ==(lhs: FilterSelectorComponent, rhs: FilterSelectorComponent) -> Bool {
@ -69,6 +77,9 @@ public final class FilterSelectorComponent: Component {
if lhs.items != rhs.items { if lhs.items != rhs.items {
return false return false
} }
if lhs.selectedItemId != rhs.selectedItemId {
return false
}
return true return true
} }
@ -123,14 +134,14 @@ public final class FilterSelectorComponent: Component {
self.state = state self.state = state
let baseHeight: CGFloat = 28.0 let baseHeight: CGFloat = 28.0
var spacing: CGFloat = 6.0 var spacing: CGFloat = 6.0
let itemFont = Font.semibold(14.0) let itemFont = Font.semibold(14.0)
let allowScroll = true let allowScroll = true
var innerContentWidth: CGFloat = 0.0 var innerContentWidth: CGFloat = 0.0
var validIds: [AnyHashable] = [] var validIds: [AnyHashable] = []
var index = 0 var index = 0
var itemViews: [AnyHashable: (VisibleItem, CGSize, ComponentTransition)] = [:] var itemViews: [AnyHashable: (VisibleItem, CGSize, ComponentTransition)] = [:]
@ -150,15 +161,17 @@ public final class FilterSelectorComponent: Component {
validIds.append(itemId) validIds.append(itemId)
let itemSize = itemView.title.update( let itemSize = itemView.title.update(
transition: .immediate, transition: transition,
component: AnyComponent(PlainButtonComponent( component: AnyComponent(PlainButtonComponent(
content: AnyComponent(ItemComponent( content: AnyComponent(ItemComponent(
context: component.context, context: component.context,
index: item.index,
iconName: item.iconName, iconName: item.iconName,
text: item.title, text: item.title,
font: itemFont, font: itemFont,
color: component.colors.foreground, color: component.colors.foreground,
backgroundColor: component.colors.background backgroundColor: component.colors.background,
isSelected: itemId == component.selectedItemId
)), )),
effectAlignment: .center, effectAlignment: .center,
minSize: nil, minSize: nil,
@ -217,7 +230,7 @@ public final class FilterSelectorComponent: Component {
self.contentSize = CGSize(width: contentWidth, height: baseHeight) self.contentSize = CGSize(width: contentWidth, height: baseHeight)
self.disablesInteractiveTransitionGestureRecognizer = contentWidth > availableSize.width self.disablesInteractiveTransitionGestureRecognizer = contentWidth > availableSize.width
return CGSize(width: min(contentWidth, availableSize.width), height: baseHeight) return CGSize(width: min(contentWidth, availableSize.width), height: baseHeight)
} }
} }
@ -233,43 +246,52 @@ public final class FilterSelectorComponent: Component {
extension CGRect { extension CGRect {
func interpolate(with other: CGRect, fraction: CGFloat) -> CGRect { func interpolate(with other: CGRect, fraction: CGFloat) -> CGRect {
return CGRect( return CGRect(
x: self.origin.x * (1.0 - fraction) + (other.origin.x) * fraction, x: self.origin.x * (1.0 - fraction) + (other.origin.x) * fraction,
y: self.origin.y * (1.0 - fraction) + (other.origin.y) * fraction, y: self.origin.y * (1.0 - fraction) + (other.origin.y) * fraction,
width: self.size.width * (1.0 - fraction) + (other.size.width) * fraction, width: self.size.width * (1.0 - fraction) + (other.size.width) * fraction,
height: self.size.height * (1.0 - fraction) + (other.size.height) * fraction height: self.size.height * (1.0 - fraction) + (other.size.height) * fraction
) )
} }
} }
private final class ItemComponent: CombinedComponent { private final class ItemComponent: Component {
let context: AccountContext? let context: AccountContext?
let index: Int
let iconName: String? let iconName: String?
let text: String let text: String
let font: UIFont let font: UIFont
let color: UIColor let color: UIColor
let backgroundColor: UIColor let backgroundColor: UIColor
let isSelected: Bool
init( init(
context: AccountContext?, context: AccountContext?,
index: Int,
iconName: String?, iconName: String?,
text: String, text: String,
font: UIFont, font: UIFont,
color: UIColor, color: UIColor,
backgroundColor: UIColor backgroundColor: UIColor,
isSelected: Bool
) { ) {
self.context = context self.context = context
self.index = index
self.iconName = iconName self.iconName = iconName
self.text = text self.text = text
self.font = font self.font = font
self.color = color self.color = color
self.backgroundColor = backgroundColor self.backgroundColor = backgroundColor
self.isSelected = isSelected
} }
static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool { static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.index != rhs.index {
return false
}
if lhs.iconName != rhs.iconName { if lhs.iconName != rhs.iconName {
return false return false
} }
@ -285,44 +307,97 @@ private final class ItemComponent: CombinedComponent {
if lhs.backgroundColor != rhs.backgroundColor { if lhs.backgroundColor != rhs.backgroundColor {
return false return false
} }
if lhs.isSelected != rhs.isSelected {
return false
}
return true return true
} }
static var body: Body { public final class View: UIView {
let background = Child(RoundedRectangle.self) private var component: ItemComponent?
let title = Child(MultilineTextWithEntitiesComponent.self) private weak var state: EmptyComponentState?
let icon = Child(BundleIconComponent.self)
return { context in private let background = ComponentView<Empty>()
let component = context.component private let title = ComponentView<Empty>()
private let icon = ComponentView<Empty>()
private var isSelected = false
private var iconName: String?
private let playOnce = ActionSlot<Void>()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let previousComponent = self.component
self.component = component
self.state = state
let attributedTitle = NSMutableAttributedString(string: component.text, font: component.font, textColor: component.color) var animateTitleInDirection: CGFloat?
let range = (attributedTitle.string as NSString).range(of: "⭐️") if let previousComponent, previousComponent.text != component.text, !transition.animation.isImmediate, let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) {
if range.location != NSNotFound { snapshotView.frame = titleView.frame
attributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range) self.addSubview(snapshotView)
var direction: CGFloat = 1.0
if previousComponent.index < component.index {
direction = -1.0
}
snapshotView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 6.0 * direction), duration: 0.2, removeOnCompletion: false, additive: true)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
snapshotView.removeFromSuperview()
})
animateTitleInDirection = direction
} }
let title = title.update( let attributedTitle = NSAttributedString(string: component.text, font: component.font, textColor: component.color)
component: MultilineTextWithEntitiesComponent( let titleSize = self.title.update(
context: component.context, transition: .immediate,
animationCache: component.context?.animationCache, component: AnyComponent(MultilineTextComponent(
animationRenderer: component.context?.animationRenderer,
placeholderColor: .white,
text: .plain(attributedTitle) text: .plain(attributedTitle)
), )),
availableSize: context.availableSize, environment: {},
transition: .immediate containerSize: availableSize
) )
let icon = icon.update( let animationName = component.iconName ?? (component.isSelected ? "GiftFilterMenuOpen" : "GiftFilterMenuClose")
component: BundleIconComponent( let animationSize = component.iconName != nil ? CGSize(width: 22.0, height: 22.0) : CGSize(width: 10.0, height: 22.0)
name: component.iconName ?? "Item List/ExpandableSelectorArrows",
tintColor: component.color, let iconSize = self.icon.update(
maxSize: component.iconName != nil ? CGSize(width: 22.0, height: 22.0) : nil transition: transition,
), component: AnyComponent(LottieComponent(
availableSize: CGSize(width: 100, height: 100), content: LottieComponent.AppBundleContent(name: animationName),
transition: .immediate color: component.color,
playOnce: self.playOnce
)),
environment: {},
containerSize: CGSize(width: 22.0, height: 22.0)
) )
var playAnimation = false
if self.isSelected != component.isSelected || self.iconName != component.iconName {
if let iconName = component.iconName {
if component.isSelected {
playAnimation = true
} else if self.iconName != iconName {
playAnimation = true
}
self.iconName = iconName
} else {
playAnimation = true
}
self.isSelected = component.isSelected
}
if playAnimation {
self.playOnce.invoke(Void())
}
let padding: CGFloat = 12.0 let padding: CGFloat = 12.0
var leftPadding = padding var leftPadding = padding
@ -330,35 +405,68 @@ private final class ItemComponent: CombinedComponent {
leftPadding -= 4.0 leftPadding -= 4.0
} }
let spacing: CGFloat = 4.0 let spacing: CGFloat = 4.0
let totalWidth = title.size.width + icon.size.width + spacing let totalWidth = titleSize.width + animationSize.width + spacing
let size = CGSize(width: totalWidth + leftPadding + padding, height: 28.0) let size = CGSize(width: totalWidth + leftPadding + padding, height: 28.0)
let background = background.update(
component: RoundedRectangle( let backgroundSize = self.background.update(
transition: transition,
component: AnyComponent(RoundedRectangle(
color: component.backgroundColor, color: component.backgroundColor,
cornerRadius: 14.0 cornerRadius: 14.0
), )),
availableSize: size, environment: {},
transition: .immediate containerSize: size
) )
context.add(background
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) if let backgroundView = self.background.view {
) if backgroundView.superview == nil {
if let _ = component.iconName { self.addSubview(backgroundView)
context.add(title }
.position(CGPoint(x: size.width - padding - title.size.width / 2.0, y: size.height / 2.0)) transition.setPosition(view: backgroundView, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0))
) transition.setBounds(view: backgroundView, bounds: CGRect(origin: CGPoint(), size: backgroundSize))
context.add(icon
.position(CGPoint(x: leftPadding + icon.size.width / 2.0, y: size.height / 2.0))
)
} else {
context.add(title
.position(CGPoint(x: padding + title.size.width / 2.0, y: size.height / 2.0))
)
context.add(icon
.position(CGPoint(x: size.width - padding - icon.size.width / 2.0, y: size.height / 2.0))
)
} }
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
let titlePosition: CGPoint
if let _ = component.iconName {
titlePosition = CGPoint(x: size.width - padding - titleSize.width / 2.0, y: size.height / 2.0)
} else {
titlePosition = CGPoint(x: padding + titleSize.width / 2.0, y: size.height / 2.0)
}
if let animateTitleInDirection {
titleView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
titleView.center = CGPoint(x: titlePosition.x, y: titlePosition.y - 6.0 * animateTitleInDirection)
}
transition.setPosition(view: titleView, position: titlePosition)
titleView.bounds = CGRect(origin: CGPoint(), size: titleSize)
}
if let iconView = self.icon.view {
if iconView.superview == nil {
self.addSubview(iconView)
}
let iconPosition: CGPoint
if let _ = component.iconName {
iconPosition = CGPoint(x: leftPadding + iconSize.width / 2.0, y: size.height / 2.0)
} else {
iconPosition = CGPoint(x: size.width - padding - animationSize.width / 2.0, y: size.height / 2.0)
}
transition.setPosition(view: iconView, position: iconPosition)
transition.setBounds(view: iconView, bounds: CGRect(origin: CGPoint(), size: iconSize))
}
return size return size
} }
} }
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
} }

View File

@ -201,70 +201,27 @@ private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenu
private let actionSelected: (ContextMenuActionResult) -> Void private let actionSelected: (ContextMenuActionResult) -> Void
private let scrollNode: ASScrollNode private let scrollNode: ASScrollNode
private let actionNodes: [ContextControllerActionsListActionItemNode] private var actionNodes: [AnyHashable: ContextControllerActionsListActionItemNode] = [:]
private let separatorNodes: [ASDisplayNode] private var separatorNodes: [AnyHashable: ASDisplayNode] = [:]
private var searchDisposable: Disposable? private var searchDisposable: Disposable?
private var searchQuery = "" private var searchQuery = ""
private var itemHeights: [AnyHashable: CGFloat] = [:]
private var totalContentHeight: CGFloat = 0
private var itemFrames: [AnyHashable: CGRect] = [:]
init(presentationData: PresentationData, item: GiftAttributeListContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) { init(presentationData: PresentationData, item: GiftAttributeListContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
self.item = item self.item = item
self.presentationData = presentationData self.presentationData = presentationData.withUpdate(listsFontSize: .regular)
self.getController = getController self.getController = getController
self.actionSelected = actionSelected self.actionSelected = actionSelected
self.scrollNode = ASScrollNode() self.scrollNode = ASScrollNode()
var actionNodes: [ContextControllerActionsListActionItemNode] = []
var separatorNodes: [ASDisplayNode] = []
let selectedAttributes = Set(item.selectedAttributes)
let selectAllAction = ContextMenuActionItem(text: presentationData.strings.Gift_Store_SelectAll, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
}, iconPosition: .left, action: { _, f in
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
item.selectAll()
})
let selectAllActionNode = ContextControllerActionsListActionItemNode(context: item.context, getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: selectAllAction)
actionNodes.append(selectAllActionNode)
let separatorNode = ASDisplayNode()
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
separatorNodes.append(separatorNode)
for attribute in item.attributes {
guard let action = actionForAttribute(attribute: attribute, presentationData: presentationData, selectedAttributes: selectedAttributes, searchQuery: self.searchQuery, item: item, getController: getController) else {
continue
}
let actionNode = ContextControllerActionsListActionItemNode(context: item.context, getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: action)
actionNodes.append(actionNode)
if actionNodes.count != item.attributes.count {
let separatorNode = ASDisplayNode()
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
separatorNodes.append(separatorNode)
}
}
let nopAction: ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)? = nil
let emptyResultsAction = ContextMenuActionItem(text: presentationData.strings.Gift_Store_NoResults, textFont: .small, icon: { _ in return nil }, action: nopAction)
let emptyResultsActionNode = ContextControllerActionsListActionItemNode(context: item.context, getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: emptyResultsAction)
actionNodes.append(emptyResultsActionNode)
self.actionNodes = actionNodes
self.separatorNodes = separatorNodes
super.init() super.init()
self.addSubnode(self.scrollNode) self.addSubnode(self.scrollNode)
for separatorNode in self.separatorNodes {
self.scrollNode.addSubnode(separatorNode)
}
for actionNode in self.actionNodes {
self.scrollNode.addSubnode(actionNode)
}
self.searchDisposable = (item.searchQuery self.searchDisposable = (item.searchQuery
|> deliverOnMainQueue).start(next: { [weak self] searchQuery in |> deliverOnMainQueue).start(next: { [weak self] searchQuery in
@ -272,15 +229,7 @@ private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenu
return return
} }
self.searchQuery = searchQuery.trimmingCharacters(in: .whitespacesAndNewlines) self.searchQuery = searchQuery.trimmingCharacters(in: .whitespacesAndNewlines)
self.invalidateLayout()
var i = 1
for attribute in item.attributes {
guard let action = actionForAttribute(attribute: attribute, presentationData: presentationData, selectedAttributes: selectedAttributes, searchQuery: self.searchQuery, item: item, getController: getController) else {
continue
}
self.actionNodes[i].setItem(item: action)
i += 1
}
self.getController()?.requestLayout(transition: .immediate) self.getController()?.requestLayout(transition: .immediate)
}) })
} }
@ -297,96 +246,246 @@ private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenu
self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0) self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0)
} }
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
let minActionsWidth: CGFloat = 250.0 if let maxWidth = self.maxWidth {
let maxActionsWidth: CGFloat = 300.0 self.updateScrolling(maxWidth: maxWidth)
let constrainedWidth = min(constrainedWidth, maxActionsWidth) }
var maxWidth: CGFloat = 0.0 }
var contentHeight: CGFloat = 0.0
var heightsAndCompletions: [(Int, CGFloat, (CGSize, ContainedViewLayoutTransition) -> Void)] = [] enum ItemType {
case selectAll
case attribute(StarGift.UniqueGift.Attribute)
case noResults
case separator
}
private func getVisibleItems(in scrollView: UIScrollView, constrainedWidth: CGFloat) -> [(itemId: AnyHashable, itemType: ItemType, frame: CGRect)] {
let effectiveAttributes: [StarGift.UniqueGift.Attribute] let effectiveAttributes: [StarGift.UniqueGift.Attribute]
if self.searchQuery.isEmpty { if self.searchQuery.isEmpty {
effectiveAttributes = self.item.attributes effectiveAttributes = self.item.attributes
} else { } else {
effectiveAttributes = filteredAttributes(attributes: self.item.attributes, query: self.searchQuery) effectiveAttributes = filteredAttributes(attributes: self.item.attributes, query: self.searchQuery)
} }
let visibleAttributes = Set(effectiveAttributes.map { attribute -> AnyHashable in
switch attribute {
case let .model(_, file, _):
return file.fileId.id
case let .pattern(_, file, _):
return file.fileId.id
case let .backdrop(_, id, _, _, _, _, _):
return id
default:
fatalError()
}
})
for i in 0 ..< self.actionNodes.count { var items: [(itemId: AnyHashable, itemType: ItemType, frame: CGRect)] = []
let itemNode = self.actionNodes[i] var yOffset: CGFloat = 0
if !self.searchQuery.isEmpty && i == 0 {
itemNode.isHidden = true let defaultHeight: CGFloat = 42.0
continue if self.searchQuery.isEmpty {
} let selectAllId = AnyHashable("selectAll")
let height = self.itemHeights[selectAllId] ?? defaultHeight
let frame = CGRect(x: 0, y: yOffset, width: constrainedWidth, height: height)
items.append((selectAllId, .selectAll, frame))
yOffset += height
if i > 0 && i < self.actionNodes.count - 1 { let separatorId = AnyHashable("separator_selectAll")
let attribute = self.item.attributes[i - 1] let separatorFrame = CGRect(x: 0, y: yOffset, width: constrainedWidth, height: UIScreenPixel)
let attributeId: AnyHashable items.append((separatorId, .separator, separatorFrame))
switch attribute { yOffset += UIScreenPixel
case let .model(_, file, _):
attributeId = AnyHashable(file.fileId.id)
case let .pattern(_, file, _):
attributeId = AnyHashable(file.fileId.id)
case let .backdrop(_, id, _, _, _, _, _):
attributeId = AnyHashable(id)
default:
fatalError()
}
if !visibleAttributes.contains(attributeId) {
itemNode.isHidden = true
continue
}
}
if i == self.actionNodes.count - 1 {
if !visibleAttributes.isEmpty {
itemNode.isHidden = true
continue
} else {
}
}
itemNode.isHidden = false
let (minSize, complete) = itemNode.update(presentationData: self.presentationData, constrainedSize: CGSize(width: constrainedWidth, height: constrainedHeight))
maxWidth = max(maxWidth, minSize.width)
heightsAndCompletions.append((i, minSize.height, complete))
contentHeight += minSize.height
} }
maxWidth = max(maxWidth, minActionsWidth) for (index, attribute) in effectiveAttributes.enumerated() {
let attributeId = self.getAttributeId(from: attribute)
let height = self.itemHeights[attributeId] ?? defaultHeight
let frame = CGRect(x: 0, y: yOffset, width: constrainedWidth, height: height)
items.append((attributeId, .attribute(attribute), frame))
yOffset += height
if index < effectiveAttributes.count - 1 {
let separatorId = AnyHashable("separator_\(attributeId)")
let separatorFrame = CGRect(x: 0, y: yOffset, width: constrainedWidth, height: UIScreenPixel)
items.append((separatorId, .separator, separatorFrame))
yOffset += UIScreenPixel
}
}
if !self.searchQuery.isEmpty && effectiveAttributes.isEmpty {
let noResultsId = AnyHashable("noResults")
let height = self.itemHeights[noResultsId] ?? defaultHeight
let frame = CGRect(x: 0, y: yOffset, width: constrainedWidth, height: height)
items.append((noResultsId, .noResults, frame))
yOffset += height
}
self.totalContentHeight = yOffset
for (itemId, _, frame) in items {
self.itemFrames[itemId] = frame
}
let visibleBounds = scrollView.bounds.insetBy(dx: 0.0, dy: -100.0)
return items.filter { visibleBounds.intersects($0.frame) }
}
private func getAttributeId(from attribute: StarGift.UniqueGift.Attribute) -> AnyHashable {
switch attribute {
case let .model(_, file, _):
return AnyHashable("model_\(file.fileId.id)")
case let .pattern(_, file, _):
return AnyHashable("pattern_\(file.fileId.id)")
case let .backdrop(_, id, _, _, _, _, _):
return AnyHashable("backdrop_\(id)")
default:
return AnyHashable("unknown")
}
}
private var maxWidth: CGFloat?
private func updateScrolling(maxWidth: CGFloat) {
let scrollView = self.scrollNode.view
let constrainedWidth = scrollView.bounds.width
let visibleItems = self.getVisibleItems(in: scrollView, constrainedWidth: constrainedWidth)
var validNodeIds: Set<AnyHashable> = []
for (itemId, itemType, frame) in visibleItems {
validNodeIds.insert(itemId)
switch itemType {
case .selectAll:
if self.actionNodes[itemId] == nil {
let selectAllAction = ContextMenuActionItem(text: presentationData.strings.Gift_Store_SelectAll, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
}, iconPosition: .left, action: { _, f in
self.getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
self.item.selectAll()
})
let actionNode = ContextControllerActionsListActionItemNode(
context: self.item.context,
getController: self.getController,
requestDismiss: self.actionSelected,
requestUpdateAction: { _, _ in },
item: selectAllAction
)
self.actionNodes[itemId] = actionNode
self.scrollNode.addSubnode(actionNode)
}
case .attribute(let attribute):
if self.actionNodes[itemId] == nil {
let selectedAttributes = Set(self.item.selectedAttributes)
guard let action = actionForAttribute(
attribute: attribute,
presentationData: self.presentationData,
selectedAttributes: selectedAttributes,
searchQuery: self.searchQuery,
item: self.item,
getController: self.getController
) else { continue }
let actionNode = ContextControllerActionsListActionItemNode(
context: self.item.context,
getController: self.getController,
requestDismiss: self.actionSelected,
requestUpdateAction: { _, _ in },
item: action
)
self.actionNodes[itemId] = actionNode
self.scrollNode.addSubnode(actionNode)
} else {
let selectedAttributes = Set(self.item.selectedAttributes)
if let action = actionForAttribute(
attribute: attribute,
presentationData: self.presentationData,
selectedAttributes: selectedAttributes,
searchQuery: self.searchQuery,
item: self.item,
getController: self.getController
) {
self.actionNodes[itemId]?.setItem(item: action)
}
}
case .noResults:
if self.actionNodes[itemId] == nil {
let nopAction: ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)? = nil
let emptyResultsAction = ContextMenuActionItem(
text: presentationData.strings.Gift_Store_NoResults,
textFont: .small,
icon: { _ in return nil },
action: nopAction
)
let actionNode = ContextControllerActionsListActionItemNode(
context: self.item.context,
getController: self.getController,
requestDismiss: self.actionSelected,
requestUpdateAction: { _, _ in },
item: emptyResultsAction
)
self.actionNodes[itemId] = actionNode
self.scrollNode.addSubnode(actionNode)
}
case .separator:
if self.separatorNodes[itemId] == nil {
let separatorNode = ASDisplayNode()
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
self.separatorNodes[itemId] = separatorNode
self.scrollNode.addSubnode(separatorNode)
}
}
if let actionNode = self.actionNodes[itemId] {
actionNode.frame = frame
let (minSize, complete) = actionNode.update(presentationData: self.presentationData, constrainedSize: frame.size)
self.itemHeights[itemId] = minSize.height
complete(CGSize(width: maxWidth, height: minSize.height), .immediate)
} else if let separatorNode = self.separatorNodes[itemId] {
separatorNode.frame = frame
}
}
var nodesToRemove: [AnyHashable] = []
for (nodeId, node) in self.actionNodes {
if !validNodeIds.contains(nodeId) {
nodesToRemove.append(nodeId)
node.removeFromSupernode()
}
}
for nodeId in nodesToRemove {
self.actionNodes.removeValue(forKey: nodeId)
}
var separatorsToRemove: [AnyHashable] = []
for (separatorId, separatorNode) in self.separatorNodes {
if !validNodeIds.contains(separatorId) {
separatorsToRemove.append(separatorId)
separatorNode.removeFromSupernode()
}
}
for separatorId in separatorsToRemove {
self.separatorNodes.removeValue(forKey: separatorId)
}
}
private func invalidateLayout() {
self.itemHeights.removeAll()
self.itemFrames.removeAll()
self.totalContentHeight = 0.0
}
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let minActionsWidth: CGFloat = 250.0
let maxActionsWidth: CGFloat = 300.0
let constrainedWidth = min(constrainedWidth, maxActionsWidth)
let maxWidth = max(constrainedWidth, minActionsWidth)
let maxHeight: CGFloat = min(360.0, constrainedHeight - 108.0) let maxHeight: CGFloat = min(360.0, constrainedHeight - 108.0)
return (CGSize(width: maxWidth, height: min(maxHeight, contentHeight)), { size, transition in if self.totalContentHeight == 0 {
var verticalOffset: CGFloat = 0.0 let _ = self.getVisibleItems(in: UIScrollView(), constrainedWidth: constrainedWidth)
for (i, itemHeight, itemCompletion) in heightsAndCompletions { }
let itemNode = self.actionNodes[i]
return (CGSize(width: maxWidth, height: min(maxHeight, self.totalContentHeight)), { size, transition in
let itemSize = CGSize(width: maxWidth, height: itemHeight) self.maxWidth = maxWidth
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: itemSize))
itemCompletion(itemSize, transition)
verticalOffset += itemHeight
if i < self.actionNodes.count - 2 {
let separatorNode = self.separatorNodes[i]
separatorNode.frame = CGRect(x: 0, y: verticalOffset, width: size.width, height: UIScreenPixel)
}
}
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight) self.scrollNode.view.contentSize = CGSize(width: size.width, height: self.totalContentHeight)
self.updateScrolling(maxWidth: maxWidth)
}) })
} }
@ -417,7 +516,7 @@ private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenu
} }
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
for actionNode in self.actionNodes { for (_, actionNode) in self.actionNodes {
actionNode.updateIsHighlighted(isHighlighted: false) actionNode.updateIsHighlighted(isHighlighted: false)
} }
} }

View File

@ -98,6 +98,8 @@ final class GiftStoreScreenComponent: Component {
private var initialCount: Int32? private var initialCount: Int32?
private var showLoading = true private var showLoading = true
private var selectedFilterId: AnyHashable?
private var component: GiftStoreScreenComponent? private var component: GiftStoreScreenComponent?
private(set) weak var state: State? private(set) weak var state: State?
private var environment: EnvironmentType? private var environment: EnvironmentType?
@ -502,6 +504,13 @@ final class GiftStoreScreenComponent: Component {
}))) })))
let contextController = ContextController(presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil) let contextController = ContextController(presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
contextController.dismissed = { [weak self] in
guard let self else {
return
}
self.selectedFilterId = nil
self.state?.updated()
}
controller.presentInGlobalOverlay(contextController) controller.presentInGlobalOverlay(contextController)
} }
@ -603,6 +612,13 @@ final class GiftStoreScreenComponent: Component {
items: .single(ContextController.Items(content: .list(items))), items: .single(ContextController.Items(content: .list(items))),
gesture: nil gesture: nil
) )
contextController.dismissed = { [weak self] in
guard let self else {
return
}
self.selectedFilterId = nil
self.state?.updated()
}
controller.presentInGlobalOverlay(contextController) controller.presentInGlobalOverlay(contextController)
} }
@ -704,6 +720,13 @@ final class GiftStoreScreenComponent: Component {
items: .single(ContextController.Items(content: .list(items))), items: .single(ContextController.Items(content: .list(items))),
gesture: nil gesture: nil
) )
contextController.dismissed = { [weak self] in
guard let self else {
return
}
self.selectedFilterId = nil
self.state?.updated()
}
controller.presentInGlobalOverlay(contextController) controller.presentInGlobalOverlay(contextController)
} }
@ -805,6 +828,13 @@ final class GiftStoreScreenComponent: Component {
items: .single(ContextController.Items(content: .list(items))), items: .single(ContextController.Items(content: .list(items))),
gesture: nil gesture: nil
) )
contextController.dismissed = { [weak self] in
guard let self else {
return
}
self.selectedFilterId = nil
self.state?.updated()
}
controller.presentInGlobalOverlay(contextController) controller.presentInGlobalOverlay(contextController)
} }
@ -996,29 +1026,43 @@ final class GiftStoreScreenComponent: Component {
let optionWidth = (availableSize.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0 let optionWidth = (availableSize.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
var sortingTitle = environment.strings.Gift_Store_Sort_Date var sortingTitle = environment.strings.Gift_Store_Sort_Date
var sortingIcon: String = "Peer Info/SortDate" var sortingIcon: String = "GiftFilterDate"
var sortingIndex: Int = 0
if let sorting = self.state?.starGiftsState?.sorting { if let sorting = self.state?.starGiftsState?.sorting {
switch sorting { switch sorting {
case .date:
sortingTitle = environment.strings.Gift_Store_Sort_Date
sortingIcon = "Peer Info/SortDate"
case .value: case .value:
sortingTitle = environment.strings.Gift_Store_Sort_Price sortingTitle = environment.strings.Gift_Store_Sort_Price
sortingIcon = "Peer Info/SortValue" sortingIcon = "GiftFilterPrice"
sortingIndex = 0
case .date:
sortingTitle = environment.strings.Gift_Store_Sort_Date
sortingIcon = "GiftFilterDate"
sortingIndex = 1
case .number: case .number:
sortingTitle = environment.strings.Gift_Store_Sort_Number sortingTitle = environment.strings.Gift_Store_Sort_Number
sortingIcon = "Peer Info/SortNumber" sortingIcon = "GiftFilterNumber"
sortingIndex = 2
} }
} }
enum FilterItemId: Int32 {
case sort
case model
case backdrop
case symbol
}
var filterItems: [FilterSelectorComponent.Item] = [] var filterItems: [FilterSelectorComponent.Item] = []
filterItems.append(FilterSelectorComponent.Item( filterItems.append(FilterSelectorComponent.Item(
id: AnyHashable(0), id: AnyHashable(FilterItemId.sort),
index: sortingIndex,
iconName: sortingIcon, iconName: sortingIcon,
title: sortingTitle, title: sortingTitle,
action: { [weak self] view in action: { [weak self] view in
if let self { if let self {
self.selectedFilterId = AnyHashable(FilterItemId.sort)
self.openSortContextMenu(sourceView: view) self.openSortContextMenu(sourceView: view)
self.state?.updated()
} }
} }
)) ))
@ -1035,10 +1079,10 @@ final class GiftStoreScreenComponent: Component {
switch attribute { switch attribute {
case .model: case .model:
modelCount += 1 modelCount += 1
case .pattern:
symbolCount += 1
case .backdrop: case .backdrop:
backdropCount += 1 backdropCount += 1
case .pattern:
symbolCount += 1
} }
} }
@ -1054,29 +1098,35 @@ final class GiftStoreScreenComponent: Component {
} }
filterItems.append(FilterSelectorComponent.Item( filterItems.append(FilterSelectorComponent.Item(
id: AnyHashable(1), id: AnyHashable(FilterItemId.model),
title: modelTitle, title: modelTitle,
action: { [weak self] view in action: { [weak self] view in
if let self { if let self {
self.selectedFilterId = AnyHashable(FilterItemId.model)
self.openModelContextMenu(sourceView: view) self.openModelContextMenu(sourceView: view)
self.state?.updated()
} }
} }
)) ))
filterItems.append(FilterSelectorComponent.Item( filterItems.append(FilterSelectorComponent.Item(
id: AnyHashable(2), id: AnyHashable(FilterItemId.backdrop),
title: backdropTitle, title: backdropTitle,
action: { [weak self] view in action: { [weak self] view in
if let self { if let self {
self.selectedFilterId = AnyHashable(FilterItemId.backdrop)
self.openBackdropContextMenu(sourceView: view) self.openBackdropContextMenu(sourceView: view)
self.state?.updated()
} }
} }
)) ))
filterItems.append(FilterSelectorComponent.Item( filterItems.append(FilterSelectorComponent.Item(
id: AnyHashable(3), id: AnyHashable(FilterItemId.symbol),
title: symbolTitle, title: symbolTitle,
action: { [weak self] view in action: { [weak self] view in
if let self { if let self {
self.selectedFilterId = AnyHashable(FilterItemId.symbol)
self.openSymbolContextMenu(sourceView: view) self.openSymbolContextMenu(sourceView: view)
self.state?.updated()
} }
} }
)) ))
@ -1092,7 +1142,8 @@ final class GiftStoreScreenComponent: Component {
foreground: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.65), foreground: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.65),
background: theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85) background: theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85)
), ),
items: filterItems items: filterItems,
selectedItemId: self.selectedFilterId
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width - 10.0 * 2.0, height: 50.0) containerSize: CGSize(width: availableSize.width - 10.0 * 2.0, height: 50.0)
@ -1193,8 +1244,17 @@ final class GiftStoreScreenComponent: Component {
guard let self else { guard let self else {
return return
} }
let previousFilterAttributes = self.starGiftsState?.filterAttributes
let previousSorting = self.starGiftsState?.sorting
self.starGiftsState = state self.starGiftsState = state
self.updated()
var transition: ComponentTransition = .immediate
if let previousFilterAttributes, previousFilterAttributes != state.filterAttributes {
transition = .easeInOut(duration: 0.25)
} else if let previousSorting, previousSorting != state.sorting {
transition = .easeInOut(duration: 0.25)
}
self.updated(transition: transition)
}) })
} }

View File

@ -1657,6 +1657,10 @@ private final class GiftViewSheetContent: CombinedComponent {
convertStars = nil convertStars = nil
titleString = "" titleString = ""
} }
if !canUpgrade, let gift = state.starGiftsMap[giftId], let _ = gift.upgradeStars {
canUpgrade = true
}
var showUpgradePreview = false var showUpgradePreview = false
if state.inUpgradePreview, let _ = state.sampleGiftAttributes { if state.inUpgradePreview, let _ = state.sampleGiftAttributes {

View File

@ -1890,8 +1890,8 @@ public class TrimView: UIView {
effectiveHandleWidth = 16.0 effectiveHandleWidth = 16.0
fullTrackHeight = 33.0 fullTrackHeight = 33.0
capsuleOffset = 8.0 capsuleOffset = 8.0
color = .clear color = theme.chat.inputPanel.panelControlAccentColor
highlightColor = .clear highlightColor = theme.chat.inputPanel.panelControlAccentColor
self.zoneView.backgroundColor = UIColor(white: 1.0, alpha: 0.4) self.zoneView.backgroundColor = UIColor(white: 1.0, alpha: 0.4)
@ -1902,7 +1902,19 @@ public class TrimView: UIView {
context.fill(CGRect(origin: .zero, size: CGSize(width: 1.0, height: size.height))) context.fill(CGRect(origin: .zero, size: CGSize(width: 1.0, height: size.height)))
context.fill(CGRect(origin: CGPoint(x: size.width - 1.0, y: 0.0), size: CGSize(width: 1.0, height: size.height))) context.fill(CGRect(origin: CGPoint(x: size.width - 1.0, y: 0.0), size: CGSize(width: 1.0, height: size.height)))
})?.withRenderingMode(.alwaysTemplate).resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 1.0, bottom: 0.0, right: 1.0)) })?.withRenderingMode(.alwaysTemplate).resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 1.0, bottom: 0.0, right: 1.0))
let handleImage = generateImage(CGSize(width: effectiveHandleWidth, height: fullTrackHeight), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
let path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: CGSize(width: size.width * 2.0, height: size.height)), cornerRadius: 16.5)
context.addPath(path.cgPath)
context.fillPath()
})?.withRenderingMode(.alwaysTemplate)
self.leftHandleView.image = handleImage
self.rightHandleView.image = handleImage
self.leftCapsuleView.backgroundColor = .white self.leftCapsuleView.backgroundColor = .white
self.rightCapsuleView.backgroundColor = .white self.rightCapsuleView.backgroundColor = .white
} }

View File

@ -1842,12 +1842,13 @@ public final class MessageInputPanelComponent: Component {
} }
} }
let lightFieldColor = UIColor(white: 1.0, alpha: 0.09) var lightFieldColor = UIColor(white: 1.0, alpha: 0.09)
var fieldBackgroundIsDark = false var fieldBackgroundIsDark = false
if component.useGrayBackground { if component.useGrayBackground {
fieldBackgroundIsDark = false fieldBackgroundIsDark = false
} else if component.style == .media { } else if component.style == .media {
fieldBackgroundIsDark = false fieldBackgroundIsDark = false
lightFieldColor = UIColor(white: 0.2, alpha: 0.45)
} else if self.textFieldExternalState.hasText && component.alwaysDarkWhenHasText { } else if self.textFieldExternalState.hasText && component.alwaysDarkWhenHasText {
fieldBackgroundIsDark = true fieldBackgroundIsDark = true
} else if isEditing || component.style == .story || component.style == .editor { } else if isEditing || component.style == .story || component.style == .editor {

View File

@ -127,7 +127,7 @@ import PostSuggestionsSettingsScreen
import ChatSendStarsScreen import ChatSendStarsScreen
extension ChatControllerImpl { extension ChatControllerImpl {
func reloadChatLocation(chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, historyNode: ChatHistoryListNodeImpl, isReady: Promise<Bool>?) { func reloadChatLocation(chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, historyNode: ChatHistoryListNodeImpl, apply: @escaping ((Bool) -> Void) -> Void) {
self.contentDataReady.set(false) self.contentDataReady.set(false)
self.contentDataDisposable?.dispose() self.contentDataDisposable?.dispose()
@ -153,35 +153,49 @@ extension ChatControllerImpl {
currentChatListFilter: self.currentChatListFilter, currentChatListFilter: self.currentChatListFilter,
customChatNavigationStack: self.customChatNavigationStack, customChatNavigationStack: self.customChatNavigationStack,
presentationData: self.presentationData, presentationData: self.presentationData,
historyNode: historyNode historyNode: historyNode,
inviteRequestsContext: self.contentData?.inviteRequestsContext
) )
self.pendingContentData = (contentData, historyNode) self.pendingContentData = (contentData, historyNode)
self.contentDataDisposable = (contentData.isReady.get() self.contentDataDisposable = (contentData.isReady.get()
|> filter { $0 } |> filter { $0 }
|> take(1) |> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak self, weak contentData] _ in |> deliverOnMainQueue).startStrict(next: { [weak self, weak contentData, weak historyNode] _ in
guard let self, let contentData, self.pendingContentData?.contentData === contentData else { guard let self, let contentData, self.pendingContentData?.contentData === contentData else {
return return
} }
self.contentData = contentData
self.pendingContentData = nil
self.contentDataUpdated(synchronous: true, previousState: contentData.state)
self.chatThemeEmoticonPromise.set(contentData.chatThemeEmoticonPromise.get()) apply({ [weak self, weak contentData] forceAnimation in
self.chatWallpaperPromise.set(contentData.chatWallpaperPromise.get()) guard let self, let contentData, self.pendingContentData?.contentData === contentData else {
self.contentDataReady.set(true)
contentData.onUpdated = { [weak self, weak contentData] previousState in
guard let self, let contentData, self.contentData === contentData else {
return return
} }
self.contentDataUpdated(synchronous: false, previousState: previousState)
} self.contentData = contentData
self.pendingContentData = nil
self.contentDataUpdated(synchronous: true, forceAnimation: forceAnimation, previousState: contentData.state)
self.chatThemeEmoticonPromise.set(contentData.chatThemeEmoticonPromise.get())
self.chatWallpaperPromise.set(contentData.chatWallpaperPromise.get())
if let historyNode {
self.setupChatHistoryNode(historyNode: historyNode)
historyNode.contentPositionChanged(historyNode.visibleContentOffset())
}
self.contentDataReady.set(true)
contentData.onUpdated = { [weak self, weak contentData] previousState in
guard let self, let contentData, self.contentData === contentData else {
return
}
self.contentDataUpdated(synchronous: false, forceAnimation: false, previousState: previousState)
}
})
}) })
} }
func contentDataUpdated(synchronous: Bool, previousState: ContentData.State) { func contentDataUpdated(synchronous: Bool, forceAnimation: Bool, previousState: ContentData.State) {
guard let contentData = self.contentData else { guard let contentData = self.contentData else {
return return
} }
@ -235,7 +249,16 @@ extension ChatControllerImpl {
self.chatDisplayNode.overlayTitle = contentData.overlayTitle self.chatDisplayNode.overlayTitle = contentData.overlayTitle
self.chatDisplayNode.historyNode.nextChannelToRead = contentData.state.nextChannelToRead self.chatDisplayNode.historyNode.nextChannelToRead = contentData.state.nextChannelToRead.flatMap { nextChannelToRead -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation)? in
return (
nextChannelToRead.peer,
nextChannelToRead.threadData.flatMap { threadData -> (id: Int64, data: MessageHistoryThreadData) in
return (threadData.id, threadData.data)
},
nextChannelToRead.unreadCount,
nextChannelToRead.location
)
}
self.chatDisplayNode.historyNode.nextChannelToReadDisplayName = contentData.state.nextChannelToReadDisplayName self.chatDisplayNode.historyNode.nextChannelToReadDisplayName = contentData.state.nextChannelToReadDisplayName
self.updateNextChannelToReadVisibility() self.updateNextChannelToReadVisibility()
@ -273,6 +296,18 @@ extension ChatControllerImpl {
didDisplayActionsPanel = true didDisplayActionsPanel = true
} }
var previousInvitationPeers: [EnginePeer] = []
if let requestsState = previousState.requestsState {
previousInvitationPeers = Array(requestsState.importers.compactMap({ $0.peer.peer.flatMap({ EnginePeer($0) }) }).prefix(3))
}
var previousInvitationRequestsPeersDismissed = false
if let dismissedInvitationRequests = previousState.dismissedInvitationRequests, Set(previousInvitationPeers.map({ $0.id.toInt64() })) == Set(dismissedInvitationRequests) {
previousInvitationRequestsPeersDismissed = true
}
if let requestsState = previousState.requestsState, requestsState.count > 0 && !previousInvitationRequestsPeersDismissed {
didDisplayActionsPanel = true
}
var displayActionsPanel = false var displayActionsPanel = false
if let contactStatus = contentData.state.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { if let contactStatus = contentData.state.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
if !peerStatusSettings.flags.isEmpty { if !peerStatusSettings.flags.isEmpty {
@ -293,19 +328,35 @@ extension ChatControllerImpl {
if self.presentationInterfaceState.search != nil && contentData.state.hasSearchTags { if self.presentationInterfaceState.search != nil && contentData.state.hasSearchTags {
displayActionsPanel = true displayActionsPanel = true
} }
var invitationPeers: [EnginePeer] = []
if let requestsState = contentData.state.requestsState {
invitationPeers = Array(requestsState.importers.compactMap({ $0.peer.peer.flatMap({ EnginePeer($0) }) }).prefix(3))
}
var invitationRequestsPeersDismissed = false
if let dismissedInvitationRequests = contentData.state.dismissedInvitationRequests, Set(invitationPeers.map({ $0.id.toInt64() })) == Set(dismissedInvitationRequests) {
invitationRequestsPeersDismissed = true
}
if let requestsState = contentData.state.requestsState, requestsState.count > 0 && !invitationRequestsPeersDismissed {
displayActionsPanel = true
}
if displayActionsPanel != didDisplayActionsPanel { if displayActionsPanel != didDisplayActionsPanel {
animated = true animated = true
} }
if previousState.pinnedMessage != contentData.state.pinnedMessage { if previousState.pinnedMessage != contentData.state.pinnedMessage {
animated = true animated = true
} }
if forceAnimation {
animated = true
}
self.updateChatPresentationInterfaceState(animated: animated && self.willAppear, interactive: false, { presentationInterfaceState in self.updateChatPresentationInterfaceState(animated: animated && self.willAppear, interactive: false, { presentationInterfaceState in
var presentationInterfaceState = presentationInterfaceState var presentationInterfaceState = presentationInterfaceState
presentationInterfaceState = presentationInterfaceState.updatedPeer({ _ in presentationInterfaceState = presentationInterfaceState.updatedPeer({ _ in
return contentData.state.renderedPeer return contentData.state.renderedPeer
}) })
presentationInterfaceState = presentationInterfaceState.updatedChatLocation(contentData.chatLocation)
presentationInterfaceState = presentationInterfaceState.updatedIsNotAccessible(contentData.state.isNotAccessible) presentationInterfaceState = presentationInterfaceState.updatedIsNotAccessible(contentData.state.isNotAccessible)
presentationInterfaceState = presentationInterfaceState.updatedContactStatus(contentData.state.contactStatus) presentationInterfaceState = presentationInterfaceState.updatedContactStatus(contentData.state.contactStatus)
presentationInterfaceState = presentationInterfaceState.updatedHasBots(contentData.state.hasBots) presentationInterfaceState = presentationInterfaceState.updatedHasBots(contentData.state.hasBots)
@ -394,7 +445,6 @@ extension ChatControllerImpl {
if let dismissedInvitationRequests = contentData.state.dismissedInvitationRequests, Set(peers.map({ $0.id.toInt64() })) == Set(dismissedInvitationRequests) { if let dismissedInvitationRequests = contentData.state.dismissedInvitationRequests, Set(peers.map({ $0.id.toInt64() })) == Set(dismissedInvitationRequests) {
peersDismissed = true peersDismissed = true
} }
if let requestsState = contentData.state.requestsState, requestsState.count > 0 && !peersDismissed { if let requestsState = contentData.state.requestsState, requestsState.count > 0 && !peersDismissed {
if !context.contains(where: { if !context.contains(where: {
switch $0 { switch $0 {
@ -749,7 +799,7 @@ extension ChatControllerImpl {
})).startStandalone() })).startStandalone()
} }
self.setupChatHistoryNode() self.setupChatHistoryNode(historyNode: self.chatDisplayNode.historyNode)
self.chatDisplayNode.requestLayout = { [weak self] transition in self.chatDisplayNode.requestLayout = { [weak self] transition in
self?.requestLayout(transition: transition) self?.requestLayout(transition: transition)
@ -1206,7 +1256,7 @@ extension ChatControllerImpl {
self.scrollToEndOfHistory() self.scrollToEndOfHistory()
} }
} else { } else {
if let messageId = self.historyNavigationStack.removeLast() { if let messageId = self.contentData?.historyNavigationStack.removeLast() {
self.navigateToMessage(from: nil, to: .id(messageId.id, NavigateToMessageParams(timestamp: nil, quote: nil)), rememberInStack: false) self.navigateToMessage(from: nil, to: .id(messageId.id, NavigateToMessageParams(timestamp: nil, quote: nil)), rememberInStack: false)
} else { } else {
if case .known = self.chatDisplayNode.historyNode.visibleContentOffset() { if case .known = self.chatDisplayNode.historyNode.visibleContentOffset() {
@ -4295,7 +4345,7 @@ extension ChatControllerImpl {
self.displayNodeDidLoad() self.displayNodeDidLoad()
} }
func setupChatHistoryNode() { func setupChatHistoryNode(historyNode: ChatHistoryListNodeImpl) {
do { do {
let peerId = self.chatLocation.peerId let peerId = self.chatLocation.peerId
if let subject = self.subject, case .scheduledMessages = subject { if let subject = self.subject, case .scheduledMessages = subject {
@ -4582,8 +4632,10 @@ extension ChatControllerImpl {
} }
} }
self.chatDisplayNode.historyNode.contentPositionChanged = { [weak self] offset in historyNode.contentPositionChanged = { [weak self, weak historyNode] offset in
guard let strongSelf = self else { return } guard let strongSelf = self, let historyNode, strongSelf.chatDisplayNode.historyNode === historyNode else {
return
}
var minOffsetForNavigation: CGFloat = 40.0 var minOffsetForNavigation: CGFloat = 40.0
strongSelf.chatDisplayNode.historyNode.enumerateItemNodes { itemNode in strongSelf.chatDisplayNode.historyNode.enumerateItemNodes { itemNode in
@ -4632,7 +4684,7 @@ extension ChatControllerImpl {
strongSelf.chatDisplayNode.updatePlainInputSeparatorAlpha(plainInputSeparatorAlpha, transition: .animated(duration: 0.2, curve: .easeInOut)) strongSelf.chatDisplayNode.updatePlainInputSeparatorAlpha(plainInputSeparatorAlpha, transition: .animated(duration: 0.2, curve: .easeInOut))
} }
self.chatDisplayNode.historyNode.scrolledToIndex = { [weak self] toSubject, initial in historyNode.scrolledToIndex = { [weak self] toSubject, initial in
if let strongSelf = self, case let .message(index) = toSubject.index { if let strongSelf = self, case let .message(index) = toSubject.index {
if case let .message(messageSubject, _, _, _) = strongSelf.subject, initial, case let .id(messageId) = messageSubject, messageId != index.id { if case let .message(messageSubject, _, _, _) = strongSelf.subject, initial, case let .id(messageId) = messageSubject, messageId != index.id {
if messageId.peerId == index.id.peerId { if messageId.peerId == index.id.peerId {
@ -4693,16 +4745,16 @@ extension ChatControllerImpl {
} }
} }
self.chatDisplayNode.historyNode.scrolledToSomeIndex = { [weak self] in historyNode.scrolledToSomeIndex = { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.contentData?.scrolledToMessageIdValue = nil strongSelf.contentData?.scrolledToMessageIdValue = nil
} }
self.chatDisplayNode.historyNode.maxVisibleMessageIndexUpdated = { [weak self] index in historyNode.maxVisibleMessageIndexUpdated = { [weak self] index in
if let strongSelf = self, !strongSelf.historyNavigationStack.isEmpty { if let strongSelf = self, let contentData = strongSelf.contentData, !contentData.historyNavigationStack.isEmpty {
strongSelf.historyNavigationStack.filterOutIndicesLessThan(index) contentData.historyNavigationStack.filterOutIndicesLessThan(index)
} }
} }
@ -4723,7 +4775,7 @@ extension ChatControllerImpl {
hasActiveCalls = .single(false) hasActiveCalls = .single(false)
} }
let shouldBeActive = combineLatest(self.context.sharedContext.mediaManager.audioSession.isPlaybackActive() |> deliverOnMainQueue, self.chatDisplayNode.historyNode.hasVisiblePlayableItemNodes, hasActiveCalls) let shouldBeActive = combineLatest(self.context.sharedContext.mediaManager.audioSession.isPlaybackActive() |> deliverOnMainQueue, historyNode.hasVisiblePlayableItemNodes, hasActiveCalls)
|> mapToSignal { [weak self] isPlaybackActive, hasVisiblePlayableItemNodes, hasActiveCalls -> Signal<Bool, NoError> in |> mapToSignal { [weak self] isPlaybackActive, hasVisiblePlayableItemNodes, hasActiveCalls -> Signal<Bool, NoError> in
if hasVisiblePlayableItemNodes && !isPlaybackActive && !hasActiveCalls { if hasVisiblePlayableItemNodes && !isPlaybackActive && !hasActiveCalls {
return Signal<Bool, NoError> { [weak self] subscriber in return Signal<Bool, NoError> { [weak self] subscriber in
@ -4776,7 +4828,7 @@ extension ChatControllerImpl {
downPressed: buttonAction downPressed: buttonAction
) )
self.chatDisplayNode.historyNode.openNextChannelToRead = { [weak self] peer, threadData, location in historyNode.openNextChannelToRead = { [weak self] peer, threadData, location in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -4831,7 +4883,7 @@ extension ChatControllerImpl {
} }
} }
self.chatDisplayNode.historyNode.beganDragging = { [weak self] in historyNode.beganDragging = { [weak self] in
guard let self else { guard let self else {
return return
} }
@ -4846,7 +4898,7 @@ extension ChatControllerImpl {
} }
} }
self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode, isTracking in historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode, isTracking in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -4876,23 +4928,22 @@ extension ChatControllerImpl {
strongSelf.chatDisplayNode.loadingPlaceholderNode?.addContentOffset(offset: offset, transition: transition) strongSelf.chatDisplayNode.loadingPlaceholderNode?.addContentOffset(offset: offset, transition: transition)
} }
strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode, isRotated: strongSelf.chatDisplayNode.historyNode.rotated) strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode, isRotated: strongSelf.chatDisplayNode.historyNode.rotated)
} }
self.chatDisplayNode.historyNode.hasAtLeast3MessagesUpdated = { [weak self] hasAtLeast3Messages in historyNode.hasAtLeast3MessagesUpdated = { [weak self] hasAtLeast3Messages in
if let strongSelf = self { if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(interactive: false, { $0.updatedHasAtLeast3Messages(hasAtLeast3Messages) }) strongSelf.updateChatPresentationInterfaceState(interactive: false, { $0.updatedHasAtLeast3Messages(hasAtLeast3Messages) })
} }
} }
self.chatDisplayNode.historyNode.hasPlentyOfMessagesUpdated = { [weak self] hasPlentyOfMessages in historyNode.hasPlentyOfMessagesUpdated = { [weak self] hasPlentyOfMessages in
if let strongSelf = self { if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(interactive: false, { $0.updatedHasPlentyOfMessages(hasPlentyOfMessages) }) strongSelf.updateChatPresentationInterfaceState(interactive: false, { $0.updatedHasPlentyOfMessages(hasPlentyOfMessages) })
} }
} }
if self.didAppear { if self.didAppear {
self.chatDisplayNode.historyNode.canReadHistory.set(self.computedCanReadHistoryPromise.get()) historyNode.canReadHistory.set(self.computedCanReadHistoryPromise.get())
} }
} }
} }

View File

@ -317,7 +317,7 @@ extension ChatControllerImpl {
}) })
} else if forceInCurrentChat { } else if forceInCurrentChat {
if let _ = fromId, let fromIndex = fromIndex, rememberInStack { if let _ = fromId, let fromIndex = fromIndex, rememberInStack {
self.historyNavigationStack.add(fromIndex) self.contentData?.historyNavigationStack.add(fromIndex)
} }
let scrollFromIndex: MessageIndex? let scrollFromIndex: MessageIndex?
@ -505,7 +505,7 @@ extension ChatControllerImpl {
return return
} }
if let _ = fromId, rememberInStack { if let _ = fromId, rememberInStack {
self.historyNavigationStack.add(fromIndex) self.contentData?.historyNavigationStack.add(fromIndex)
} }
self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue())) self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue()))

View File

@ -23,8 +23,6 @@ func updateChatPresentationInterfaceStateImpl(
_ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState,
completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void
) { ) {
let previousChatLocation = selfController.chatDisplayNode.historyNode.chatLocation
var completion = externalCompletion var completion = externalCompletion
var temporaryChatPresentationInterfaceState = f(selfController.presentationInterfaceState) var temporaryChatPresentationInterfaceState = f(selfController.presentationInterfaceState)
@ -436,14 +434,6 @@ func updateChatPresentationInterfaceStateImpl(
selfController.presentationInterfaceState = updatedChatPresentationInterfaceState selfController.presentationInterfaceState = updatedChatPresentationInterfaceState
if selfController.chatDisplayNode.chatLocation != selfController.presentationInterfaceState.chatLocation {
let defaultDirection: ChatControllerAnimateInnerChatSwitchDirection? = selfController.chatDisplayNode.chatLocationTabSwitchDirection(from: selfController.chatLocation.threadId, to: selfController.presentationInterfaceState.chatLocation.threadId).flatMap { direction -> ChatControllerAnimateInnerChatSwitchDirection in
return direction ? .right : .left
}
let tabSwitchDirection = selfController.currentChatSwitchDirection ?? defaultDirection
selfController.chatDisplayNode.updateChatLocation(chatLocation: selfController.presentationInterfaceState.chatLocation, transition: transition, tabSwitchDirection: tabSwitchDirection)
}
selfController.updateSlowmodeStatus() selfController.updateSlowmodeStatus()
switch updatedChatPresentationInterfaceState.inputMode { switch updatedChatPresentationInterfaceState.inputMode {
@ -500,20 +490,11 @@ func updateChatPresentationInterfaceStateImpl(
} }
var buttonsAnimated = transition.isAnimated var buttonsAnimated = transition.isAnimated
if selfController.currentChatSwitchDirection != nil {
buttonsAnimated = false
}
if let button = rightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.rightNavigationButton, target: selfController, selector: #selector(selfController.rightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) { if let button = rightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.rightNavigationButton, target: selfController, selector: #selector(selfController.rightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) {
if selfController.rightNavigationButton != button { if selfController.rightNavigationButton != button {
if let currentButton = selfController.rightNavigationButton?.action, currentButton == button.action { if let currentButton = selfController.rightNavigationButton?.action, currentButton == button.action {
buttonsAnimated = false buttonsAnimated = false
} }
if case .replyThread = selfController.chatLocation {
buttonsAnimated = false
}
if let channel = updatedChatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
buttonsAnimated = false
}
selfController.rightNavigationButton = button selfController.rightNavigationButton = button
} }
} else if let _ = selfController.rightNavigationButton { } else if let _ = selfController.rightNavigationButton {
@ -603,12 +584,6 @@ func updateChatPresentationInterfaceStateImpl(
} }
} }
if previousChatLocation != selfController.presentationInterfaceState.chatLocation {
selfController.chatLocation = selfController.presentationInterfaceState.chatLocation
selfController.reloadCachedData()
selfController.setupChatHistoryNode()
}
selfController.updateDownButtonVisibility() selfController.updateDownButtonVisibility()
if selfController.presentationInterfaceState.hasBirthdayToday { if selfController.presentationInterfaceState.hasBirthdayToday {

View File

@ -389,8 +389,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var searchDisposable: MetaDisposable? var searchDisposable: MetaDisposable?
var historyNavigationStack = ChatHistoryNavigationStack()
public let canReadHistory = ValuePromise<Bool>(true, ignoreRepeated: true) public let canReadHistory = ValuePromise<Bool>(true, ignoreRepeated: true)
public let hasBrowserOrAppInFront = Promise<Bool>(false) public let hasBrowserOrAppInFront = Promise<Bool>(false)
@ -5230,7 +5228,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}) })
} }
self.reloadChatLocation(chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, historyNode: self.chatDisplayNode.historyNode, isReady: nil) self.reloadChatLocation(chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, historyNode: self.chatDisplayNode.historyNode, apply: { $0(false) })
self.botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get() self.botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get()
|> deliverOnMainQueue).startStrict(next: { [weak self] message in |> deliverOnMainQueue).startStrict(next: { [weak self] message in
@ -9711,10 +9709,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var currentChatSwitchDirection: ChatControllerAnimateInnerChatSwitchDirection? var currentChatSwitchDirection: ChatControllerAnimateInnerChatSwitchDirection?
public func updateChatLocationThread(threadId: Int64?, animationDirection: ChatControllerAnimateInnerChatSwitchDirection? = nil) { public func updateChatLocationThread(threadId: Int64?, animationDirection: ChatControllerAnimateInnerChatSwitchDirection? = nil) {
/*if self.isUpdatingChatLocationThread { if self.isUpdatingChatLocationThread {
return return
} }
guard let peerId = self.chatLocation.peerId else { guard let peerId = self.chatLocation.peerId else {
return return
} }
@ -9725,16 +9722,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let navigationSnapshot = self.chatTitleView?.prepareSnapshotState() self.saveInterfaceState()
//let historyNode = self.chatDisplayNode.createHistoryNodeForChatLocation(chatLocation: self.chatLocation, chatLocationContextHolder: )
let rightBarButtonItemSnapshots: [(UIView, CGRect)] = (self.navigationItem.rightBarButtonItems ?? []).compactMap { item -> (UIView, CGRect)? in
guard let view = item.customDisplayNode?.view, let snapshotView = view.snapshotView(afterScreenUpdates: false) else {
return nil
}
return (snapshotView, view.convert(view.bounds, to: self.view))
}
let updatedChatLocation: ChatLocation let updatedChatLocation: ChatLocation
if let threadId { if let threadId {
@ -9762,25 +9750,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
updatedChatLocation = .peer(id: peerId) updatedChatLocation = .peer(id: peerId)
} }
let isReady = Promise<Bool>() let navigationSnapshot = self.chatTitleView?.prepareSnapshotState()
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil) let avatarSnapshot = self.chatInfoNavigationButton?.buttonItem.customDisplayNode?.view.window != nil ? (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.prepareSnapshotState() : nil
self.reloadChatLocation(chatLocation: updatedChatLocation, chatLocationContextHolder: chatLocationContextHolder, historyNode: historyNode, isReady: isReady)
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
let historyNode = self.chatDisplayNode.createHistoryNodeForChatLocation(chatLocation: updatedChatLocation, chatLocationContextHolder: chatLocationContextHolder)
self.isUpdatingChatLocationThread = true self.isUpdatingChatLocationThread = true
self.updateChatLocationThreadDisposable?.dispose() self.reloadChatLocation(chatLocation: updatedChatLocation, chatLocationContextHolder: chatLocationContextHolder, historyNode: historyNode, apply: { [weak self, weak historyNode] apply in
self.updateChatLocationThreadDisposable = (isReady.get() guard let self, let historyNode else {
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
guard let self else {
return return
} }
self.isUpdatingChatLocationThread = false
self.currentChatSwitchDirection = animationDirection self.currentChatSwitchDirection = animationDirection
self.updateChatPresentationInterfaceState(animated: animationDirection != nil, interactive: false, { presentationInterfaceState in self.chatLocation = updatedChatLocation
return presentationInterfaceState.updatedChatLocation(updatedChatLocation) self.chatDisplayNode.prepareSwitchToChatLocation(historyNode: historyNode, animationDirection: animationDirection)
})
apply(true)
if let navigationSnapshot, let animationDirection { if let navigationSnapshot, let animationDirection {
let mappedAnimationDirection: ChatTitleView.AnimateFromSnapshotDirection let mappedAnimationDirection: ChatTitleView.AnimateFromSnapshotDirection
@ -9796,33 +9781,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
self.chatTitleView?.animateFromSnapshot(navigationSnapshot, direction: mappedAnimationDirection) self.chatTitleView?.animateFromSnapshot(navigationSnapshot, direction: mappedAnimationDirection)
if let rightBarButtonItems = self.navigationItem.rightBarButtonItems { }
for i in 0 ..< rightBarButtonItems.count {
let item = rightBarButtonItems[i] if let avatarSnapshot, self.chatInfoNavigationButton?.buttonItem.customDisplayNode?.view.window != nil {
if let customDisplayNode = item.customDisplayNode { (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.animateFromSnapshot(avatarSnapshot)
customDisplayNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
//customDisplayNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
let _ = rightBarButtonItemSnapshots
/*if i < rightBarButtonItemSnapshots.count {
let (snapshotItem, snapshotFrame) = rightBarButtonItemSnapshots[i]
if let targetSuperview = customDisplayNode.view.superview {
snapshotItem.frame = targetSuperview.convert(snapshotFrame, from: self.view)
targetSuperview.addSubview(snapshotItem)
snapshotItem.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, completion: { [weak snapshotItem] _ in
snapshotItem?.removeFromSuperview()
})
snapshotItem.layer.animateScale(from: 1.0, to: 0.1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
}*/
}
}
}
} }
self.currentChatSwitchDirection = nil self.currentChatSwitchDirection = nil
})*/ self.isUpdatingChatLocationThread = false
})
} }
public var contentContainerNode: ASDisplayNode { public var contentContainerNode: ASDisplayNode {

View File

@ -57,6 +57,30 @@ extension ChatControllerImpl {
case dismiss case dismiss
} }
struct NextChannelToRead: Equatable {
struct ThreadData: Equatable {
let id: Int64
let data: MessageHistoryThreadData
init(id: Int64, data: MessageHistoryThreadData) {
self.id = id
self.data = data
}
}
let peer: EnginePeer
let threadData: ThreadData?
let unreadCount: Int
let location: TelegramEngine.NextUnreadChannelLocation
init(peer: EnginePeer, threadData: ThreadData?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) {
self.peer = peer
self.threadData = threadData
self.unreadCount = unreadCount
self.location = location
}
}
struct State { struct State {
var peerView: PeerView? var peerView: PeerView?
var threadInfo: EngineMessageHistoryThread.Info? var threadInfo: EngineMessageHistoryThread.Info?
@ -72,7 +96,7 @@ extension ChatControllerImpl {
var contactStatus: ChatContactStatus? var contactStatus: ChatContactStatus?
var adMessage: Message? var adMessage: Message?
var offerNextChannelToRead: Bool = false var offerNextChannelToRead: Bool = false
var nextChannelToRead: (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation)? var nextChannelToRead: NextChannelToRead?
var nextChannelToReadDisplayName: Bool = false var nextChannelToReadDisplayName: Bool = false
var isNotAccessible: Bool = false var isNotAccessible: Bool = false
var hasBots: Bool = false var hasBots: Bool = false
@ -144,6 +168,7 @@ extension ChatControllerImpl {
private let isChatLocationInfoReady = ValuePromise<Bool>(false, ignoreRepeated: true) private let isChatLocationInfoReady = ValuePromise<Bool>(false, ignoreRepeated: true)
private let isCachedDataReady = ValuePromise<Bool>(false, ignoreRepeated: true) private let isCachedDataReady = ValuePromise<Bool>(false, ignoreRepeated: true)
let chatLocation: ChatLocation
let chatLocationInfoData: ChatLocationInfoData let chatLocationInfoData: ChatLocationInfoData
private(set) var state: State = State() private(set) var state: State = State()
@ -172,11 +197,13 @@ extension ChatControllerImpl {
} }
} }
var historyNavigationStack = ChatHistoryNavigationStack()
let chatThemeEmoticonPromise = Promise<String?>() let chatThemeEmoticonPromise = Promise<String?>()
let chatWallpaperPromise = Promise<TelegramWallpaper?>() let chatWallpaperPromise = Promise<TelegramWallpaper?>()
var inviteRequestsContext: PeerInvitationImportersContext? private(set) var inviteRequestsContext: PeerInvitationImportersContext?
private var inviteRequestsDisposable = MetaDisposable() private var inviteRequestsDisposable: Disposable?
init( init(
context: AccountContext, context: AccountContext,
@ -189,9 +216,14 @@ extension ChatControllerImpl {
currentChatListFilter: Int32?, currentChatListFilter: Int32?,
customChatNavigationStack: [EnginePeer.Id]?, customChatNavigationStack: [EnginePeer.Id]?,
presentationData: PresentationData, presentationData: PresentationData,
historyNode: ChatHistoryListNodeImpl historyNode: ChatHistoryListNodeImpl,
inviteRequestsContext: PeerInvitationImportersContext?
) { ) {
self.chatLocation = chatLocation
self.presentationData = presentationData self.presentationData = presentationData
self.inviteRequestsContext = inviteRequestsContext
let strings = self.presentationData.strings let strings = self.presentationData.strings
let chatLocationPeerId: PeerId? = chatLocation.peerId let chatLocationPeerId: PeerId? = chatLocation.peerId
@ -968,11 +1000,23 @@ extension ChatControllerImpl {
let previousState = strongSelf.state let previousState = strongSelf.state
strongSelf.state.offerNextChannelToRead = true var isUpdated = false
strongSelf.state.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in
return (peer: nextPeer, threadData: nil, unreadCount: 0, location: .same) if !strongSelf.state.offerNextChannelToRead {
strongSelf.state.offerNextChannelToRead = true
isUpdated = true
}
let nextChannelToRead = nextPeer.flatMap { nextPeer -> NextChannelToRead in
return NextChannelToRead(peer: nextPeer, threadData: nil, unreadCount: 0, location: .same)
}
if strongSelf.state.nextChannelToRead != nextChannelToRead {
strongSelf.state.nextChannelToRead = nextChannelToRead
isUpdated = true
}
if strongSelf.state.nextChannelToReadDisplayName != (nextChatSuggestionTip >= 3) {
strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
isUpdated = true
} }
strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
let nextPeerId = nextPeer?.id let nextPeerId = nextPeer?.id
@ -988,7 +1032,9 @@ extension ChatControllerImpl {
} }
} }
strongSelf.onUpdated?(previousState) if isUpdated {
strongSelf.onUpdated?(previousState)
}
}) })
} }
} else if isRegularChat, strongSelf.nextChannelToReadDisposable == nil { } else if isRegularChat, strongSelf.nextChannelToReadDisposable == nil {
@ -1008,11 +1054,23 @@ extension ChatControllerImpl {
let previousState = strongSelf.state let previousState = strongSelf.state
strongSelf.state.offerNextChannelToRead = true var isUpdated = false
strongSelf.state.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in
return (peer: nextPeer.peer, threadData: nil, unreadCount: nextPeer.unreadCount, location: nextPeer.location) if !strongSelf.state.offerNextChannelToRead {
strongSelf.state.offerNextChannelToRead = true
isUpdated = true
}
let nextChannelToRead = nextPeer.flatMap { nextPeer -> NextChannelToRead in
return NextChannelToRead(peer: nextPeer.peer, threadData: nil, unreadCount: nextPeer.unreadCount, location: nextPeer.location)
}
if strongSelf.state.nextChannelToRead != nextChannelToRead {
strongSelf.state.nextChannelToRead = nextChannelToRead
isUpdated = true
}
if strongSelf.state.nextChannelToReadDisplayName != (nextChatSuggestionTip >= 3) {
strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
isUpdated = true
} }
strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
let nextPeerId = nextPeer?.peer.id let nextPeerId = nextPeer?.peer.id
@ -1028,7 +1086,9 @@ extension ChatControllerImpl {
} }
} }
strongSelf.onUpdated?(previousState) if isUpdated {
strongSelf.onUpdated?(previousState)
}
}) })
} }
} }
@ -1566,13 +1626,27 @@ extension ChatControllerImpl {
let previousState = strongSelf.state let previousState = strongSelf.state
strongSelf.state.offerNextChannelToRead = true var isUpdated = false
strongSelf.state.nextChannelToRead = nextThreadData.flatMap { nextThreadData -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in
return (peer: EnginePeer(channel), threadData: nextThreadData, unreadCount: Int(nextThreadData.data.incomingUnreadCount), location: .same)
}
strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
strongSelf.onUpdated?(previousState) if !strongSelf.state.offerNextChannelToRead {
strongSelf.state.offerNextChannelToRead = true
isUpdated = true
}
let nextChannelToRead = nextThreadData.flatMap { nextThreadData -> NextChannelToRead in
return NextChannelToRead(peer: EnginePeer(channel), threadData: NextChannelToRead.ThreadData(id: nextThreadData.id, data: nextThreadData.data), unreadCount: Int(nextThreadData.data.incomingUnreadCount), location: .same)
}
if strongSelf.state.nextChannelToRead != nextChannelToRead {
strongSelf.state.nextChannelToRead = nextChannelToRead
isUpdated = true
}
if strongSelf.state.nextChannelToReadDisplayName != (nextChatSuggestionTip >= 3) {
strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
isUpdated = true
}
if isUpdated {
strongSelf.onUpdated?(previousState)
}
}) })
} }
} }
@ -2125,19 +2199,6 @@ extension ChatControllerImpl {
if strongSelf.inviteRequestsContext == nil { if strongSelf.inviteRequestsContext == nil {
let inviteRequestsContext = context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil)) let inviteRequestsContext = context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil))
strongSelf.inviteRequestsContext = inviteRequestsContext strongSelf.inviteRequestsContext = inviteRequestsContext
strongSelf.inviteRequestsDisposable.set((combineLatest(queue: Queue.mainQueue(), inviteRequestsContext.state, ApplicationSpecificNotice.dismissedInvitationRequests(accountManager: context.sharedContext.accountManager, peerId: peerId))).startStrict(next: { [weak strongSelf] requestsState, dismissedInvitationRequests in
guard let strongSelf else {
return
}
let previousState = strongSelf.state
strongSelf.state.requestsState = requestsState
strongSelf.state.dismissedInvitationRequests = dismissedInvitationRequests
strongSelf.onUpdated?(previousState)
}))
} else if let inviteRequestsContext = strongSelf.inviteRequestsContext { } else if let inviteRequestsContext = strongSelf.inviteRequestsContext {
let _ = (inviteRequestsContext.state let _ = (inviteRequestsContext.state
|> take(1) |> take(1)
@ -2147,6 +2208,30 @@ extension ChatControllerImpl {
} }
}) })
} }
if chatLocation.threadId == nil {
if strongSelf.inviteRequestsDisposable == nil, let inviteRequestsContext = strongSelf.inviteRequestsContext {
strongSelf.inviteRequestsDisposable = combineLatest(queue: Queue.mainQueue(), inviteRequestsContext.state, ApplicationSpecificNotice.dismissedInvitationRequests(accountManager: context.sharedContext.accountManager, peerId: peerId)).startStrict(next: { [weak strongSelf] requestsState, dismissedInvitationRequests in
guard let strongSelf else {
return
}
let previousState = strongSelf.state
strongSelf.state.requestsState = requestsState
strongSelf.state.dismissedInvitationRequests = dismissedInvitationRequests
strongSelf.onUpdated?(previousState)
})
}
} else {
strongSelf.state.requestsState = nil
strongSelf.state.dismissedInvitationRequests = []
}
} else {
strongSelf.inviteRequestsContext = nil
strongSelf.state.requestsState = nil
strongSelf.state.dismissedInvitationRequests = []
} }
var isUpdated = false var isUpdated = false
@ -2192,7 +2277,7 @@ extension ChatControllerImpl {
self.cachedDataDisposable?.dispose() self.cachedDataDisposable?.dispose()
self.premiumGiftSuggestionDisposable?.dispose() self.premiumGiftSuggestionDisposable?.dispose()
self.translationStateDisposable?.dispose() self.translationStateDisposable?.dispose()
self.inviteRequestsDisposable.dispose() self.inviteRequestsDisposable?.dispose()
} }
} }
} }

View File

@ -133,6 +133,19 @@ class HistoryNodeContainer: ASDisplayNode {
} }
} }
private final class PendingSwitchToChatLocation {
let historyNode: ChatHistoryListNodeImpl
let animationDirection: ChatControllerAnimateInnerChatSwitchDirection?
init(
historyNode: ChatHistoryListNodeImpl,
animationDirection: ChatControllerAnimateInnerChatSwitchDirection?
) {
self.historyNode = historyNode
self.animationDirection = animationDirection
}
}
class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
let context: AccountContext let context: AccountContext
private(set) var chatLocation: ChatLocation private(set) var chatLocation: ChatLocation
@ -162,7 +175,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
var historyNode: ChatHistoryListNodeImpl var historyNode: ChatHistoryListNodeImpl
var blurredHistoryNode: ASImageNode? var blurredHistoryNode: ASImageNode?
let historyNodeContainer: HistoryNodeContainer let historyNodeContainer: HistoryNodeContainer
let loadingNode: ChatLoadingNode private(set) var loadingNode: ChatLoadingNode
private var isLoadingValue: Bool = false
private var isLoadingEarlier: Bool = false
private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode? private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode?
var alwaysShowSearchResultsAsList: Bool = false var alwaysShowSearchResultsAsList: Bool = false
@ -316,8 +332,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
let adMessagesContext: AdMessagesHistoryContext? let adMessagesContext: AdMessagesHistoryContext?
private var isLoadingValue: Bool = false private var pendingSwitchToChatLocation: PendingSwitchToChatLocation?
private var isLoadingEarlier: Bool = false
private func updateIsLoading(isLoading: Bool, earlier: Bool, animated: Bool) { private func updateIsLoading(isLoading: Bool, earlier: Bool, animated: Bool) {
var useLoadingPlaceholder = self.chatLocation.peerId?.namespace != Namespaces.Peer.CloudUser && self.chatLocation.peerId?.namespace != Namespaces.Peer.SecretChat var useLoadingPlaceholder = self.chatLocation.peerId?.namespace != Namespaces.Peer.CloudUser && self.chatLocation.peerId?.namespace != Namespaces.Peer.SecretChat
if case let .replyThread(message) = self.chatLocation, message.peerId == self.context.account.peerId { if case let .replyThread(message) = self.chatLocation, message.peerId == self.context.account.peerId {
@ -351,10 +367,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
loadingPlaceholderNode.setup(self.historyNode, updating: false) loadingPlaceholderNode.setup(self.historyNode, updating: false)
if let (layout, navigationHeight) = self.validLayout { let contentBounds = self.loadingNode.frame
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate, listViewTransaction: { _, _, _, _ in loadingPlaceholderNode.frame = contentBounds
}, updateExtraNavigationBarBackgroundHeight: { _, _, _, _ in if let loadingPlaceholderNode = self.loadingPlaceholderNode, let validLayout = self.validLayout {
}) loadingPlaceholderNode.updateLayout(size: contentBounds.size, insets: self.visibleAreaInset, metrics: validLayout.0.metrics, transition: .immediate)
loadingPlaceholderNode.update(rect: contentBounds, within: contentBounds.size, transition: .immediate)
} }
} }
loadingPlaceholderNode.alpha = 1.0 loadingPlaceholderNode.alpha = 1.0
@ -371,12 +388,17 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
} else { } else {
if useLoadingPlaceholder { if useLoadingPlaceholder {
if let loadingPlaceholderNode = self.loadingPlaceholderNode { if let loadingPlaceholderNode = self.loadingPlaceholderNode {
loadingPlaceholderNode.animateOut(self.historyNode, completion: { [weak self] in if animated {
if let strongSelf = self { loadingPlaceholderNode.animateOut(self.historyNode, completion: { [weak self] in
strongSelf.loadingPlaceholderNode?.removeFromSupernode() if let strongSelf = self {
strongSelf.loadingPlaceholderNode = nil strongSelf.loadingPlaceholderNode?.removeFromSupernode()
} strongSelf.loadingPlaceholderNode = nil
}) }
})
} else {
self.loadingPlaceholderNode = nil
loadingPlaceholderNode.removeFromSupernode()
}
} }
} else { } else {
self.loadingNode.alpha = 0.0 self.loadingNode.alpha = 0.0
@ -1295,8 +1317,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
} }
self.containerLayoutAndNavigationBarHeight = (layout, navigationBarHeight) self.containerLayoutAndNavigationBarHeight = (layout, navigationBarHeight)
var extraTransition = transition
var dismissedTitleTopicsAccessoryPanelNode: ChatTopicListTitleAccessoryPanelNode? var dismissedTitleTopicsAccessoryPanelNode: ChatTopicListTitleAccessoryPanelNode?
var immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance = false var immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance = false
var titleTopicsAccessoryPanelHeight: CGFloat? var titleTopicsAccessoryPanelHeight: CGFloat?
@ -1357,9 +1377,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.titleAccessoryPanelContainer.addSubnode(titleAccessoryPanelNode) self.titleAccessoryPanelContainer.addSubnode(titleAccessoryPanelNode)
titleAccessoryPanelNode.clipsToBounds = true titleAccessoryPanelNode.clipsToBounds = true
if transition.isAnimated {
extraTransition = .animated(duration: 0.2, curve: .easeInOut)
}
} }
let layoutResult = titleAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState) let layoutResult = titleAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
@ -1367,9 +1384,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
titleAccessoryPanelBackgroundHeight = layoutResult.backgroundHeight titleAccessoryPanelBackgroundHeight = layoutResult.backgroundHeight
titleAccessoryPanelHitTestSlop = layoutResult.hitTestSlop titleAccessoryPanelHitTestSlop = layoutResult.hitTestSlop
if immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance { if immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance {
titleAccessoryPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) if transition.isAnimated {
titleAccessoryPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
titleAccessoryPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -layoutResult.backgroundHeight, 0.0) titleAccessoryPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -layoutResult.backgroundHeight, 0.0)
extraTransition.updateSublayerTransformOffset(layer: titleAccessoryPanelNode.layer, offset: CGPoint()) transition.updateSublayerTransformOffset(layer: titleAccessoryPanelNode.layer, offset: CGPoint())
} }
} else if let titleAccessoryPanelNode = self.titleAccessoryPanelNode { } else if let titleAccessoryPanelNode = self.titleAccessoryPanelNode {
dismissedTitleAccessoryPanelNode = titleAccessoryPanelNode dismissedTitleAccessoryPanelNode = titleAccessoryPanelNode
@ -1412,9 +1431,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.titleAccessoryPanelContainer.addSubnode(translationPanelNode) self.titleAccessoryPanelContainer.addSubnode(translationPanelNode)
translationPanelNode.clipsToBounds = true translationPanelNode.clipsToBounds = true
if transition.isAnimated {
extraTransition = .animated(duration: 0.2, curve: .easeInOut)
}
} }
let height = translationPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState) let height = translationPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
@ -1422,7 +1438,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance { if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance {
translationPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) translationPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
translationPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -height, 0.0) translationPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -height, 0.0)
extraTransition.updateSublayerTransformOffset(layer: translationPanelNode.layer, offset: CGPoint()) transition.updateSublayerTransformOffset(layer: translationPanelNode.layer, offset: CGPoint())
} }
} else if let chatTranslationPanel = self.chatTranslationPanel { } else if let chatTranslationPanel = self.chatTranslationPanel {
dismissedTranslationPanelNode = chatTranslationPanel dismissedTranslationPanelNode = chatTranslationPanel
@ -1803,18 +1819,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
wrappingInsets.top += statusBarHeight wrappingInsets.top += statusBarHeight
} }
} }
var isSelectionEnabled = true
if previewing {
isSelectionEnabled = false
} else if case .pinnedMessages = self.chatPresentationInterfaceState.subject {
isSelectionEnabled = false
} else if self.chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState != nil {
isSelectionEnabled = false
} else if case .customChatContents = self.chatLocation {
isSelectionEnabled = false
}
self.historyNode.isSelectionGestureEnabled = isSelectionEnabled
transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 200.0))) transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 200.0)))
self.titleAccessoryPanelContainer.hitTestExcludeInsets = UIEdgeInsets(top: 0.0, left: leftPanelSize?.width ?? 0.0, bottom: 0.0, right: 0.0) self.titleAccessoryPanelContainer.hitTestExcludeInsets = UIEdgeInsets(top: 0.0, left: leftPanelSize?.width ?? 0.0, bottom: 0.0, right: 0.0)
@ -1839,6 +1843,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
} }
var titleAccessoryPanelFrame: CGRect? var titleAccessoryPanelFrame: CGRect?
let titleAccessoryPanelBaseY = titlePanelsContentOffset
if let _ = self.titleAccessoryPanelNode, let panelHeight = titleAccessoryPanelHeight { if let _ = self.titleAccessoryPanelNode, let panelHeight = titleAccessoryPanelHeight {
titleAccessoryPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: titlePanelsContentOffset), size: CGSize(width: layout.size.width, height: panelHeight)) titleAccessoryPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: titlePanelsContentOffset), size: CGSize(width: layout.size.width, height: panelHeight))
insets.top += panelHeight insets.top += panelHeight
@ -1881,7 +1886,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
extraNavigationBarLeftCutout = CGSize(width: 0.0, height: navigationBarHeight) extraNavigationBarLeftCutout = CGSize(width: 0.0, height: navigationBarHeight)
} }
updateExtraNavigationBarBackgroundHeight(extraNavigationBarHeight, extraNavigationBarHitTestSlop, extraNavigationBarLeftCutout, extraTransition) updateExtraNavigationBarBackgroundHeight(extraNavigationBarHeight, extraNavigationBarHitTestSlop, extraNavigationBarLeftCutout, transition)
let contentBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height - wrappingInsets.top - wrappingInsets.bottom) let contentBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height - wrappingInsets.top - wrappingInsets.bottom)
@ -1908,13 +1913,154 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateBounds(node: self.historyNodeContainer, bounds: contentBounds) transition.updateBounds(node: self.historyNodeContainer, bounds: contentBounds)
transition.updatePosition(node: self.historyNodeContainer, position: contentBounds.center) transition.updatePosition(node: self.historyNodeContainer, position: contentBounds.center)
transition.updateBounds(node: self.historyNode, bounds: CGRect(origin: CGPoint(), size: contentBounds.size)) if let pendingSwitchToChatLocation = self.pendingSwitchToChatLocation {
transition.updatePosition(node: self.historyNode, position: CGPoint(x: contentBounds.size.width / 2.0, y: contentBounds.size.height / 2.0)) self.pendingSwitchToChatLocation = nil
let previousHistoryNode = self.historyNode
self.historyNode = pendingSwitchToChatLocation.historyNode
self.historyNode.position = previousHistoryNode.position
self.historyNode.bounds = previousHistoryNode.bounds
self.historyNode.transform = previousHistoryNode.transform
self.historyNode.messageTransitionNode = { [weak self] in
guard let self else {
return nil
}
return self.messageTransitionNode
}
transition.updateBounds(node: self.historyNode, bounds: CGRect(origin: CGPoint(), size: contentBounds.size))
transition.updatePosition(node: self.historyNode, position: CGPoint(x: contentBounds.size.width / 2.0, y: contentBounds.size.height / 2.0))
previousHistoryNode.supernode?.insertSubnode(self.historyNode, aboveSubnode: previousHistoryNode)
let messageTransitionNode = ChatMessageTransitionNodeImpl(listNode: self.historyNode, getContentAreaInScreenSpace: { [weak self] in
guard let self else {
return CGRect()
}
return self.view.convert(self.frameForVisibleArea(), to: nil)
}, onTransitionEvent: { [weak self] transition in
guard let self else {
return
}
if (self.context.sharedContext.currentPresentationData.with({ $0 })).reduceMotion {
return
}
if self.context.sharedContext.energyUsageSettings.fullTranslucency {
self.backgroundNode.animateEvent(transition: transition, extendAnimation: false)
}
})
let previousMessageTransitionNode = self.messageTransitionNode
self.messageTransitionNode = messageTransitionNode
messageTransitionNode.position = previousMessageTransitionNode.position
messageTransitionNode.bounds = previousMessageTransitionNode.bounds
messageTransitionNode.transform = previousMessageTransitionNode.transform
self.wrappingNode.contentNode.insertSubnode(self.messageTransitionNode, aboveSubnode: previousMessageTransitionNode)
self.emptyType = nil
self.isLoadingValue = false
self.isLoadingEarlier = false
let previousLoadingNode = self.loadingNode
self.loadingNode = ChatLoadingNode(context: self.context, theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, bubbleCorners: self.chatPresentationInterfaceState.bubbleCorners)
self.loadingNode.frame = previousLoadingNode.frame
self.loadingNode.isHidden = previousLoadingNode.isHidden
self.loadingNode.alpha = previousLoadingNode.alpha
previousLoadingNode.supernode?.insertSubnode(self.loadingNode, aboveSubnode: previousLoadingNode)
let previousLoadingPlaceholderNode = self.loadingPlaceholderNode
self.loadingPlaceholderNode = nil
let previousEmptyNode = self.emptyNode
self.emptyNode = nil
self.setupHistoryNode()
self.historyNode.loadStateUpdated?(self.historyNode.loadState ?? .messages, false)
if let animationDirection = pendingSwitchToChatLocation.animationDirection {
var offsetMultiplier = CGPoint()
switch animationDirection {
case .up:
offsetMultiplier.y = -1.0
case .down:
offsetMultiplier.y = 1.0
case .left:
offsetMultiplier.x = -1.0
case .right:
offsetMultiplier.x = 1.0
}
previousHistoryNode.clipsToBounds = true
self.historyNode.clipsToBounds = true
transition.animatePosition(layer: self.historyNode.layer, from: CGPoint(x: offsetMultiplier.x * layout.size.width, y: offsetMultiplier.y * layout.size.height), to: CGPoint(), removeOnCompletion: true, additive: true, completion: { [weak self] _ in
guard let self else {
return
}
self.historyNode.clipsToBounds = false
})
transition.animatePosition(layer: previousHistoryNode.layer, from: CGPoint(), to: CGPoint(x: -offsetMultiplier.x * layout.size.width, y: -offsetMultiplier.y * layout.size.height), removeOnCompletion: false, additive: true, completion: { [weak previousHistoryNode] _ in
previousHistoryNode?.removeFromSupernode()
})
transition.animatePosition(layer: self.messageTransitionNode.layer, from: CGPoint(x: offsetMultiplier.x * layout.size.width, y: offsetMultiplier.y * layout.size.height), to: CGPoint(), removeOnCompletion: true, additive: true)
transition.animatePosition(layer: previousMessageTransitionNode.layer, from: CGPoint(), to: CGPoint(x: -offsetMultiplier.x * layout.size.width, y: -offsetMultiplier.y * layout.size.height), removeOnCompletion: false, additive: true, completion: { [weak previousMessageTransitionNode] _ in
previousMessageTransitionNode?.removeFromSupernode()
})
transition.animatePosition(layer: self.loadingNode.layer, from: CGPoint(x: offsetMultiplier.x * layout.size.width, y: offsetMultiplier.y * layout.size.height), to: CGPoint(), removeOnCompletion: true, additive: true)
transition.animatePosition(layer: previousLoadingNode.layer, from: CGPoint(), to: CGPoint(x: -offsetMultiplier.x * layout.size.width, y: -offsetMultiplier.y * layout.size.height), removeOnCompletion: false, additive: true, completion: { [weak previousLoadingNode] _ in
previousLoadingNode?.removeFromSupernode()
})
if let loadingPlaceholderNode = self.loadingPlaceholderNode {
transition.animatePosition(layer: loadingPlaceholderNode.layer, from: CGPoint(x: offsetMultiplier.x * layout.size.width, y: offsetMultiplier.y * layout.size.height), to: CGPoint(), removeOnCompletion: true, additive: true)
}
if let previousLoadingPlaceholderNode {
transition.animatePosition(layer: previousLoadingPlaceholderNode.layer, from: CGPoint(), to: CGPoint(x: -offsetMultiplier.x * layout.size.width, y: -offsetMultiplier.y * layout.size.height), removeOnCompletion: false, additive: true, completion: { [weak previousLoadingPlaceholderNode] _ in
previousLoadingPlaceholderNode?.removeFromSupernode()
})
}
if let emptyNode = self.emptyNode {
transition.animatePosition(layer: emptyNode.layer, from: CGPoint(x: offsetMultiplier.x * layout.size.width, y: offsetMultiplier.y * layout.size.height), to: CGPoint(), removeOnCompletion: true, additive: true)
}
if let previousEmptyNode {
transition.animatePosition(layer: previousEmptyNode.layer, from: CGPoint(), to: CGPoint(x: -offsetMultiplier.x * layout.size.width, y: -offsetMultiplier.y * layout.size.height), removeOnCompletion: false, additive: true, completion: { [weak previousEmptyNode] _ in
previousEmptyNode?.removeFromSupernode()
})
}
} else {
previousHistoryNode.removeFromSupernode()
previousMessageTransitionNode.removeFromSupernode()
previousLoadingNode.removeFromSupernode()
previousLoadingPlaceholderNode?.removeFromSupernode()
previousEmptyNode?.removeFromSupernode()
}
} else {
transition.updateBounds(node: self.historyNode, bounds: CGRect(origin: CGPoint(), size: contentBounds.size))
transition.updatePosition(node: self.historyNode, position: CGPoint(x: contentBounds.size.width / 2.0, y: contentBounds.size.height / 2.0))
}
if let blurredHistoryNode = self.blurredHistoryNode { if let blurredHistoryNode = self.blurredHistoryNode {
transition.updateFrame(node: blurredHistoryNode, frame: contentBounds) transition.updateFrame(node: blurredHistoryNode, frame: contentBounds)
} }
//transition.updateFrame(node: self.historyScrollingArea, frame: contentBounds) var isSelectionEnabled = true
if previewing {
isSelectionEnabled = false
} else if case .pinnedMessages = self.chatPresentationInterfaceState.subject {
isSelectionEnabled = false
} else if self.chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState != nil {
isSelectionEnabled = false
} else if case .customChatContents = self.chatLocation {
isSelectionEnabled = false
}
self.historyNode.isSelectionGestureEnabled = isSelectionEnabled
transition.updateFrame(node: self.loadingNode, frame: contentBounds) transition.updateFrame(node: self.loadingNode, frame: contentBounds)
if let loadingPlaceholderNode = self.loadingPlaceholderNode { if let loadingPlaceholderNode = self.loadingPlaceholderNode {
@ -2552,7 +2698,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
if let dismissedTitleAccessoryPanelNode { if let dismissedTitleAccessoryPanelNode {
var dismissedPanelFrame = dismissedTitleAccessoryPanelNode.frame var dismissedPanelFrame = dismissedTitleAccessoryPanelNode.frame
dismissedPanelFrame.origin.y = -dismissedPanelFrame.size.height transition.updateSublayerTransformOffset(layer: dismissedTitleAccessoryPanelNode.layer, offset: CGPoint(x: 0.0, y: -dismissedPanelFrame.height))
dismissedPanelFrame.origin.y = titleAccessoryPanelBaseY
dismissedTitleAccessoryPanelNode.clipsToBounds = true
dismissedPanelFrame.size.height = 0.0
if transition.isAnimated {
dismissedTitleAccessoryPanelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
transition.updateFrame(node: dismissedTitleAccessoryPanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedTitleAccessoryPanelNode] _ in transition.updateFrame(node: dismissedTitleAccessoryPanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedTitleAccessoryPanelNode] _ in
dismissedTitleAccessoryPanelNode?.removeFromSupernode() dismissedTitleAccessoryPanelNode?.removeFromSupernode()
}) })
@ -2847,7 +2999,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
displayInlineSearch = true displayInlineSearch = true
} }
} }
if let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { if self.chatLocation.threadId == nil, let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil {
if self.chatPresentationInterfaceState.search != nil { if self.chatPresentationInterfaceState.search != nil {
displayInlineSearch = true displayInlineSearch = true
} }
@ -2877,7 +3029,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
} else { } else {
mappedContents = .empty mappedContents = .empty
} }
} else if let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { } else if self.chatLocation.threadId == nil, let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil {
mappedContents = .monoforumChats(query: self.chatPresentationInterfaceState.search?.query ?? "") mappedContents = .monoforumChats(query: self.chatPresentationInterfaceState.search?.query ?? "")
} else if case .peer(self.context.account.peerId) = self.chatPresentationInterfaceState.chatLocation { } else if case .peer(self.context.account.peerId) = self.chatPresentationInterfaceState.chatLocation {
mappedContents = .tag(MemoryBuffer()) mappedContents = .tag(MemoryBuffer())
@ -5078,9 +5230,27 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
return nil return nil
} }
) )
historyNode.position = self.historyNode.position
historyNode.bounds = self.historyNode.bounds
historyNode.transform = self.historyNode.transform
if let currentListViewLayout = self.currentListViewLayout {
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: currentListViewLayout.size, insets: currentListViewLayout.insets, scrollIndicatorInsets: currentListViewLayout.scrollIndicatorInsets, duration: 0.0, curve: .Default(duration: nil), ensureTopInsetForOverlayHighlightedItems: nil, customAnimationTransition: nil)
historyNode.updateLayout(transition: .immediate, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: 0.0, scrollToTop: false, completion: {})
}
return historyNode return historyNode
} }
func prepareSwitchToChatLocation(historyNode: ChatHistoryListNodeImpl, animationDirection: ChatControllerAnimateInnerChatSwitchDirection?) {
self.chatLocation = historyNode.chatLocation
self.pendingSwitchToChatLocation = PendingSwitchToChatLocation(
historyNode: historyNode,
animationDirection: animationDirection
)
}
func updateChatLocation(chatLocation: ChatLocation, transition: ContainedViewLayoutTransition, tabSwitchDirection: ChatControllerAnimateInnerChatSwitchDirection?) { func updateChatLocation(chatLocation: ChatLocation, transition: ContainedViewLayoutTransition, tabSwitchDirection: ChatControllerAnimateInnerChatSwitchDirection?) {
if chatLocation == self.chatLocation { if chatLocation == self.chatLocation {
return return

View File

@ -61,8 +61,10 @@ extension ChatControllerImpl {
var canSendPolls = true var canSendPolls = true
if let peer = self.presentationInterfaceState.renderedPeer?.peer { if let peer = self.presentationInterfaceState.renderedPeer?.peer {
if let peer = peer as? TelegramUser, peer.botInfo == nil { if let peer = peer as? TelegramUser {
canSendPolls = false if peer.botInfo == nil && peer.id != self.context.account.peerId {
canSendPolls = false
}
} else if peer is TelegramSecretChat { } else if peer is TelegramSecretChat {
canSendPolls = false canSendPolls = false
} else if let channel = peer as? TelegramChannel { } else if let channel = peer as? TelegramChannel {

View File

@ -43,7 +43,7 @@ extension ChatControllerImpl {
strongSelf.alwaysShowSearchResultsAsList = false strongSelf.alwaysShowSearchResultsAsList = false
strongSelf.chatDisplayNode.alwaysShowSearchResultsAsList = false strongSelf.chatDisplayNode.alwaysShowSearchResultsAsList = false
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
return state.updatedDisplayHistoryFilterAsList(false) return state.updatedDisplayHistoryFilterAsList(false).updatedSearch(nil)
}) })
c.dismiss() c.dismiss()

View File

@ -667,7 +667,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
public var contentPositionChanged: (ListViewVisibleContentOffset) -> Void = { _ in } public var contentPositionChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
public private(set) var loadState: ChatHistoryNodeLoadState? public private(set) var loadState: ChatHistoryNodeLoadState?
private var loadStateUpdated: ((ChatHistoryNodeLoadState, Bool) -> Void)? public private(set) var loadStateUpdated: ((ChatHistoryNodeLoadState, Bool) -> Void)?
private var additionalLoadStateUpdated: [(ChatHistoryNodeLoadState, Bool) -> Void] = [] private var additionalLoadStateUpdated: [(ChatHistoryNodeLoadState, Bool) -> Void] = []
public private(set) var hasAtLeast3Messages: Bool = false public private(set) var hasAtLeast3Messages: Bool = false
@ -1830,11 +1830,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
let initialData: ChatHistoryCombinedInitialData? let initialData: ChatHistoryCombinedInitialData?
switch update.0 { switch update.0 {
case let .Loading(combinedInitialData, type): case let .Loading(combinedInitialData, type):
if case .Generic(.FillHole) = type {
applyHole()
return
}
initialData = combinedInitialData initialData = combinedInitialData
if resetScrolling, let previousViewValue = previousView.with({ $0 })?.0 { if resetScrolling, let previousViewValue = previousView.with({ $0 })?.0 {
@ -1891,13 +1886,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
Queue.mainQueue().async { Queue.mainQueue().async {
if let strongSelf = self { if let strongSelf = self {
if !strongSelf.didSetInitialData {
strongSelf.didSetInitialData = true
var combinedInitialData = combinedInitialData
combinedInitialData?.cachedData = nil
strongSelf._initialData.set(.single(combinedInitialData))
}
let cachedData = initialData?.cachedData let cachedData = initialData?.cachedData
let cachedDataMessages = initialData?.cachedDataMessages let cachedDataMessages = initialData?.cachedDataMessages
@ -1917,8 +1905,30 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
strongSelf.currentHistoryState = historyState strongSelf.currentHistoryState = historyState
strongSelf.historyState.set(historyState) strongSelf.historyState.set(historyState)
} }
if !strongSelf.didSetInitialData {
strongSelf.didSetInitialData = true
var combinedInitialData = combinedInitialData
combinedInitialData?.cachedData = nil
strongSelf._initialData.set(.single(combinedInitialData))
}
strongSelf._isReady.set(true)
if !strongSelf.didSetReady {
strongSelf.didSetReady = true
#if DEBUG
let deltaTime = (CFAbsoluteTimeGetCurrent() - strongSelf.initTimestamp) * 1000.0
print("Chat init to dequeue time: \(deltaTime) ms")
#endif
}
} }
} }
if case .Generic(.FillHole) = type {
applyHole()
return
}
return return
case let .HistoryView(view, type, scrollPosition, flashIndicators, originalScrollPosition, data, id): case let .HistoryView(view, type, scrollPosition, flashIndicators, originalScrollPosition, data, id):
if case .Generic(.FillHole) = type { if case .Generic(.FillHole) = type {

View File

@ -230,10 +230,6 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
} }
func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTopicListTitleAccessoryPanelNode? { func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTopicListTitleAccessoryPanelNode? {
//TODO:release
if "".isEmpty {
return nil
}
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, chatPresentationInterfaceState.search == nil { if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, chatPresentationInterfaceState.search == nil {
let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId { if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId {

View File

@ -108,12 +108,27 @@ private final class ChatInfoTitlePanelPeerNearbyInfoNode: ASDisplayNode {
} }
final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
private final class Params {
let width: CGFloat
let leftInset: CGFloat
let rightInset: CGFloat
let interfaceState: ChatPresentationInterfaceState
init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, interfaceState: ChatPresentationInterfaceState) {
self.width = width
self.leftInset = leftInset
self.rightInset = rightInset
self.interfaceState = interfaceState
}
}
private let context: AccountContext private let context: AccountContext
private let separatorNode: ASDisplayNode private let separatorNode: ASDisplayNode
private let closeButton: HighlightableButtonNode private let closeButton: HighlightableButtonNode
private var button: UIButton? private let button: HighlightableButtonNode
private let buttonTitle: ImmediateTextNode
private let avatarsContext: AnimatedAvatarSetContext private let avatarsContext: AnimatedAvatarSetContext
private var avatarsContent: AnimatedAvatarSetContext.Content? private var avatarsContent: AnimatedAvatarSetContext.Content?
@ -127,6 +142,8 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
private var peers: [EnginePeer] = [] private var peers: [EnginePeer] = []
private var count: Int32 = 0 private var count: Int32 = 0
private var params: Params?
init(context: AccountContext) { init(context: AccountContext) {
self.context = context self.context = context
@ -137,6 +154,10 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
self.closeButton.displaysAsynchronously = false self.closeButton.displaysAsynchronously = false
self.button = HighlightableButtonNode()
self.buttonTitle = ImmediateTextNode()
self.buttonTitle.anchorPoint = CGPoint()
self.avatarsContext = AnimatedAvatarSetContext() self.avatarsContext = AnimatedAvatarSetContext()
self.avatarsNode = AnimatedAvatarSetNode() self.avatarsNode = AnimatedAvatarSetNode()
@ -150,6 +171,12 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside]) self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside])
self.addSubnode(self.closeButton) self.addSubnode(self.closeButton)
self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.addSubnode(self.button)
self.buttonTitle.isUserInteractionEnabled = false
self.button.addSubnode(self.buttonTitle)
self.addSubnode(self.avatarsNode) self.addSubnode(self.avatarsNode)
self.addSubnode(self.activateAreaNode) self.addSubnode(self.activateAreaNode)
@ -161,12 +188,16 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
self.peers = peers self.peers = peers
self.count = count self.count = count
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.avatarsContent = self.avatarsContext.update(peers: peers, animated: false) self.avatarsContent = self.avatarsContext.update(peers: peers, animated: false)
self.button?.setTitle(presentationData.strings.Conversation_RequestsToJoin(count), for: [])
if let params = self.params {
let _ = self.updateLayout(width: params.width, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate, interfaceState: params.interfaceState)
}
} }
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult { override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
self.params = Params(width: width, leftInset: leftInset, rightInset: rightInset, interfaceState: interfaceState)
if interfaceState.theme !== self.theme { if interfaceState.theme !== self.theme {
self.theme = interfaceState.theme self.theme = interfaceState.theme
@ -181,21 +212,15 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: width - contentRightInset - closeButtonSize.width, y: floorToScreenPixels((panelHeight - closeButtonSize.height) / 2.0)), size: closeButtonSize)) transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: width - contentRightInset - closeButtonSize.width, y: floorToScreenPixels((panelHeight - closeButtonSize.height) / 2.0)), size: closeButtonSize))
if self.button == nil { self.buttonTitle.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_RequestsToJoin(self.count), font: Font.regular(16.0), textColor: interfaceState.theme.rootController.navigationBar.accentTextColor)
let view = UIButton()
view.titleLabel?.font = Font.regular(16.0)
view.setTitleColor(interfaceState.theme.rootController.navigationBar.accentTextColor, for: [])
view.setTitleColor(interfaceState.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(0.7), for: [.highlighted])
view.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside])
self.view.addSubview(view)
self.button = view
}
self.button?.setTitle(interfaceState.strings.Conversation_RequestsToJoin(self.count), for: []) transition.updateFrame(node: self.button, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: panelHeight)))
let maxInset = max(contentRightInset, leftInset) let titleSize = self.buttonTitle.updateLayout(CGSize(width: width - leftInset - 90.0 - contentRightInset, height: 100.0))
let buttonWidth = floor(width - maxInset * 2.0) var buttonTitleFrame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - titleSize.width) * 0.5), y: floor((panelHeight - titleSize.height) * 0.5)), size: titleSize)
self.button?.frame = CGRect(origin: CGPoint(x: maxInset, y: 0.0), size: CGSize(width: buttonWidth, height: panelHeight)) buttonTitleFrame.origin.x = max(buttonTitleFrame.minX, leftInset + 90.0)
transition.updatePosition(node: self.buttonTitle, position: buttonTitleFrame.origin)
self.buttonTitle.bounds = CGRect(origin: CGPoint(), size: buttonTitleFrame.size)
let initialPanelHeight = panelHeight let initialPanelHeight = panelHeight
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))

View File

@ -188,7 +188,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
if case .everything = search.domain { if case .everything = search.domain {
if let _ = interfaceState.renderedPeer?.peer as? TelegramGroup { if let _ = interfaceState.renderedPeer?.peer as? TelegramGroup {
canSearchMembers = true canSearchMembers = true
} else if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel, case .group = peer.info { } else if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel, case .group = peer.info, !peer.isMonoForum {
canSearchMembers = true canSearchMembers = true
} }
} else { } else {

View File

@ -191,7 +191,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
if case .everything = search.domain { if case .everything = search.domain {
if let _ = params.interfaceState.renderedPeer?.peer as? TelegramGroup { if let _ = params.interfaceState.renderedPeer?.peer as? TelegramGroup {
canSearchMembers = true canSearchMembers = true
} else if let peer = params.interfaceState.renderedPeer?.peer as? TelegramChannel, case .group = peer.info { } else if let peer = params.interfaceState.renderedPeer?.peer as? TelegramChannel, case .group = peer.info, !peer.isMonoForum {
canSearchMembers = true canSearchMembers = true
} }
} else { } else {

View File

@ -898,7 +898,7 @@ func openResolvedUrlImpl(
func subject(for path: String) -> MediaEditorScreenImpl.Subject? { func subject(for path: String) -> MediaEditorScreenImpl.Subject? {
if path.hasSuffix(".jpg") { if path.hasSuffix(".jpg") {
if let image = UIImage(contentsOfFile: path) { if let image = UIImage(contentsOfFile: path)?.fixedOrientation() {
return .image(image: image, dimensions: PixelDimensions(image.size), additionalImage: nil, additionalImagePosition: .topLeft, fromCamera: false) return .image(image: image, dimensions: PixelDimensions(image.size), additionalImage: nil, additionalImagePosition: .topLeft, fromCamera: false)
} }
} else { } else {

View File

@ -528,9 +528,22 @@ public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer
} }
fileprivate func proceed() { fileprivate func proceed() {
let requestPeerType = self.preparedMessage.peerTypes.requestPeerTypes let peerTypes = self.preparedMessage.peerTypes
var types: [ReplyMarkupButtonRequestPeerType] = []
if peerTypes.contains(.users) {
types.append(.user(.init(isBot: false, isPremium: nil)))
}
if peerTypes.contains(.bots) {
types.append(.user(.init(isBot: true, isPremium: nil)))
}
if peerTypes.contains(.channels) {
types.append(.channel(.init(isCreator: false, hasUsername: nil, userAdminRights: TelegramChatAdminRights(rights: [.canPostMessages]), botAdminRights: nil)))
}
if peerTypes.contains(.groups) {
types.append(.group(.init(isCreator: false, hasUsername: nil, isForum: nil, botParticipant: false, userAdminRights: nil, botAdminRights: nil)))
}
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: requestPeerType, hasContactSelector: false, multipleSelection: true, selectForumThreads: true, immediatelyActivateMultipleSelection: true)) let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: types, hasContactSelector: false, multipleSelection: true, selectForumThreads: true, immediatelyActivateMultipleSelection: true))
controller.multiplePeersSelected = { [weak self, weak controller] peers, _, _, _, _, _ in controller.multiplePeersSelected = { [weak self, weak controller] peers, _, _, _, _, _ in
guard let self else { guard let self else {