mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
d1e1fffed9
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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? {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterDate.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterDate.tgs
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterNumber.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterNumber.tgs
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterPrice.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterPrice.tgs
Normal file
Binary file not shown.
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()))
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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)))
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user