mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-08 17:53:38 +00:00
Merge commit '23e53f0b3f326de2db01c0e6bfd16304f4eea86a'
This commit is contained in:
commit
a4e3e33a1f
@ -171,7 +171,7 @@ public enum ResolvedUrl {
|
|||||||
case inaccessiblePeer
|
case inaccessiblePeer
|
||||||
case botStart(peerId: PeerId, payload: String)
|
case botStart(peerId: PeerId, payload: String)
|
||||||
case groupBotStart(peerId: PeerId, payload: String)
|
case groupBotStart(peerId: PeerId, payload: String)
|
||||||
case channelMessage(peerId: PeerId, messageId: MessageId)
|
case channelMessage(peerId: PeerId, messageId: MessageId, timecode: Double?)
|
||||||
case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId)
|
case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId)
|
||||||
case stickerPack(name: String)
|
case stickerPack(name: String)
|
||||||
case instantView(TelegramMediaWebpage, String?)
|
case instantView(TelegramMediaWebpage, String?)
|
||||||
|
|||||||
@ -339,7 +339,7 @@ public struct ChatTextInputStateText: PostboxCoding, Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum ChatControllerSubject: Equatable {
|
public enum ChatControllerSubject: Equatable {
|
||||||
case message(id: MessageId, highlight: Bool)
|
case message(id: MessageId, highlight: Bool, timecode: Double?)
|
||||||
case scheduledMessages
|
case scheduledMessages
|
||||||
case pinnedMessages(id: MessageId?)
|
case pinnedMessages(id: MessageId?)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -184,7 +184,7 @@ public final class AppLockContextImpl: AppLockContext {
|
|||||||
}
|
}
|
||||||
passcodeController.ensureInputFocused()
|
passcodeController.ensureInputFocused()
|
||||||
} else {
|
} else {
|
||||||
let passcodeController = PasscodeEntryController(applicationBindings: strongSelf.applicationBindings, accountManager: strongSelf.accountManager, appLockContext: strongSelf, presentationData: presentationData, presentationDataSignal: strongSelf.presentationDataSignal, challengeData: accessChallengeData.data, biometrics: biometrics, arguments: PasscodeEntryControllerPresentationArguments(animated: !becameActiveRecently, lockIconInitialFrame: { [weak self] in
|
let passcodeController = PasscodeEntryController(applicationBindings: strongSelf.applicationBindings, accountManager: strongSelf.accountManager, appLockContext: strongSelf, presentationData: presentationData, presentationDataSignal: strongSelf.presentationDataSignal, statusBarHost: window?.statusBarHost, challengeData: accessChallengeData.data, biometrics: biometrics, arguments: PasscodeEntryControllerPresentationArguments(animated: !becameActiveRecently, lockIconInitialFrame: { [weak self] in
|
||||||
if let lockViewFrame = lockIconInitialFrame() {
|
if let lockViewFrame = lockIconInitialFrame() {
|
||||||
return lockViewFrame
|
return lockViewFrame
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -692,7 +692,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass {
|
if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass {
|
||||||
scrollToEndIfExists = true
|
scrollToEndIfExists = true
|
||||||
}
|
}
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: .message(id: messageId, highlight: true), purposefulAction: {
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: .message(id: messageId, highlight: true, timecode: nil), purposefulAction: {
|
||||||
if deactivateOnAction {
|
if deactivateOnAction {
|
||||||
self?.deactivateSearch(animated: false)
|
self?.deactivateSearch(animated: false)
|
||||||
}
|
}
|
||||||
@ -862,7 +862,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
} else {
|
} else {
|
||||||
var subject: ChatControllerSubject?
|
var subject: ChatControllerSubject?
|
||||||
if case let .search(messageId) = source, let id = messageId {
|
if case let .search(messageId) = source, let id = messageId {
|
||||||
subject = .message(id: id, highlight: false)
|
subject = .message(id: id, highlight: false, timecode: nil)
|
||||||
}
|
}
|
||||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.id), subject: subject, botStart: nil, mode: .standard(previewing: true))
|
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.id), subject: subject, botStart: nil, mode: .standard(previewing: true))
|
||||||
chatController.canReadHistory.set(false)
|
chatController.canReadHistory.set(false)
|
||||||
|
|||||||
@ -965,7 +965,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
let resolvedMessage = .single(nil)
|
let resolvedMessage = .single(nil)
|
||||||
|> then(context.sharedContext.resolveUrl(context: context, peerId: nil, url: finalQuery, skipUrlAuth: true)
|
|> then(context.sharedContext.resolveUrl(context: context, peerId: nil, url: finalQuery, skipUrlAuth: true)
|
||||||
|> mapToSignal { resolvedUrl -> Signal<Message?, NoError> in
|
|> mapToSignal { resolvedUrl -> Signal<Message?, NoError> in
|
||||||
if case let .channelMessage(_, messageId) = resolvedUrl {
|
if case let .channelMessage(_, messageId, _) = resolvedUrl {
|
||||||
return context.engine.messages.downloadMessage(messageId: messageId)
|
return context.engine.messages.downloadMessage(messageId: messageId)
|
||||||
} else {
|
} else {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
@ -1518,11 +1518,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.recentListNode.beganInteractiveDragging = {
|
self.recentListNode.beganInteractiveDragging = { _ in
|
||||||
interaction.dismissInput()
|
interaction.dismissInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = {
|
self.listNode.beganInteractiveDragging = { _ in
|
||||||
interaction.dismissInput()
|
interaction.dismissInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1220,7 +1220,7 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
var startedScrollingAtUpperBound = false
|
var startedScrollingAtUpperBound = false
|
||||||
|
|
||||||
self.beganInteractiveDragging = { [weak self] in
|
self.beganInteractiveDragging = { [weak self] _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -414,7 +414,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -286,7 +286,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
|
|
||||||
public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
||||||
public final var visibleBottomContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
public final var visibleBottomContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
||||||
public final var beganInteractiveDragging: () -> Void = { }
|
public final var beganInteractiveDragging: (CGPoint) -> Void = { _ in }
|
||||||
public final var endedInteractiveDragging: () -> Void = { }
|
public final var endedInteractiveDragging: () -> Void = { }
|
||||||
public final var didEndScrolling: (() -> Void)?
|
public final var didEndScrolling: (() -> Void)?
|
||||||
|
|
||||||
@ -683,7 +683,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
}
|
}
|
||||||
self.scrolledToItem = nil
|
self.scrolledToItem = nil
|
||||||
|
|
||||||
self.beganInteractiveDragging()
|
self.beganInteractiveDragging(self.touchesPosition)
|
||||||
|
|
||||||
for itemNode in self.itemNodes {
|
for itemNode in self.itemNodes {
|
||||||
if !itemNode.isLayerBacked {
|
if !itemNode.isLayerBacked {
|
||||||
@ -4039,7 +4039,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
self.updateOverlayHighlight(transition: transition)
|
self.updateOverlayHighlight(transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func itemIndexAtPoint(_ point: CGPoint) -> Int? {
|
public func itemIndexAtPoint(_ point: CGPoint) -> Int? {
|
||||||
for itemNode in self.itemNodes {
|
for itemNode in self.itemNodes {
|
||||||
if itemNode.apparentContentFrame.contains(point) {
|
if itemNode.apparentContentFrame.contains(point) {
|
||||||
return itemNode.index
|
return itemNode.index
|
||||||
@ -4057,6 +4057,15 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func indexOf(itemNode: ListViewItemNode) -> Int? {
|
||||||
|
for listItemNode in self.itemNodes {
|
||||||
|
if itemNode === listItemNode {
|
||||||
|
return listItemNode.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
public func forEachItemNode(_ f: (ASDisplayNode) -> Void) {
|
public func forEachItemNode(_ f: (ASDisplayNode) -> Void) {
|
||||||
for itemNode in self.itemNodes {
|
for itemNode in self.itemNodes {
|
||||||
if itemNode.index != nil {
|
if itemNode.index != nil {
|
||||||
|
|||||||
@ -237,7 +237,7 @@ public class Window1 {
|
|||||||
|
|
||||||
private var deviceMetrics: DeviceMetrics
|
private var deviceMetrics: DeviceMetrics
|
||||||
|
|
||||||
private let statusBarHost: StatusBarHost?
|
public let statusBarHost: StatusBarHost?
|
||||||
private let keyboardManager: KeyboardManager?
|
private let keyboardManager: KeyboardManager?
|
||||||
private let keyboardViewManager: KeyboardViewManager?
|
private let keyboardViewManager: KeyboardViewManager?
|
||||||
private var statusBarChangeObserver: AnyObject?
|
private var statusBarChangeObserver: AnyObject?
|
||||||
|
|||||||
@ -639,7 +639,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.mediaPlaybackStateDisposable.set(throttledSignal.start(next: { status in
|
self.mediaPlaybackStateDisposable.set(throttledSignal.start(next: { status in
|
||||||
if let status = status, status.duration >= 60.0 * 20.0 {
|
if let status = status, status.duration >= 60.0 * 10.0 {
|
||||||
var timestamp: Double?
|
var timestamp: Double?
|
||||||
if status.timestamp > 5.0 && status.timestamp < status.duration - 5.0 {
|
if status.timestamp > 5.0 && status.timestamp < status.duration - 5.0 {
|
||||||
timestamp = status.timestamp
|
timestamp = status.timestamp
|
||||||
|
|||||||
@ -219,16 +219,6 @@ public final class GradientBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var validLayout: CGSize?
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
private struct PhaseTransitionKey: Hashable {
|
|
||||||
var width: Int
|
|
||||||
var height: Int
|
|
||||||
var fromPhase: Int
|
|
||||||
var toPhase: Int
|
|
||||||
var numberOfFrames: Int
|
|
||||||
var curve: ContainedViewLayoutTransitionCurve
|
|
||||||
}
|
|
||||||
|
|
||||||
private let cloneNodes = SparseBag<Weak<CloneNode>>()
|
private let cloneNodes = SparseBag<Weak<CloneNode>>()
|
||||||
|
|
||||||
private let useSharedAnimationPhase: Bool
|
private let useSharedAnimationPhase: Bool
|
||||||
@ -259,7 +249,7 @@ public final class GradientBackgroundNode: ASDisplayNode {
|
|||||||
deinit {
|
deinit {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, extendAnimation: Bool = false) {
|
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, extendAnimation: Bool = false, backwards: Bool = false) {
|
||||||
let sizeUpdated = self.validLayout != size
|
let sizeUpdated = self.validLayout != size
|
||||||
self.validLayout = size
|
self.validLayout = size
|
||||||
|
|
||||||
@ -273,7 +263,20 @@ public final class GradientBackgroundNode: ASDisplayNode {
|
|||||||
self.invalidated = false
|
self.invalidated = false
|
||||||
|
|
||||||
var steps: [[CGPoint]] = []
|
var steps: [[CGPoint]] = []
|
||||||
if extendAnimation {
|
if backwards {
|
||||||
|
let phaseCount = extendAnimation ? 4 : 1
|
||||||
|
self.phase = (self.phase + phaseCount) % 8
|
||||||
|
self.validPhase = self.phase
|
||||||
|
|
||||||
|
var stepPhase = self.phase - phaseCount
|
||||||
|
if stepPhase < 0 {
|
||||||
|
stepPhase = 7
|
||||||
|
}
|
||||||
|
for _ in 0 ... phaseCount {
|
||||||
|
steps.append(gatherPositions(shiftArray(array: GradientBackgroundNode.basePositions, offset: stepPhase)))
|
||||||
|
stepPhase = (stepPhase + 1) % 8
|
||||||
|
}
|
||||||
|
} else if extendAnimation {
|
||||||
let phaseCount = 4
|
let phaseCount = 4
|
||||||
var stepPhase = (self.phase + phaseCount) % 8
|
var stepPhase = (self.phase + phaseCount) % 8
|
||||||
for _ in 0 ... phaseCount {
|
for _ in 0 ... phaseCount {
|
||||||
@ -332,13 +335,13 @@ public final class GradientBackgroundNode: ASDisplayNode {
|
|||||||
let animation = CAKeyframeAnimation(keyPath: "contents")
|
let animation = CAKeyframeAnimation(keyPath: "contents")
|
||||||
animation.values = images.map { $0.cgImage! }
|
animation.values = images.map { $0.cgImage! }
|
||||||
animation.duration = duration * UIView.animationDurationFactor()
|
animation.duration = duration * UIView.animationDurationFactor()
|
||||||
if extendAnimation {
|
if backwards || extendAnimation {
|
||||||
animation.calculationMode = .discrete
|
animation.calculationMode = .discrete
|
||||||
} else {
|
} else {
|
||||||
animation.calculationMode = .linear
|
animation.calculationMode = .linear
|
||||||
}
|
}
|
||||||
animation.isRemovedOnCompletion = true
|
animation.isRemovedOnCompletion = true
|
||||||
if extendAnimation {
|
if extendAnimation && !backwards {
|
||||||
animation.fillMode = .backwards
|
animation.fillMode = .backwards
|
||||||
animation.beginTime = self.contentView.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.25
|
animation.beginTime = self.contentView.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.25
|
||||||
}
|
}
|
||||||
@ -347,19 +350,13 @@ public final class GradientBackgroundNode: ASDisplayNode {
|
|||||||
self.contentView.layer.add(animation, forKey: "contents")
|
self.contentView.layer.add(animation, forKey: "contents")
|
||||||
|
|
||||||
if !self.cloneNodes.isEmpty {
|
if !self.cloneNodes.isEmpty {
|
||||||
let animation = CAKeyframeAnimation(keyPath: "contents")
|
let cloneAnimation = CAKeyframeAnimation(keyPath: "contents")
|
||||||
animation.values = dimmedImages.map { $0.cgImage! }
|
cloneAnimation.values = dimmedImages.map { $0.cgImage! }
|
||||||
animation.duration = duration * UIView.animationDurationFactor()
|
cloneAnimation.duration = animation.duration
|
||||||
if extendAnimation {
|
cloneAnimation.calculationMode = animation.calculationMode
|
||||||
animation.calculationMode = .discrete
|
cloneAnimation.isRemovedOnCompletion = animation.isRemovedOnCompletion
|
||||||
} else {
|
cloneAnimation.fillMode = animation.fillMode
|
||||||
animation.calculationMode = .linear
|
cloneAnimation.beginTime = animation.beginTime
|
||||||
}
|
|
||||||
animation.isRemovedOnCompletion = true
|
|
||||||
if extendAnimation {
|
|
||||||
animation.fillMode = .backwards
|
|
||||||
animation.beginTime = self.contentView.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.25
|
|
||||||
}
|
|
||||||
|
|
||||||
self._dimmedImage = dimmedImages.last
|
self._dimmedImage = dimmedImages.last
|
||||||
|
|
||||||
@ -367,7 +364,7 @@ public final class GradientBackgroundNode: ASDisplayNode {
|
|||||||
if let value = cloneNode.value {
|
if let value = cloneNode.value {
|
||||||
value.image = dimmedImages.last
|
value.image = dimmedImages.last
|
||||||
value.layer.removeAnimation(forKey: "contents")
|
value.layer.removeAnimation(forKey: "contents")
|
||||||
value.layer.add(animation, forKey: "contents")
|
value.layer.add(cloneAnimation, forKey: "contents")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -422,12 +419,12 @@ public final class GradientBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool = false) {
|
public func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool = false, backwards: Bool = false) {
|
||||||
guard case let .animated(duration, _) = transition, duration > 0.001 else {
|
guard case let .animated(duration, _) = transition, duration > 0.001 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if extendAnimation {
|
if extendAnimation || backwards {
|
||||||
self.invalidated = true
|
self.invalidated = true
|
||||||
} else {
|
} else {
|
||||||
if self.phase == 0 {
|
if self.phase == 0 {
|
||||||
@ -440,7 +437,7 @@ public final class GradientBackgroundNode: ASDisplayNode {
|
|||||||
GradientBackgroundNode.sharedPhase = self.phase
|
GradientBackgroundNode.sharedPhase = self.phase
|
||||||
}
|
}
|
||||||
if let size = self.validLayout {
|
if let size = self.validLayout {
|
||||||
self.updateLayout(size: size, transition: transition, extendAnimation: extendAnimation)
|
self.updateLayout(size: size, transition: transition, extendAnimation: extendAnimation, backwards: backwards)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,7 +57,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.context.account, peer: peer) |> deliverOnMainQueue).start(next: { actualPeerId in
|
strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.context.account, peer: peer) |> deliverOnMainQueue).start(next: { actualPeerId in
|
||||||
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
|
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: message.id.peerId == actualPeerId ? .message(id: message.id, highlight: true) : nil, keepStack: .always))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: message.id.peerId == actualPeerId ? .message(id: message.id, highlight: true, timecode: nil) : nil, keepStack: .always))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
strongSelf.controllerNode.listNode.clearHighlightAnimated(true)
|
strongSelf.controllerNode.listNode.clearHighlightAnimated(true)
|
||||||
|
|||||||
@ -204,6 +204,14 @@ private class TextField: UITextField, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let validIdentifierSet: CharacterSet = {
|
||||||
|
var set = CharacterSet(charactersIn: "a".unicodeScalars.first! ... "z".unicodeScalars.first!)
|
||||||
|
set.insert(charactersIn: "A".unicodeScalars.first! ... "Z".unicodeScalars.first!)
|
||||||
|
set.insert(charactersIn: "0".unicodeScalars.first! ... "9".unicodeScalars.first!)
|
||||||
|
set.insert("_")
|
||||||
|
return set
|
||||||
|
}()
|
||||||
|
|
||||||
private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
private let backgroundNode: ASImageNode
|
private let backgroundNode: ASImageNode
|
||||||
@ -260,7 +268,6 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF
|
|||||||
self.textInputNode.typingAttributes = [NSAttributedString.Key.font: Font.regular(14.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor]
|
self.textInputNode.typingAttributes = [NSAttributedString.Key.font: Font.regular(14.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor]
|
||||||
self.textInputNode.clipsToBounds = true
|
self.textInputNode.clipsToBounds = true
|
||||||
self.textInputNode.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(14.0), textColor: theme.actionSheet.secondaryTextColor)
|
self.textInputNode.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(14.0), textColor: theme.actionSheet.secondaryTextColor)
|
||||||
// self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0)
|
|
||||||
self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
||||||
self.textInputNode.keyboardType = keyboardType
|
self.textInputNode.keyboardType = keyboardType
|
||||||
self.textInputNode.autocapitalizationType = .sentences
|
self.textInputNode.autocapitalizationType = .sentences
|
||||||
@ -358,6 +365,39 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF
|
|||||||
self.complete?()
|
self.complete?()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.textInputNode.keyboardType == .asciiCapable {
|
||||||
|
var cleanString = string.folding(options: .diacriticInsensitive, locale: .current).replacingOccurrences(of: " ", with: "_")
|
||||||
|
|
||||||
|
let filtered = cleanString.unicodeScalars.filter { validIdentifierSet.contains($0) }
|
||||||
|
let filteredString = String(String.UnicodeScalarView(filtered))
|
||||||
|
|
||||||
|
if cleanString != filteredString {
|
||||||
|
cleanString = filteredString
|
||||||
|
|
||||||
|
self.textInputNode.layer.addShakeAnimation()
|
||||||
|
let hapticFeedback = HapticFeedback()
|
||||||
|
hapticFeedback.error()
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: {
|
||||||
|
let _ = hapticFeedback
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if cleanString != string {
|
||||||
|
var text = textField.text ?? ""
|
||||||
|
text.replaceSubrange(text.index(text.startIndex, offsetBy: range.lowerBound) ..< text.index(text.startIndex, offsetBy: range.upperBound), with: cleanString)
|
||||||
|
textField.text = text
|
||||||
|
if let startPosition = textField.position(from: textField.beginningOfDocument, offset: range.lowerBound + cleanString.count) {
|
||||||
|
let selectionRange = textField.textRange(from: startPosition, to: startPosition)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
textField.selectedTextRange = selectionRange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.textFieldDidUpdateText(text)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.textFieldDidUpdateText(updatedText)
|
self.textFieldDidUpdateText(updatedText)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -347,7 +347,7 @@ open class ItemListControllerNode: ASDisplayNode {
|
|||||||
self?.contentOffsetChanged?(offset, inVoiceOver)
|
self?.contentOffsetChanged?(offset, inVoiceOver)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.beganInteractiveDragging?()
|
strongSelf.beganInteractiveDragging?()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,9 @@
|
|||||||
|
|
||||||
@property (nonatomic, copy) void (^micLevel)(CGFloat);
|
@property (nonatomic, copy) void (^micLevel)(CGFloat);
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) bool isZoomAvailable;
|
||||||
|
@property (nonatomic, assign) CGFloat zoomLevel;
|
||||||
|
|
||||||
- (instancetype)initWithDelegate:(id<TGVideoCameraPipelineDelegate>)delegate position:(AVCaptureDevicePosition)position callbackQueue:(dispatch_queue_t)queue liveUploadInterface:(id<TGLiveUploadInterface>)liveUploadInterface;
|
- (instancetype)initWithDelegate:(id<TGVideoCameraPipelineDelegate>)delegate position:(AVCaptureDevicePosition)position callbackQueue:(dispatch_queue_t)queue liveUploadInterface:(id<TGLiveUploadInterface>)liveUploadInterface;
|
||||||
|
|
||||||
- (void)startRunning;
|
- (void)startRunning;
|
||||||
|
|||||||
@ -864,6 +864,52 @@ static CGFloat angleOffsetFromPortraitOrientationToOrientation(AVCaptureVideoOri
|
|||||||
return _recorder.videoDuration;
|
return _recorder.videoDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (CGFloat)zoomLevel
|
||||||
|
{
|
||||||
|
if (![_videoDevice respondsToSelector:@selector(videoZoomFactor)])
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
return (_videoDevice.videoZoomFactor - 1.0f) / ([self _maximumZoomFactor] - 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGFloat)_maximumZoomFactor
|
||||||
|
{
|
||||||
|
return MIN(5.0f, _videoDevice.activeFormat.videoMaxZoomFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setZoomLevel:(CGFloat)zoomLevel
|
||||||
|
{
|
||||||
|
zoomLevel = MAX(0.0f, MIN(1.0f, zoomLevel));
|
||||||
|
|
||||||
|
__weak TGVideoCameraPipeline *weakSelf = self;
|
||||||
|
[[TGVideoCameraPipeline cameraQueue] dispatch:^
|
||||||
|
{
|
||||||
|
__strong TGVideoCameraPipeline *strongSelf = weakSelf;
|
||||||
|
if (strongSelf == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
[self _reconfigureDevice:_videoDevice withBlock:^(AVCaptureDevice *device) {
|
||||||
|
device.videoZoomFactor = MAX(1.0f, MIN([strongSelf _maximumZoomFactor], 1.0f + ([strongSelf _maximumZoomFactor] - 1.0f) * zoomLevel));
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (bool)isZoomAvailable
|
||||||
|
{
|
||||||
|
return [TGVideoCameraPipeline _isZoomAvailableForDevice:_videoDevice];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (bool)_isZoomAvailableForDevice:(AVCaptureDevice *)device
|
||||||
|
{
|
||||||
|
if (![device respondsToSelector:@selector(setVideoZoomFactor:)])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (device.position == AVCaptureDevicePositionFront)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setCameraPosition:(AVCaptureDevicePosition)position
|
- (void)setCameraPosition:(AVCaptureDevicePosition)position
|
||||||
{
|
{
|
||||||
@synchronized (self)
|
@synchronized (self)
|
||||||
|
|||||||
@ -91,6 +91,8 @@ typedef enum
|
|||||||
TGVideoCameraGLView *_previewView;
|
TGVideoCameraGLView *_previewView;
|
||||||
TGVideoMessageRingView *_ringView;
|
TGVideoMessageRingView *_ringView;
|
||||||
|
|
||||||
|
UIPinchGestureRecognizer *_pinchGestureRecognizer;
|
||||||
|
|
||||||
UIView *_separatorView;
|
UIView *_separatorView;
|
||||||
|
|
||||||
UIImageView *_placeholderView;
|
UIImageView *_placeholderView;
|
||||||
@ -344,7 +346,6 @@ typedef enum
|
|||||||
[_circleWrapperView addSubview:_ringView];
|
[_circleWrapperView addSubview:_ringView];
|
||||||
|
|
||||||
CGRect controlsFrame = _controlsFrame;
|
CGRect controlsFrame = _controlsFrame;
|
||||||
// controlsFrame.size.width = _wrapperView.frame.size.width;
|
|
||||||
|
|
||||||
_controlsView = [[TGVideoMessageControls alloc] initWithFrame:controlsFrame assets:_assets slowmodeTimestamp:_slowmodeTimestamp slowmodeView:_slowmodeView];
|
_controlsView = [[TGVideoMessageControls alloc] initWithFrame:controlsFrame assets:_assets slowmodeTimestamp:_slowmodeTimestamp slowmodeView:_slowmodeView];
|
||||||
_controlsView.pallete = self.pallete;
|
_controlsView.pallete = self.pallete;
|
||||||
@ -417,12 +418,43 @@ typedef enum
|
|||||||
[self.view addSubview:_switchButton];
|
[self.view addSubview:_switchButton];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
|
||||||
|
_pinchGestureRecognizer.delegate = self;
|
||||||
|
[self.view addGestureRecognizer:_pinchGestureRecognizer];
|
||||||
|
|
||||||
void (^voidBlock)(void) = ^{};
|
void (^voidBlock)(void) = ^{};
|
||||||
_buttonHandler = [[PGCameraVolumeButtonHandler alloc] initWithUpButtonPressedBlock:voidBlock upButtonReleasedBlock:voidBlock downButtonPressedBlock:voidBlock downButtonReleasedBlock:voidBlock];
|
_buttonHandler = [[PGCameraVolumeButtonHandler alloc] initWithUpButtonPressedBlock:voidBlock upButtonReleasedBlock:voidBlock downButtonPressedBlock:voidBlock downButtonReleasedBlock:voidBlock];
|
||||||
|
|
||||||
[self configureCamera];
|
[self configureCamera];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
||||||
|
{
|
||||||
|
if (gestureRecognizer == _pinchGestureRecognizer)
|
||||||
|
return _capturePipeline.isZoomAvailable;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer
|
||||||
|
{
|
||||||
|
switch (gestureRecognizer.state)
|
||||||
|
{
|
||||||
|
case UIGestureRecognizerStateChanged:
|
||||||
|
{
|
||||||
|
CGFloat delta = (gestureRecognizer.scale - 1.0f) / 1.5f;
|
||||||
|
CGFloat value = MAX(0.0f, MIN(1.0f, _capturePipeline.zoomLevel + delta));
|
||||||
|
|
||||||
|
[_capturePipeline setZoomLevel:value];
|
||||||
|
|
||||||
|
gestureRecognizer.scale = 1.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (TGVideoMessageTransitionType)_transitionType
|
- (TGVideoMessageTransitionType)_transitionType
|
||||||
{
|
{
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
|
|||||||
@ -640,7 +640,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
|||||||
strongSelf.layoutEmptyResultsPlaceholder(transition: listTransition)
|
strongSelf.layoutEmptyResultsPlaceholder(transition: listTransition)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -224,7 +224,7 @@ final class LocationSearchContainerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.interaction.dismissInput()
|
self?.interaction.dismissInput()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -537,7 +537,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
|
|||||||
strongSelf.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition)
|
strongSelf.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ swift_library(
|
|||||||
"//submodules/AppBundle:AppBundle",
|
"//submodules/AppBundle:AppBundle",
|
||||||
"//submodules/PasscodeInputFieldNode:PasscodeInputFieldNode",
|
"//submodules/PasscodeInputFieldNode:PasscodeInputFieldNode",
|
||||||
"//submodules/MonotonicTime:MonotonicTime",
|
"//submodules/MonotonicTime:MonotonicTime",
|
||||||
|
"//submodules/GradientBackground:GradientBackground",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -1,19 +1,40 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
import ImageBlur
|
import ImageBlur
|
||||||
import FastBlur
|
import FastBlur
|
||||||
|
import GradientBackground
|
||||||
|
|
||||||
protocol PasscodeBackground {
|
protocol PasscodeBackground {
|
||||||
var size: CGSize { get }
|
var size: CGSize { get }
|
||||||
var backgroundImage: UIImage { get }
|
var backgroundImage: UIImage? { get }
|
||||||
var foregroundImage: UIImage { get }
|
var foregroundImage: UIImage? { get }
|
||||||
|
|
||||||
|
func makeBackgroundNode() -> ASDisplayNode?
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CustomPasscodeBackground: PasscodeBackground {
|
||||||
|
private let colors: [UIColor]
|
||||||
|
|
||||||
|
public private(set) var size: CGSize
|
||||||
|
public private(set) var backgroundImage: UIImage? = nil
|
||||||
|
public private(set) var foregroundImage: UIImage? = nil
|
||||||
|
|
||||||
|
init(size: CGSize, colors: [UIColor]) {
|
||||||
|
self.size = size
|
||||||
|
self.colors = colors
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeBackgroundNode() -> ASDisplayNode? {
|
||||||
|
return createGradientBackgroundNode(colors: self.colors)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class GradientPasscodeBackground: PasscodeBackground {
|
final class GradientPasscodeBackground: PasscodeBackground {
|
||||||
public private(set) var size: CGSize
|
public private(set) var size: CGSize
|
||||||
public private(set) var backgroundImage: UIImage
|
public private(set) var backgroundImage: UIImage?
|
||||||
public private(set) var foregroundImage: UIImage
|
public private(set) var foregroundImage: UIImage?
|
||||||
|
|
||||||
init(size: CGSize, backgroundColors: (UIColor, UIColor), buttonColor: UIColor) {
|
init(size: CGSize, backgroundColors: (UIColor, UIColor), buttonColor: UIColor) {
|
||||||
self.size = size
|
self.size = size
|
||||||
@ -35,12 +56,16 @@ final class GradientPasscodeBackground: PasscodeBackground {
|
|||||||
context.fill(bounds)
|
context.fill(bounds)
|
||||||
})!
|
})!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeBackgroundNode() -> ASDisplayNode? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ImageBasedPasscodeBackground: PasscodeBackground {
|
final class ImageBasedPasscodeBackground: PasscodeBackground {
|
||||||
public private(set) var size: CGSize
|
public private(set) var size: CGSize
|
||||||
public private(set) var backgroundImage: UIImage
|
public private(set) var backgroundImage: UIImage?
|
||||||
public private(set) var foregroundImage: UIImage
|
public private(set) var foregroundImage: UIImage?
|
||||||
|
|
||||||
init(image: UIImage, size: CGSize) {
|
init(image: UIImage, size: CGSize) {
|
||||||
self.size = size
|
self.size = size
|
||||||
@ -82,4 +107,8 @@ final class ImageBasedPasscodeBackground: PasscodeBackground {
|
|||||||
}
|
}
|
||||||
self.backgroundImage = backgroundContext.generateImage()!
|
self.backgroundImage = backgroundContext.generateImage()!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeBackgroundNode() -> ASDisplayNode? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,10 @@ public final class PasscodeEntryController: ViewController {
|
|||||||
private var inBackground: Bool = false
|
private var inBackground: Bool = false
|
||||||
private var inBackgroundDisposable: Disposable?
|
private var inBackgroundDisposable: Disposable?
|
||||||
|
|
||||||
public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, appLockContext: AppLockContext, presentationData: PresentationData, presentationDataSignal: Signal<PresentationData, NoError>, challengeData: PostboxAccessChallengeData, biometrics: PasscodeEntryControllerBiometricsMode, arguments: PasscodeEntryControllerPresentationArguments) {
|
private let statusBarHost: StatusBarHost?
|
||||||
|
private var previousStatusBarStyle: UIStatusBarStyle?
|
||||||
|
|
||||||
|
public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, appLockContext: AppLockContext, presentationData: PresentationData, presentationDataSignal: Signal<PresentationData, NoError>, statusBarHost: StatusBarHost?, challengeData: PostboxAccessChallengeData, biometrics: PasscodeEntryControllerBiometricsMode, arguments: PasscodeEntryControllerPresentationArguments) {
|
||||||
self.applicationBindings = applicationBindings
|
self.applicationBindings = applicationBindings
|
||||||
self.accountManager = accountManager
|
self.accountManager = accountManager
|
||||||
self.appLockContext = appLockContext
|
self.appLockContext = appLockContext
|
||||||
@ -68,10 +71,12 @@ public final class PasscodeEntryController: ViewController {
|
|||||||
self.biometrics = biometrics
|
self.biometrics = biometrics
|
||||||
self.arguments = arguments
|
self.arguments = arguments
|
||||||
|
|
||||||
|
self.statusBarHost = statusBarHost
|
||||||
|
self.previousStatusBarStyle = statusBarHost?.statusBarStyle
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
self.statusBar.statusBarStyle = .White
|
statusBarHost?.setStatusBarStyle(.lightContent, animated: true)
|
||||||
|
|
||||||
self.presentationDataDisposable = (presentationDataSignal
|
self.presentationDataDisposable = (presentationDataSignal
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||||
@ -128,7 +133,7 @@ public final class PasscodeEntryController: ViewController {
|
|||||||
} else {
|
} else {
|
||||||
biometricsType = nil
|
biometricsType = nil
|
||||||
}
|
}
|
||||||
self.displayNode = PasscodeEntryControllerNode(accountManager: self.accountManager, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, passcodeType: passcodeType, biometricsType: biometricsType, arguments: self.arguments, statusBar: self.statusBar, modalPresentation: self.arguments.modalPresentation)
|
self.displayNode = PasscodeEntryControllerNode(accountManager: self.accountManager, presentationData: self.presentationData, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, passcodeType: passcodeType, biometricsType: biometricsType, arguments: self.arguments, modalPresentation: self.arguments.modalPresentation)
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
let _ = (self.appLockContext.invalidAttempts
|
let _ = (self.appLockContext.invalidAttempts
|
||||||
@ -271,6 +276,9 @@ public final class PasscodeEntryController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override func dismiss(completion: (() -> Void)? = nil) {
|
public override func dismiss(completion: (() -> Void)? = nil) {
|
||||||
|
if let statusBarHost = self.statusBarHost, let previousStatusBarStyle = self.previousStatusBarStyle {
|
||||||
|
statusBarHost.setStatusBarStyle(previousStatusBarStyle, animated: true)
|
||||||
|
}
|
||||||
self.view.endEditing(true)
|
self.view.endEditing(true)
|
||||||
self.controllerNode.animateOut { [weak self] in
|
self.controllerNode.animateOut { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import LocalAuth
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import PasscodeInputFieldNode
|
import PasscodeInputFieldNode
|
||||||
import MonotonicTime
|
import MonotonicTime
|
||||||
|
import GradientBackground
|
||||||
|
|
||||||
private let titleFont = Font.regular(20.0)
|
private let titleFont = Font.regular(20.0)
|
||||||
private let subtitleFont = Font.regular(15.0)
|
private let subtitleFont = Font.regular(15.0)
|
||||||
@ -19,6 +20,7 @@ private let buttonFont = Font.regular(17.0)
|
|||||||
|
|
||||||
final class PasscodeEntryControllerNode: ASDisplayNode {
|
final class PasscodeEntryControllerNode: ASDisplayNode {
|
||||||
private let accountManager: AccountManager
|
private let accountManager: AccountManager
|
||||||
|
private var presentationData: PresentationData
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
private var strings: PresentationStrings
|
private var strings: PresentationStrings
|
||||||
private var wallpaper: TelegramWallpaper
|
private var wallpaper: TelegramWallpaper
|
||||||
@ -27,11 +29,11 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
private let arguments: PasscodeEntryControllerPresentationArguments
|
private let arguments: PasscodeEntryControllerPresentationArguments
|
||||||
private var background: PasscodeBackground?
|
private var background: PasscodeBackground?
|
||||||
|
|
||||||
private let statusBar: StatusBar
|
|
||||||
|
|
||||||
private let modalPresentation: Bool
|
private let modalPresentation: Bool
|
||||||
|
|
||||||
private let backgroundNode: ASImageNode
|
private var backgroundCustomNode: ASDisplayNode?
|
||||||
|
private let backgroundDimNode: ASDisplayNode
|
||||||
|
private let backgroundImageNode: ASImageNode
|
||||||
private let iconNode: PasscodeLockIconNode
|
private let iconNode: PasscodeLockIconNode
|
||||||
private let titleNode: PasscodeEntryLabelNode
|
private let titleNode: PasscodeEntryLabelNode
|
||||||
private let inputFieldNode: PasscodeInputFieldNode
|
private let inputFieldNode: PasscodeInputFieldNode
|
||||||
@ -52,19 +54,23 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
var checkPasscode: ((String) -> Void)?
|
var checkPasscode: ((String) -> Void)?
|
||||||
var requestBiometrics: (() -> Void)?
|
var requestBiometrics: (() -> Void)?
|
||||||
|
|
||||||
init(accountManager: AccountManager, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, passcodeType: PasscodeEntryFieldType, biometricsType: LocalAuthBiometricAuthentication?, arguments: PasscodeEntryControllerPresentationArguments, statusBar: StatusBar, modalPresentation: Bool) {
|
init(accountManager: AccountManager, presentationData: PresentationData, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, passcodeType: PasscodeEntryFieldType, biometricsType: LocalAuthBiometricAuthentication?, arguments: PasscodeEntryControllerPresentationArguments, modalPresentation: Bool) {
|
||||||
self.accountManager = accountManager
|
self.accountManager = accountManager
|
||||||
|
self.presentationData = presentationData
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.wallpaper = wallpaper
|
self.wallpaper = wallpaper
|
||||||
self.passcodeType = passcodeType
|
self.passcodeType = passcodeType
|
||||||
self.biometricsType = biometricsType
|
self.biometricsType = biometricsType
|
||||||
self.arguments = arguments
|
self.arguments = arguments
|
||||||
self.statusBar = statusBar
|
|
||||||
self.modalPresentation = modalPresentation
|
self.modalPresentation = modalPresentation
|
||||||
|
|
||||||
self.backgroundNode = ASImageNode()
|
self.backgroundImageNode = ASImageNode()
|
||||||
self.backgroundNode.contentMode = .scaleToFill
|
self.backgroundImageNode.contentMode = .scaleToFill
|
||||||
|
|
||||||
|
self.backgroundDimNode = ASDisplayNode()
|
||||||
|
self.backgroundDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.15)
|
||||||
|
self.backgroundDimNode.isHidden = true
|
||||||
|
|
||||||
self.iconNode = PasscodeLockIconNode()
|
self.iconNode = PasscodeLockIconNode()
|
||||||
self.titleNode = PasscodeEntryLabelNode()
|
self.titleNode = PasscodeEntryLabelNode()
|
||||||
@ -86,7 +92,12 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
self.iconNode.unlockedColor = theme.rootController.navigationBar.primaryTextColor
|
self.iconNode.unlockedColor = theme.rootController.navigationBar.primaryTextColor
|
||||||
|
|
||||||
self.keyboardNode.charactedEntered = { [weak self] character in
|
self.keyboardNode.charactedEntered = { [weak self] character in
|
||||||
self?.inputFieldNode.append(character)
|
if let strongSelf = self {
|
||||||
|
strongSelf.inputFieldNode.append(character)
|
||||||
|
if let gradientNode = strongSelf.backgroundCustomNode as? GradientBackgroundNode {
|
||||||
|
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.inputFieldNode.complete = { [weak self] passcode in
|
self.inputFieldNode.complete = { [weak self] passcode in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -111,7 +122,8 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundImageNode)
|
||||||
|
self.addSubnode(self.backgroundDimNode)
|
||||||
self.addSubnode(self.iconNode)
|
self.addSubnode(self.iconNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.inputFieldNode)
|
self.addSubnode(self.inputFieldNode)
|
||||||
@ -146,7 +158,10 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
@objc private func deletePressed() {
|
@objc private func deletePressed() {
|
||||||
self.hapticFeedback.tap()
|
self.hapticFeedback.tap()
|
||||||
self.inputFieldNode.delete()
|
let result = self.inputFieldNode.delete()
|
||||||
|
if result, let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
|
||||||
|
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring), backwards: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func biometricsPressed() {
|
@objc private func biometricsPressed() {
|
||||||
@ -158,6 +173,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updatePresentationData(_ presentationData: PresentationData) {
|
func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
|
self.presentationData = presentationData
|
||||||
self.theme = presentationData.theme
|
self.theme = presentationData.theme
|
||||||
self.strings = presentationData.strings
|
self.strings = presentationData.strings
|
||||||
self.wallpaper = presentationData.chatWallpaper
|
self.wallpaper = presentationData.chatWallpaper
|
||||||
@ -173,26 +189,42 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var size = validLayout.size
|
let size = validLayout.size
|
||||||
if let background = self.background, background.size == size {
|
if let background = self.background, background.size == size {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch self.wallpaper {
|
switch self.wallpaper {
|
||||||
|
case let .gradient(_, colors, _):
|
||||||
|
self.background = CustomPasscodeBackground(size: size, colors: colors.compactMap { UIColor(rgb: $0) })
|
||||||
case .image, .file:
|
case .image, .file:
|
||||||
if let image = chatControllerBackgroundImage(theme: self.theme, wallpaper: self.wallpaper, mediaBox: self.accountManager.mediaBox, composed: false, knockoutMode: false) {
|
if let image = chatControllerBackgroundImage(theme: self.theme, wallpaper: self.wallpaper, mediaBox: self.accountManager.mediaBox, composed: false, knockoutMode: false) {
|
||||||
self.background = ImageBasedPasscodeBackground(image: image, size: size)
|
self.background = ImageBasedPasscodeBackground(image: image, size: size)
|
||||||
|
} else {
|
||||||
|
if case let .file(file) = self.wallpaper, !file.settings.colors.isEmpty {
|
||||||
|
self.background = CustomPasscodeBackground(size: size, colors: file.settings.colors.compactMap { UIColor(rgb: $0) })
|
||||||
} else {
|
} else {
|
||||||
self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor)
|
self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor)
|
self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let background = self.background {
|
if let background = self.background {
|
||||||
self.backgroundNode.image = background.backgroundImage
|
self.backgroundCustomNode?.removeFromSupernode()
|
||||||
self.keyboardNode.updateBackground(background)
|
self.backgroundCustomNode = nil
|
||||||
self.inputFieldNode.updateBackground(background.foregroundImage, size: background.size)
|
|
||||||
|
if let backgroundImage = background.backgroundImage {
|
||||||
|
self.backgroundImageNode.image = backgroundImage
|
||||||
|
self.backgroundDimNode.isHidden = true
|
||||||
|
} else if let customBackgroundNode = background.makeBackgroundNode() {
|
||||||
|
self.backgroundCustomNode = customBackgroundNode
|
||||||
|
self.insertSubnode(customBackgroundNode, aboveSubnode: self.backgroundImageNode)
|
||||||
|
self.backgroundDimNode.isHidden = false
|
||||||
|
}
|
||||||
|
self.keyboardNode.updateBackground(self.presentationData, background)
|
||||||
|
self.inputFieldNode.updateBackground(background)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,7 +295,12 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
self.effectView.alpha = 1.0
|
self.effectView.alpha = 1.0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
|
||||||
|
gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.backgroundDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
gradientNode.animateEvent(transition: .animated(duration: 1.0, curve: .spring), extendAnimation: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.titleNode.setAttributedText(NSAttributedString(string: self.strings.EnterPasscode_EnterPasscode, font: titleFont, textColor: .white), animation: .none)
|
self.titleNode.setAttributedText(NSAttributedString(string: self.strings.EnterPasscode_EnterPasscode, font: titleFont, textColor: .white), animation: .none)
|
||||||
}
|
}
|
||||||
@ -277,15 +314,17 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
self.effectView.alpha = 1.0
|
self.effectView.alpha = 1.0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
|
||||||
|
gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
gradientNode.animateEvent(transition: .animated(duration: 0.35, curve: .spring))
|
||||||
|
self.backgroundDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
}
|
||||||
if !iconFrame.isEmpty {
|
if !iconFrame.isEmpty {
|
||||||
self.iconNode.animateIn(fromScale: 0.416)
|
self.iconNode.animateIn(fromScale: 0.416)
|
||||||
self.iconNode.layer.animatePosition(from: iconFrame.center.offsetBy(dx: 6.0, dy: 6.0), to: self.iconNode.layer.position, duration: 0.45)
|
self.iconNode.layer.animatePosition(from: iconFrame.center.offsetBy(dx: 6.0, dy: 6.0), to: self.iconNode.layer.position, duration: 0.45)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.statusBar.layer.removeAnimation(forKey: "opacity")
|
|
||||||
self.statusBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
||||||
|
|
||||||
self.subtitleNode.isHidden = true
|
self.subtitleNode.isHidden = true
|
||||||
self.inputFieldNode.isHidden = true
|
self.inputFieldNode.isHidden = true
|
||||||
self.keyboardNode.isHidden = true
|
self.keyboardNode.isHidden = true
|
||||||
@ -303,6 +342,9 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.subtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
self.subtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
|
||||||
|
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
|
||||||
|
gradientNode.animateEvent(transition: .animated(duration: 1.0, curve: .spring))
|
||||||
|
}
|
||||||
self.inputFieldNode.animateIn()
|
self.inputFieldNode.animateIn()
|
||||||
self.keyboardNode.animateIn()
|
self.keyboardNode.animateIn()
|
||||||
var biometricDelay = 0.3
|
var biometricDelay = 0.3
|
||||||
@ -323,7 +365,6 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(down: Bool = false, completion: @escaping () -> Void = {}) {
|
func animateOut(down: Bool = false, completion: @escaping () -> Void = {}) {
|
||||||
self.statusBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
|
||||||
self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: down ? self.bounds.size.height : -self.bounds.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in
|
self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: down ? self.bounds.size.height : -self.bounds.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in
|
||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
@ -340,6 +381,10 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
self.iconNode.layer.addShakeAnimation(amplitude: -8.0, duration: 0.5, count: 6, decay: true)
|
self.iconNode.layer.addShakeAnimation(amplitude: -8.0, duration: 0.5, count: 6, decay: true)
|
||||||
|
|
||||||
self.hapticFeedback.error()
|
self.hapticFeedback.error()
|
||||||
|
|
||||||
|
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
|
||||||
|
gradientNode.animateEvent(transition: .animated(duration: 1.5, curve: .spring), extendAnimation: true, backwards: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
@ -348,7 +393,14 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
|||||||
self.updateBackground()
|
self.updateBackground()
|
||||||
|
|
||||||
let bounds = CGRect(origin: CGPoint(), size: layout.size)
|
let bounds = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: bounds)
|
transition.updateFrame(node: self.backgroundImageNode, frame: bounds)
|
||||||
|
transition.updateFrame(node: self.backgroundDimNode, frame: bounds)
|
||||||
|
if let backgroundCustomNode = self.backgroundCustomNode {
|
||||||
|
transition.updateFrame(node: backgroundCustomNode, frame: bounds)
|
||||||
|
if let gradientBackgroundNode = backgroundCustomNode as? GradientBackgroundNode {
|
||||||
|
gradientBackgroundNode.updateLayout(size: bounds.size, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
transition.updateFrame(view: self.effectView, frame: bounds)
|
transition.updateFrame(view: self.effectView, frame: bounds)
|
||||||
|
|
||||||
switch self.passcodeType {
|
switch self.passcodeType {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import UIKit
|
|||||||
import Display
|
import Display
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
private let regularTitleFont = Font.regular(36.0)
|
private let regularTitleFont = Font.regular(36.0)
|
||||||
private let regularSubtitleFont: UIFont = {
|
private let regularSubtitleFont: UIFont = {
|
||||||
@ -35,8 +36,9 @@ private func generateButtonImage(background: PasscodeBackground, frame: CGRect,
|
|||||||
context.clip()
|
context.clip()
|
||||||
|
|
||||||
context.setAlpha(0.8)
|
context.setAlpha(0.8)
|
||||||
context.draw(background.foregroundImage.cgImage!, in: relativeFrame)
|
if let foregroundImage = background.foregroundImage {
|
||||||
|
context.draw(foregroundImage.cgImage!, in: relativeFrame)
|
||||||
|
}
|
||||||
if highlighted {
|
if highlighted {
|
||||||
context.setFillColor(UIColor(white: 1.0, alpha: 0.65).cgColor)
|
context.setFillColor(UIColor(white: 1.0, alpha: 0.65).cgColor)
|
||||||
context.fillEllipse(in: bounds)
|
context.fillEllipse(in: bounds)
|
||||||
@ -98,6 +100,7 @@ private func generateButtonImage(background: PasscodeBackground, frame: CGRect,
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class PasscodeEntryButtonNode: HighlightTrackingButtonNode {
|
final class PasscodeEntryButtonNode: HighlightTrackingButtonNode {
|
||||||
|
private var presentationData: PresentationData
|
||||||
private var background: PasscodeBackground
|
private var background: PasscodeBackground
|
||||||
let title: String
|
let title: String
|
||||||
private let subtitle: String
|
private let subtitle: String
|
||||||
@ -106,15 +109,24 @@ final class PasscodeEntryButtonNode: HighlightTrackingButtonNode {
|
|||||||
private var regularImage: UIImage?
|
private var regularImage: UIImage?
|
||||||
private var highlightedImage: UIImage?
|
private var highlightedImage: UIImage?
|
||||||
|
|
||||||
|
private var blurredBackgroundNode: NavigationBackgroundNode?
|
||||||
private let backgroundNode: ASImageNode
|
private let backgroundNode: ASImageNode
|
||||||
|
|
||||||
var action: (() -> Void)?
|
var action: (() -> Void)?
|
||||||
|
|
||||||
init(background: PasscodeBackground, title: String, subtitle: String) {
|
init(presentationData: PresentationData, background: PasscodeBackground, title: String, subtitle: String) {
|
||||||
|
self.presentationData = presentationData
|
||||||
self.background = background
|
self.background = background
|
||||||
self.title = title
|
self.title = title
|
||||||
self.subtitle = subtitle
|
self.subtitle = subtitle
|
||||||
|
|
||||||
|
if background is CustomPasscodeBackground {
|
||||||
|
let blurredBackgroundColor = (selectDateFillStaticColor(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), dateFillNeedsBlur(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper))
|
||||||
|
|
||||||
|
let blurredBackgroundNode = NavigationBackgroundNode(color: blurredBackgroundColor.0, enableBlur: blurredBackgroundColor.1)
|
||||||
|
self.blurredBackgroundNode = blurredBackgroundNode
|
||||||
|
}
|
||||||
|
|
||||||
self.backgroundNode = ASImageNode()
|
self.backgroundNode = ASImageNode()
|
||||||
self.backgroundNode.displaysAsynchronously = false
|
self.backgroundNode.displaysAsynchronously = false
|
||||||
self.backgroundNode.displayWithoutProcessing = true
|
self.backgroundNode.displayWithoutProcessing = true
|
||||||
@ -122,6 +134,9 @@ final class PasscodeEntryButtonNode: HighlightTrackingButtonNode {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
if let blurredBackgroundNode = self.blurredBackgroundNode {
|
||||||
|
self.addSubnode(blurredBackgroundNode)
|
||||||
|
}
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
|
|
||||||
self.highligthedChanged = { [weak self] highlighted in
|
self.highligthedChanged = { [weak self] highlighted in
|
||||||
@ -146,7 +161,8 @@ final class PasscodeEntryButtonNode: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateBackground(_ background: PasscodeBackground) {
|
func updateBackground(_ presentationData: PresentationData, _ background: PasscodeBackground) {
|
||||||
|
self.presentationData = presentationData
|
||||||
self.background = background
|
self.background = background
|
||||||
self.updateGraphics()
|
self.updateGraphics()
|
||||||
}
|
}
|
||||||
@ -175,6 +191,10 @@ final class PasscodeEntryButtonNode: HighlightTrackingButtonNode {
|
|||||||
override func layout() {
|
override func layout() {
|
||||||
super.layout()
|
super.layout()
|
||||||
|
|
||||||
|
if let blurredBackgroundNode = self.blurredBackgroundNode {
|
||||||
|
blurredBackgroundNode.frame = self.bounds
|
||||||
|
blurredBackgroundNode.update(size: blurredBackgroundNode.bounds.size, cornerRadius: blurredBackgroundNode.bounds.height / 2.0, transition: .immediate)
|
||||||
|
}
|
||||||
self.backgroundNode.frame = self.bounds
|
self.backgroundNode.frame = self.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,22 +219,23 @@ private let buttonsData = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
final class PasscodeEntryKeyboardNode: ASDisplayNode {
|
final class PasscodeEntryKeyboardNode: ASDisplayNode {
|
||||||
|
private var presentationData: PresentationData?
|
||||||
private var background: PasscodeBackground?
|
private var background: PasscodeBackground?
|
||||||
|
|
||||||
var charactedEntered: ((String) -> Void)?
|
var charactedEntered: ((String) -> Void)?
|
||||||
|
|
||||||
private func updateButtons() {
|
private func updateButtons() {
|
||||||
guard let background = self.background else {
|
guard let presentationData = self.presentationData, let background = self.background else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let subnodes = self.subnodes, !subnodes.isEmpty {
|
if let subnodes = self.subnodes, !subnodes.isEmpty {
|
||||||
for case let button as PasscodeEntryButtonNode in subnodes {
|
for case let button as PasscodeEntryButtonNode in subnodes {
|
||||||
button.updateBackground(background)
|
button.updateBackground(presentationData, background)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (title, subtitle) in buttonsData {
|
for (title, subtitle) in buttonsData {
|
||||||
let buttonNode = PasscodeEntryButtonNode(background: background, title: title, subtitle: subtitle)
|
let buttonNode = PasscodeEntryButtonNode(presentationData: presentationData, background: background, title: title, subtitle: subtitle)
|
||||||
buttonNode.action = { [weak self] in
|
buttonNode.action = { [weak self] in
|
||||||
self?.charactedEntered?(title)
|
self?.charactedEntered?(title)
|
||||||
}
|
}
|
||||||
@ -223,7 +244,8 @@ final class PasscodeEntryKeyboardNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateBackground(_ background: PasscodeBackground) {
|
func updateBackground(_ presentationData: PresentationData, _ background: PasscodeBackground) {
|
||||||
|
self.presentationData = presentationData
|
||||||
self.background = background
|
self.background = background
|
||||||
self.updateButtons()
|
self.updateButtons()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ final class PasscodeEntryLabelNode: ASDisplayNode {
|
|||||||
self.textNode = ASTextNode()
|
self.textNode = ASTextNode()
|
||||||
self.textNode.isLayerBacked = false
|
self.textNode.isLayerBacked = false
|
||||||
self.textNode.textAlignment = .center
|
self.textNode.textAlignment = .center
|
||||||
|
self.textNode.displaysAsynchronously = false
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
|||||||
@ -24,20 +24,23 @@ private func generateDotImage(color: UIColor, filled: Bool) -> UIImage? {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateFieldBackgroundImage(backgroundImage: UIImage, backgroundSize: CGSize, frame: CGRect) -> UIImage? {
|
private func generateFieldBackgroundImage(backgroundImage: UIImage?, backgroundSize: CGSize?, frame: CGRect) -> UIImage? {
|
||||||
return generateImage(frame.size, contextGenerator: { size, context in
|
return generateImage(frame.size, contextGenerator: { size, context in
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
context.clear(bounds)
|
context.clear(bounds)
|
||||||
|
|
||||||
let relativeFrame = CGRect(x: -frame.minX, y: frame.minY - backgroundSize.height + frame.size.height
|
|
||||||
, width: backgroundSize.width, height: backgroundSize.height)
|
|
||||||
|
|
||||||
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height), cornerRadius: 6.0)
|
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height), cornerRadius: 6.0)
|
||||||
context.addPath(path.cgPath)
|
context.addPath(path.cgPath)
|
||||||
context.clip()
|
context.clip()
|
||||||
|
|
||||||
|
if let backgroundImage = backgroundImage, let backgroundSize = backgroundSize {
|
||||||
|
let relativeFrame = CGRect(x: -frame.minX, y: frame.minY - backgroundSize.height + frame.size.height
|
||||||
|
, width: backgroundSize.width, height: backgroundSize.height)
|
||||||
context.draw(backgroundImage.cgImage!, in: relativeFrame)
|
context.draw(backgroundImage.cgImage!, in: relativeFrame)
|
||||||
|
} else {
|
||||||
|
context.setFillColor(UIColor(rgb: 0xffffff, alpha: 1.0).cgColor)
|
||||||
|
context.fill(bounds)
|
||||||
|
}
|
||||||
context.setBlendMode(.clear)
|
context.setBlendMode(.clear)
|
||||||
context.setFillColor(UIColor.clear.cgColor)
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
|
||||||
@ -129,7 +132,7 @@ private class PasscodeEntryDotNode: ASImageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
||||||
private var background: (UIImage, CGSize)?
|
private var background: PasscodeBackground?
|
||||||
private var color: UIColor
|
private var color: UIColor
|
||||||
private var accentColor: UIColor
|
private var accentColor: UIColor
|
||||||
private var fieldType: PasscodeEntryFieldType
|
private var fieldType: PasscodeEntryFieldType
|
||||||
@ -207,8 +210,8 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateBackground(_ image: UIImage, size: CGSize) {
|
func updateBackground(_ background: PasscodeBackground) {
|
||||||
self.background = (image, size)
|
self.background = background
|
||||||
if let (size, topOffset) = self.validLayout {
|
if let (size, topOffset) = self.validLayout {
|
||||||
let _ = self.updateLayout(size: size, topOffset: topOffset, transition: .immediate)
|
let _ = self.updateLayout(size: size, topOffset: topOffset, transition: .immediate)
|
||||||
}
|
}
|
||||||
@ -276,14 +279,15 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func delete() {
|
func delete() -> Bool {
|
||||||
var text = self.textFieldNode.textField.text ?? ""
|
var text = self.textFieldNode.textField.text ?? ""
|
||||||
guard !text.isEmpty else {
|
guard !text.isEmpty else {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
text = String(text[text.startIndex ..< text.index(text.endIndex, offsetBy: -1)])
|
text = String(text[text.startIndex ..< text.index(text.endIndex, offsetBy: -1)])
|
||||||
self.textFieldNode.textField.text = text
|
self.textFieldNode.textField.text = text
|
||||||
self.updateDots(count: text.count, animated: true)
|
self.updateDots(count: text.count, animated: true)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateDots(count: Int, animated: Bool) {
|
func updateDots(count: Int, animated: Bool) {
|
||||||
@ -346,9 +350,8 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
|||||||
let fieldFrame = CGRect(x: inset, y: origin.y, width: size.width - inset * 2.0, height: fieldHeight)
|
let fieldFrame = CGRect(x: inset, y: origin.y, width: size.width - inset * 2.0, height: fieldHeight)
|
||||||
transition.updateFrame(node: self.borderNode, frame: fieldFrame)
|
transition.updateFrame(node: self.borderNode, frame: fieldFrame)
|
||||||
transition.updateFrame(node: self.textFieldNode, frame: fieldFrame.insetBy(dx: 13.0, dy: 0.0))
|
transition.updateFrame(node: self.textFieldNode, frame: fieldFrame.insetBy(dx: 13.0, dy: 0.0))
|
||||||
if let (backgroundImage, backgroundSize) = self.background {
|
|
||||||
self.borderNode.image = generateFieldBackgroundImage(backgroundImage: backgroundImage, backgroundSize: backgroundSize, frame: fieldFrame)
|
self.borderNode.image = generateFieldBackgroundImage(backgroundImage: self.background?.foregroundImage, backgroundSize: self.background?.size, frame: fieldFrame)
|
||||||
}
|
|
||||||
|
|
||||||
return fieldFrame
|
return fieldFrame
|
||||||
}
|
}
|
||||||
|
|||||||
@ -219,7 +219,7 @@ final class ChannelDiscussionGroupSearchContainerNode: SearchDisplayControllerCo
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1279,10 +1279,10 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.emptyQueryListNode.beganInteractiveDragging = { [weak self] in
|
self.emptyQueryListNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -581,7 +581,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.view.endEditing(true)
|
self?.view.endEditing(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -263,7 +263,7 @@ private final class OldChannelsSearchContainerNode: SearchDisplayControllerConte
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -174,7 +174,7 @@ private final class LocalizationListSearchContainerNode: SearchDisplayController
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1267,7 +1267,7 @@ private final class NotificationExceptionsSearchContainerNode: SearchDisplayCont
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -469,7 +469,7 @@ public func passcodeEntryController(context: AccountContext, animateIn: Bool = t
|
|||||||
biometrics = .none
|
biometrics = .none
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
let controller = PasscodeEntryController(applicationBindings: context.sharedContext.applicationBindings, accountManager: context.sharedContext.accountManager, appLockContext: context.sharedContext.appLockContext, presentationData: context.sharedContext.currentPresentationData.with { $0 }, presentationDataSignal: context.sharedContext.presentationData, challengeData: challenge, biometrics: biometrics, arguments: PasscodeEntryControllerPresentationArguments(animated: false, fadeIn: true, cancel: {
|
let controller = PasscodeEntryController(applicationBindings: context.sharedContext.applicationBindings, accountManager: context.sharedContext.accountManager, appLockContext: context.sharedContext.appLockContext, presentationData: context.sharedContext.currentPresentationData.with { $0 }, presentationDataSignal: context.sharedContext.presentationData, statusBarHost: context.sharedContext.mainWindow?.statusBarHost, challengeData: challenge, biometrics: biometrics, arguments: PasscodeEntryControllerPresentationArguments(animated: false, fadeIn: true, cancel: {
|
||||||
completion(false)
|
completion(false)
|
||||||
}, modalPresentation: modalPresentation))
|
}, modalPresentation: modalPresentation))
|
||||||
controller.presentationCompleted = { [weak controller] in
|
controller.presentationCompleted = { [weak controller] in
|
||||||
|
|||||||
@ -535,11 +535,11 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.recentListNode.beganInteractiveDragging = { [weak self] in
|
self.recentListNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -654,7 +654,7 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.recentListNode.beganInteractiveDragging = { [weak self] in
|
self.recentListNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -518,7 +518,7 @@ public func channelStatsController(context: AccountContext, peerId: PeerId, cach
|
|||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, _ in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, _ in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
if let navigationController = controller?.navigationController as? NavigationController {
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true)))
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true, timecode: nil)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
|||||||
@ -264,7 +264,7 @@ public func messageStatsController(context: AccountContext, messageId: MessageId
|
|||||||
}
|
}
|
||||||
navigateToMessageImpl = { [weak controller] messageId in
|
navigateToMessageImpl = { [weak controller] messageId in
|
||||||
if let navigationController = controller?.navigationController as? NavigationController {
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true), keepStack: .always, useExisting: false, purposefulAction: {}, peekData: nil))
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {}, peekData: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return controller
|
return controller
|
||||||
|
|||||||
@ -166,7 +166,7 @@ class MessageStatsOverviewItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
centerValueLabelLayoutAndApply = makeCenterValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.publicShares.flatMap { compactNumericCountString(Int($0)) } ?? "–", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
centerValueLabelLayoutAndApply = makeCenterValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.publicShares.flatMap { compactNumericCountString(Int($0)) } ?? "–", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
rightValueLabelLayoutAndApply = makeRightValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.publicShares.flatMap { "≈\( compactNumericCountString(item.stats.forwards - Int($0)))" } ?? "–", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
rightValueLabelLayoutAndApply = makeRightValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.publicShares.flatMap { "≈\( compactNumericCountString(max(0, item.stats.forwards - Int($0))))" } ?? "–", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
leftTitleLabelLayoutAndApply = makeLeftTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_Message_Views, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
leftTitleLabelLayoutAndApply = makeLeftTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_Message_Views, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
|||||||
@ -759,7 +759,7 @@ final class AuthorizedApplicationContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let navigateToMessage = {
|
let navigateToMessage = {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true)))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true, timecode: nil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if chatIsVisible {
|
if chatIsVisible {
|
||||||
@ -838,7 +838,7 @@ final class AuthorizedApplicationContext {
|
|||||||
|
|
||||||
if visiblePeerId != peerId || messageId != nil {
|
if visiblePeerId != peerId || messageId != nil {
|
||||||
if self.rootController.rootTabController != nil {
|
if self.rootController.rootTabController != nil {
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: .peer(peerId), subject: messageId.flatMap { .message(id: $0, highlight: true) }, activateInput: activateInput))
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: .peer(peerId), subject: messageId.flatMap { .message(id: $0, highlight: true, timecode: nil) }, activateInput: activateInput))
|
||||||
} else {
|
} else {
|
||||||
self.scheduledOpenChatWithPeerId = (peerId, messageId, activateInput)
|
self.scheduledOpenChatWithPeerId = (peerId, messageId, activateInput)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,13 +106,13 @@ private enum ChatRecordingActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum NavigateToMessageLocation {
|
public enum NavigateToMessageLocation {
|
||||||
case id(MessageId)
|
case id(MessageId, Double?)
|
||||||
case index(MessageIndex)
|
case index(MessageIndex)
|
||||||
case upperBound(PeerId)
|
case upperBound(PeerId)
|
||||||
|
|
||||||
var messageId: MessageId? {
|
var messageId: MessageId? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .id(id):
|
case let .id(id, _):
|
||||||
return id
|
return id
|
||||||
case let .index(index):
|
case let .index(index):
|
||||||
return index.id
|
return index.id
|
||||||
@ -123,7 +123,7 @@ public enum NavigateToMessageLocation {
|
|||||||
|
|
||||||
var peerId: PeerId {
|
var peerId: PeerId {
|
||||||
switch self {
|
switch self {
|
||||||
case let .id(id):
|
case let .id(id, _):
|
||||||
return id.peerId
|
return id.peerId
|
||||||
case let .index(index):
|
case let .index(index):
|
||||||
return index.id.peerId
|
return index.id.peerId
|
||||||
@ -571,7 +571,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
case .pinnedMessageUpdated:
|
case .pinnedMessageUpdated:
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? ReplyMessageAttribute {
|
if let attribute = attribute as? ReplyMessageAttribute {
|
||||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId))
|
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, nil))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -580,7 +580,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
case .gameScore:
|
case .gameScore:
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? ReplyMessageAttribute {
|
if let attribute = attribute as? ReplyMessageAttribute {
|
||||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId))
|
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, nil))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -996,9 +996,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}, openMessageContextActions: { message, node, rect, gesture in
|
}, openMessageContextActions: { message, node, rect, gesture in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, navigateToMessage: { [weak self] fromId, id in
|
}, navigateToMessage: { [weak self] fromId, id in
|
||||||
self?.navigateToMessage(from: fromId, to: .id(id), forceInCurrentChat: fromId.peerId == id.peerId)
|
self?.navigateToMessage(from: fromId, to: .id(id, nil), forceInCurrentChat: fromId.peerId == id.peerId)
|
||||||
}, navigateToMessageStandalone: { [weak self] id in
|
}, navigateToMessageStandalone: { [weak self] id in
|
||||||
self?.navigateToMessage(from: nil, to: .id(id), forceInCurrentChat: false)
|
self?.navigateToMessage(from: nil, to: .id(id, nil), forceInCurrentChat: false)
|
||||||
}, tapMessage: nil, clickThroughMessage: { [weak self] in
|
}, tapMessage: nil, clickThroughMessage: { [weak self] in
|
||||||
self?.chatDisplayNode.dismissInput()
|
self?.chatDisplayNode.dismissInput()
|
||||||
}, toggleMessagesSelection: { [weak self] ids, value in
|
}, toggleMessagesSelection: { [weak self] ids, value in
|
||||||
@ -1875,7 +1875,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
guard let message = message else {
|
guard let message = message else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let context = strongSelf.context
|
||||||
|
let chatPresentationInterfaceState = strongSelf.presentationInterfaceState
|
||||||
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||||
|
|
||||||
|
let isCopyLink: Bool
|
||||||
|
if message.id.namespace == Namespaces.Message.Cloud, let _ = message.peers[message.id.peerId] as? TelegramChannel, !(message.media.first is TelegramMediaAction) {
|
||||||
|
isCopyLink = true
|
||||||
|
} else {
|
||||||
|
isCopyLink = false
|
||||||
|
}
|
||||||
|
|
||||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||||
ActionSheetTextItem(title: text),
|
ActionSheetTextItem(title: text),
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
|
||||||
@ -1884,12 +1894,52 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.controllerInteraction?.seekToTimecode(message, timecode, true)
|
strongSelf.controllerInteraction?.seekToTimecode(message, timecode, true)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
|
ActionSheetButtonItem(title: isCopyLink ? strongSelf.presentationData.strings.Conversation_ContextMenuCopyLink : strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
|
if isCopyLink, let channel = message.peers[message.id.peerId] as? TelegramChannel {
|
||||||
|
var threadMessageId: MessageId?
|
||||||
|
|
||||||
|
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
|
||||||
|
threadMessageId = replyThreadMessage.messageId
|
||||||
|
}
|
||||||
|
let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id, isThread: threadMessageId != nil)
|
||||||
|
|> map { result -> String? in
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { link in
|
||||||
|
if let link = link {
|
||||||
|
UIPasteboard.general.string = link + "?t=\(Int32(timecode))"
|
||||||
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
var warnAboutPrivate = false
|
||||||
|
if case .peer = chatPresentationInterfaceState.chatLocation {
|
||||||
|
if channel.addressName == nil {
|
||||||
|
warnAboutPrivate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Queue.mainQueue().after(0.2, {
|
||||||
|
let content: UndoOverlayContent
|
||||||
|
if warnAboutPrivate {
|
||||||
|
content = .linkCopied(text: presentationData.strings.Conversation_PrivateMessageLinkCopiedLong)
|
||||||
|
} else {
|
||||||
|
content = .linkCopied(text: presentationData.strings.Conversation_LinkCopied)
|
||||||
|
}
|
||||||
|
self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
UIPasteboard.general.string = text
|
UIPasteboard.general.string = text
|
||||||
|
|
||||||
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_TextCopied)
|
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_TextCopied)
|
||||||
self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
UIPasteboard.general.string = text
|
||||||
|
|
||||||
|
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_TextCopied)
|
||||||
|
self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
]), ActionSheetItemGroup(items: [
|
]), ActionSheetItemGroup(items: [
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||||
@ -2641,7 +2691,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.openMessageReplies(messageId: threadMessageId, displayProgressInMessage: message.id, isChannelPost: true, atMessage: attribute.messageId, displayModalProgress: false)
|
strongSelf.openMessageReplies(messageId: threadMessageId, displayProgressInMessage: message.id, isChannelPost: true, atMessage: attribute.messageId, displayModalProgress: false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId))
|
strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId, nil))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -4535,7 +4585,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
self.chatDisplayNode.historyNode.scrolledToIndex = { [weak self] toIndex, initial in
|
self.chatDisplayNode.historyNode.scrolledToIndex = { [weak self] toIndex, initial in
|
||||||
if let strongSelf = self, case let .message(index) = toIndex {
|
if let strongSelf = self, case let .message(index) = toIndex {
|
||||||
if case let .message(messageId, _) = strongSelf.subject, initial, messageId != index.id {
|
if case let .message(messageId, _, _) = strongSelf.subject, initial, messageId != index.id {
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(text: strongSelf.presentationData.strings.Conversation_MessageDoesntExist), elevatedLayout: false, action: { _ in return true }), in: .current)
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(text: strongSelf.presentationData.strings.Conversation_MessageDoesntExist), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||||
} else if let controllerInteraction = strongSelf.controllerInteraction {
|
} else if let controllerInteraction = strongSelf.controllerInteraction {
|
||||||
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(index.id) {
|
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(index.id) {
|
||||||
@ -4552,6 +4602,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
if case let .message(_, _, maybeTimecode) = strongSelf.subject, let timecode = maybeTimecode, initial {
|
||||||
|
Queue.mainQueue().after(0.2) {
|
||||||
|
let _ = strongSelf.controllerInteraction?.openMessage(message, .timecode(timecode))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4836,7 +4892,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.chatDisplayNode.navigateButtons.downPressed = { [weak self] in
|
self.chatDisplayNode.navigateButtons.downPressed = { [weak self] in
|
||||||
if let strongSelf = self, strongSelf.isNodeLoaded {
|
if let strongSelf = self, strongSelf.isNodeLoaded {
|
||||||
if let messageId = strongSelf.historyNavigationStack.removeLast() {
|
if let messageId = strongSelf.historyNavigationStack.removeLast() {
|
||||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId.id), rememberInStack: false)
|
strongSelf.navigateToMessage(from: nil, to: .id(messageId.id, nil), rememberInStack: false)
|
||||||
} else {
|
} else {
|
||||||
if case .known = strongSelf.chatDisplayNode.historyNode.visibleContentOffset() {
|
if case .known = strongSelf.chatDisplayNode.historyNode.visibleContentOffset() {
|
||||||
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||||
@ -4859,7 +4915,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
switch result {
|
switch result {
|
||||||
case let .result(messageId):
|
case let .result(messageId):
|
||||||
if let messageId = messageId {
|
if let messageId = messageId {
|
||||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId))
|
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil))
|
||||||
}
|
}
|
||||||
case .loading:
|
case .loading:
|
||||||
break
|
break
|
||||||
@ -5422,7 +5478,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.loadingMessage.set(.single(nil))
|
strongSelf.loadingMessage.set(.single(nil))
|
||||||
if let messageId = messageId {
|
if let messageId = messageId {
|
||||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: true)
|
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil), forceInCurrentChat: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -5450,7 +5506,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||||
}
|
}
|
||||||
}, navigateToMessage: { [weak self] messageId, dropStack, forceInCurrentChat, statusSubject in
|
}, navigateToMessage: { [weak self] messageId, dropStack, forceInCurrentChat, statusSubject in
|
||||||
self?.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, statusSubject: statusSubject)
|
self?.navigateToMessage(from: nil, to: .id(messageId, nil), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, statusSubject: statusSubject)
|
||||||
}, navigateToChat: { [weak self] peerId in
|
}, navigateToChat: { [weak self] peerId in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -6598,7 +6654,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let navigationController = strongSelf.effectiveNavigationController {
|
if let navigationController = strongSelf.effectiveNavigationController {
|
||||||
let subject: ChatControllerSubject? = sourceMessageId.flatMap { ChatControllerSubject.message(id: $0, highlight: true) }
|
let subject: ChatControllerSubject? = sourceMessageId.flatMap { ChatControllerSubject.message(id: $0, highlight: true, timecode: nil) }
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadResult), subject: subject, keepStack: .always))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadResult), subject: subject, keepStack: .always))
|
||||||
}
|
}
|
||||||
}, activatePinnedListPreview: { [weak self] node, gesture in
|
}, activatePinnedListPreview: { [weak self] node, gesture in
|
||||||
@ -7061,7 +7117,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
if let currentItem = currentItem?.id as? PeerMessagesMediaPlaylistItemId, let previousItem = previousItem?.id as? PeerMessagesMediaPlaylistItemId, previousItem.messageId.peerId == peerId, currentItem.messageId.peerId == peerId, currentItem.messageId != previousItem.messageId {
|
if let currentItem = currentItem?.id as? PeerMessagesMediaPlaylistItemId, let previousItem = previousItem?.id as? PeerMessagesMediaPlaylistItemId, previousItem.messageId.peerId == peerId, currentItem.messageId.peerId == peerId, currentItem.messageId != previousItem.messageId {
|
||||||
if strongSelf.chatDisplayNode.historyNode.isMessageVisibleOnScreen(currentItem.messageId) {
|
if strongSelf.chatDisplayNode.historyNode.isMessageVisibleOnScreen(currentItem.messageId) {
|
||||||
strongSelf.navigateToMessage(from: nil, to: .id(currentItem.messageId), scrollPosition: .center(.bottom), rememberInStack: false, animated: true, completion: nil)
|
strongSelf.navigateToMessage(from: nil, to: .id(currentItem.messageId, nil), scrollPosition: .center(.bottom), rememberInStack: false, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -10537,9 +10593,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
let subject: ChatControllerSubject?
|
let subject: ChatControllerSubject?
|
||||||
if let atMessageId = atMessageId {
|
if let atMessageId = atMessageId {
|
||||||
subject = .message(id: atMessageId, highlight: true)
|
subject = .message(id: atMessageId, highlight: true, timecode: nil)
|
||||||
} else if let index = result.scrollToLowerBoundMessage {
|
} else if let index = result.scrollToLowerBoundMessage {
|
||||||
subject = .message(id: index.id, highlight: false)
|
subject = .message(id: index.id, highlight: false, timecode: nil)
|
||||||
} else {
|
} else {
|
||||||
subject = nil
|
subject = nil
|
||||||
}
|
}
|
||||||
@ -10603,11 +10659,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if isPinnedMessages, let messageId = messageLocation.messageId {
|
if isPinnedMessages, let messageId = messageLocation.messageId {
|
||||||
if let navigationController = self.effectiveNavigationController {
|
if let navigationController = self.effectiveNavigationController {
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true), keepStack: .always))
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true, timecode: nil), keepStack: .always))
|
||||||
}
|
}
|
||||||
} else if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) {
|
} else if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) {
|
||||||
if let navigationController = self.effectiveNavigationController {
|
if let navigationController = self.effectiveNavigationController {
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true), keepStack: .always))
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true, timecode: nil), keepStack: .always))
|
||||||
}
|
}
|
||||||
} else if forceInCurrentChat {
|
} else if forceInCurrentChat {
|
||||||
if let _ = fromId, let fromIndex = fromIndex, rememberInStack {
|
if let _ = fromId, let fromIndex = fromIndex, rememberInStack {
|
||||||
@ -10629,13 +10685,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.messageIndexDisposable.set(nil)
|
self.messageIndexDisposable.set(nil)
|
||||||
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition)
|
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition)
|
||||||
completion?()
|
completion?()
|
||||||
|
|
||||||
|
if case let .id(_, maybeTimecode) = messageLocation, let timecode = maybeTimecode {
|
||||||
|
let _ = self.controllerInteraction?.openMessage(message, .timecode(timecode))
|
||||||
|
}
|
||||||
} else if case let .index(index) = messageLocation, index.id.id == 0, index.timestamp > 0, case .scheduledMessages = self.presentationInterfaceState.subject {
|
} else if case let .index(index) = messageLocation, index.id.id == 0, index.timestamp > 0, case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||||
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition)
|
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition)
|
||||||
} else {
|
} else {
|
||||||
self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue()))
|
self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue()))
|
||||||
let searchLocation: ChatHistoryInitialSearchLocation
|
let searchLocation: ChatHistoryInitialSearchLocation
|
||||||
switch messageLocation {
|
switch messageLocation {
|
||||||
case let .id(id):
|
case let .id(id, _):
|
||||||
searchLocation = .id(id)
|
searchLocation = .id(id)
|
||||||
case let .index(index):
|
case let .index(index):
|
||||||
searchLocation = .index(index)
|
searchLocation = .index(index)
|
||||||
@ -10728,7 +10788,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let fromIndex = fromIndex {
|
if let fromIndex = fromIndex {
|
||||||
let searchLocation: ChatHistoryInitialSearchLocation
|
let searchLocation: ChatHistoryInitialSearchLocation
|
||||||
switch messageLocation {
|
switch messageLocation {
|
||||||
case let .id(id):
|
case let .id(id, _):
|
||||||
searchLocation = .id(id)
|
searchLocation = .id(id)
|
||||||
case let .index(index):
|
case let .index(index):
|
||||||
searchLocation = .index(index)
|
searchLocation = .index(index)
|
||||||
@ -10762,7 +10822,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
completion?()
|
completion?()
|
||||||
} else {
|
} else {
|
||||||
if let navigationController = strongSelf.effectiveNavigationController {
|
if let navigationController = strongSelf.effectiveNavigationController {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message(id: $0, highlight: true) }))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message(id: $0, highlight: true, timecode: nil) }))
|
||||||
}
|
}
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
@ -10774,7 +10834,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
if let navigationController = self.effectiveNavigationController {
|
if let navigationController = self.effectiveNavigationController {
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message(id: $0, highlight: true) }))
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message(id: $0, highlight: true, timecode: nil) }))
|
||||||
}
|
}
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
@ -11532,8 +11592,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
switch navigation {
|
switch navigation {
|
||||||
case let .chat(_, subject, peekData):
|
case let .chat(_, subject, peekData):
|
||||||
if case .peer(peerId) = strongSelf.chatLocation {
|
if case .peer(peerId) = strongSelf.chatLocation {
|
||||||
if let subject = subject, case let .message(messageId, _) = subject {
|
if let subject = subject, case let .message(messageId, _, timecode) = subject {
|
||||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId))
|
strongSelf.navigateToMessage(from: nil, to: .id(messageId, timecode))
|
||||||
}
|
}
|
||||||
} else if let navigationController = strongSelf.effectiveNavigationController {
|
} else if let navigationController = strongSelf.effectiveNavigationController {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, keepStack: .always, peekData: peekData))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, keepStack: .always, peekData: peekData))
|
||||||
|
|||||||
@ -1248,7 +1248,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
let previousInputPanelBackgroundFrame = self.inputPanelBackgroundNode.frame
|
let previousInputPanelBackgroundFrame = self.inputPanelBackgroundNode.frame
|
||||||
transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame)
|
transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame)
|
||||||
self.inputPanelBackgroundNode.update(size: CGSize(width: apparentInputBackgroundFrame.size.width, height: apparentInputBackgroundFrame.size.height + 41.0), transition: transition)
|
self.inputPanelBackgroundNode.update(size: CGSize(width: apparentInputBackgroundFrame.size.width, height: apparentInputBackgroundFrame.size.height + 41.0 + 31.0), transition: transition)
|
||||||
transition.updateFrame(node: self.inputPanelBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: CGSize(width: apparentInputBackgroundFrame.size.width, height: UIScreenPixel)))
|
transition.updateFrame(node: self.inputPanelBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: CGSize(width: apparentInputBackgroundFrame.size.width, height: UIScreenPixel)))
|
||||||
transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame)
|
transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame)
|
||||||
|
|
||||||
|
|||||||
@ -910,7 +910,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
|
|
||||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount, highlight: false), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount, highlight: false), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||||
} else {
|
} else {
|
||||||
if let subject = subject, case let .message(messageId, highlight) = subject {
|
if let subject = subject, case let .message(messageId, highlight, _) = subject {
|
||||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: highlight), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: highlight), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||||
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
||||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: true), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: true), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||||
@ -1088,7 +1088,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if let subject = subject, case let .message(messageId, highlight) = subject {
|
if let subject = subject, case let .message(messageId, highlight, _) = subject {
|
||||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: highlight), id: 0)
|
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: highlight), id: 0)
|
||||||
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
||||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: true), id: 0)
|
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: true), id: 0)
|
||||||
@ -1193,7 +1193,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
}).start()
|
}).start()
|
||||||
|
|
||||||
self.beganInteractiveDragging = { [weak self] in
|
self.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.isInteractivelyScrollingValue = true
|
self?.isInteractivelyScrollingValue = true
|
||||||
self?.isInteractivelyScrollingPromise.set(true)
|
self?.isInteractivelyScrollingPromise.set(true)
|
||||||
self?.beganDragging?()
|
self?.beganDragging?()
|
||||||
|
|||||||
@ -231,7 +231,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,17 +21,19 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
|
|||||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||||
let type: ChatMediaInputMetaSectionItemType
|
let type: ChatMediaInputMetaSectionItemType
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
let expanded: Bool
|
||||||
let selectedItem: () -> Void
|
let selectedItem: () -> Void
|
||||||
|
|
||||||
var selectable: Bool {
|
var selectable: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(inputNodeInteraction: ChatMediaInputNodeInteraction, type: ChatMediaInputMetaSectionItemType, theme: PresentationTheme, selected: @escaping () -> Void) {
|
init(inputNodeInteraction: ChatMediaInputNodeInteraction, type: ChatMediaInputMetaSectionItemType, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) {
|
||||||
self.inputNodeInteraction = inputNodeInteraction
|
self.inputNodeInteraction = inputNodeInteraction
|
||||||
self.type = type
|
self.type = type
|
||||||
self.selectedItem = selected
|
self.selectedItem = selected
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.expanded = expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
@ -40,11 +42,11 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
|
|||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
node.inputNodeInteraction = self.inputNodeInteraction
|
node.inputNodeInteraction = self.inputNodeInteraction
|
||||||
node.setItem(item: self)
|
node.setItem(item: self)
|
||||||
node.updateTheme(theme: self.theme)
|
node.updateTheme(theme: self.theme, expanded: self.expanded)
|
||||||
node.updateIsHighlighted()
|
node.updateIsHighlighted()
|
||||||
node.updateAppearanceTransition(transition: .immediate)
|
node.updateAppearanceTransition(transition: .immediate)
|
||||||
|
|
||||||
node.contentSize = CGSize(width: 41.0, height: 41.0)
|
node.contentSize = self.expanded ? expandedBoundingSize : boundingSize
|
||||||
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||||
|
|
||||||
completion(node, {
|
completion(node, {
|
||||||
@ -58,9 +60,9 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
|
|||||||
|
|
||||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: node().insets), { _ in
|
completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: node().insets), { _ in
|
||||||
(node() as? ChatMediaInputMetaSectionItemNode)?.setItem(item: self)
|
(node() as? ChatMediaInputMetaSectionItemNode)?.setItem(item: self)
|
||||||
(node() as? ChatMediaInputMetaSectionItemNode)?.updateTheme(theme: self.theme)
|
(node() as? ChatMediaInputMetaSectionItemNode)?.updateTheme(theme: self.theme, expanded: self.expanded)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,16 +72,22 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let boundingSize = CGSize(width: 41.0, height: 41.0)
|
private let boundingSize = CGSize(width: 72.0, height: 41.0)
|
||||||
private let boundingImageSize = CGSize(width: 30.0, height: 30.0)
|
private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0)
|
||||||
private let highlightSize = CGSize(width: 35.0, height: 35.0)
|
private let boundingImageScale: CGFloat = 0.625
|
||||||
|
private let highlightSize = CGSize(width: 56.0, height: 56.0)
|
||||||
private let verticalOffset: CGFloat = 3.0 + UIScreenPixel
|
private let verticalOffset: CGFloat = 3.0 + UIScreenPixel
|
||||||
|
|
||||||
final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
|
final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
|
||||||
|
private let containerNode: ASDisplayNode
|
||||||
|
private let scalingNode: ASDisplayNode
|
||||||
private let imageNode: ASImageNode
|
private let imageNode: ASImageNode
|
||||||
private let textNodeContainer: ASDisplayNode
|
private let textNodeContainer: ASDisplayNode
|
||||||
private let textNode: ImmediateTextNode
|
private let textNode: ImmediateTextNode
|
||||||
private let highlightNode: ASImageNode
|
private let highlightNode: ASImageNode
|
||||||
|
private let titleNode: ImmediateTextNode
|
||||||
|
|
||||||
|
private var currentExpanded = false
|
||||||
|
|
||||||
var item: ChatMediaInputMetaSectionItem?
|
var item: ChatMediaInputMetaSectionItem?
|
||||||
var currentCollectionId: ItemCollectionId?
|
var currentCollectionId: ItemCollectionId?
|
||||||
@ -88,6 +96,11 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
|
|||||||
var theme: PresentationTheme?
|
var theme: PresentationTheme?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self.containerNode = ASDisplayNode()
|
||||||
|
self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
|
||||||
|
self.scalingNode = ASDisplayNode()
|
||||||
|
|
||||||
self.highlightNode = ASImageNode()
|
self.highlightNode = ASImageNode()
|
||||||
self.highlightNode.isLayerBacked = true
|
self.highlightNode.isLayerBacked = true
|
||||||
self.highlightNode.isHidden = true
|
self.highlightNode.isHidden = true
|
||||||
@ -105,22 +118,17 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
|
|||||||
self.textNodeContainer.addSubnode(self.textNode)
|
self.textNodeContainer.addSubnode(self.textNode)
|
||||||
self.textNodeContainer.isUserInteractionEnabled = false
|
self.textNodeContainer.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - highlightSize.height) / 2.0)), size: highlightSize)
|
self.titleNode = ImmediateTextNode()
|
||||||
|
|
||||||
self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
|
||||||
|
|
||||||
self.textNodeContainer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.highlightNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.addSubnode(self.imageNode)
|
self.containerNode.addSubnode(self.scalingNode)
|
||||||
self.addSubnode(self.textNodeContainer)
|
|
||||||
|
|
||||||
let imageSize = CGSize(width: 26.0, height: 26.0)
|
self.scalingNode.addSubnode(self.highlightNode)
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
self.scalingNode.addSubnode(self.titleNode)
|
||||||
|
self.scalingNode.addSubnode(self.imageNode)
|
||||||
self.textNodeContainer.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + 1.0), size: imageSize)
|
self.scalingNode.addSubnode(self.textNodeContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
@ -139,25 +147,60 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTheme(theme: PresentationTheme) {
|
func updateTheme(theme: PresentationTheme, expanded: Bool) {
|
||||||
|
let imageSize = CGSize(width: 26.0 * 1.6, height: 26.0 * 1.6)
|
||||||
|
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((expandedBoundingSize.width - imageSize.width) / 2.0), y: floor((expandedBoundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
||||||
|
|
||||||
|
self.textNodeContainer.frame = CGRect(origin: CGPoint(x: floor((expandedBoundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((expandedBoundingSize.height - imageSize.height) / 2.0) + 1.0), size: imageSize)
|
||||||
|
|
||||||
if self.theme !== theme {
|
if self.theme !== theme {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme)
|
self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme)
|
||||||
|
var title = ""
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
switch item.type {
|
switch item.type {
|
||||||
case .savedStickers:
|
case .savedStickers:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelSavedStickersIcon(theme)
|
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelSavedStickersIcon(theme)
|
||||||
|
title = "Favorites"
|
||||||
case .recentStickers:
|
case .recentStickers:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentStickersIcon(theme)
|
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentStickersIcon(theme)
|
||||||
|
title = "Recent"
|
||||||
case .stickersMode:
|
case .stickersMode:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelStickersModeIcon(theme)
|
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelStickersModeIcon(theme)
|
||||||
|
title = "Stickers"
|
||||||
case .savedGifs:
|
case .savedGifs:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentStickersIcon(theme)
|
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentStickersIcon(theme)
|
||||||
|
title = "GIFs"
|
||||||
case .trendingGifs:
|
case .trendingGifs:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingGifsIcon(theme)
|
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingGifsIcon(theme)
|
||||||
|
title = "Trending"
|
||||||
case let .gifEmoji(emoji):
|
case let .gifEmoji(emoji):
|
||||||
var emoji = emoji
|
var emoji = emoji
|
||||||
|
switch emoji {
|
||||||
|
case "😡":
|
||||||
|
title = "Angry"
|
||||||
|
case "😮":
|
||||||
|
title = "Surprised"
|
||||||
|
case "😂":
|
||||||
|
title = "Joy"
|
||||||
|
case "😘":
|
||||||
|
title = "Kiss"
|
||||||
|
case "😍":
|
||||||
|
title = "Hearts"
|
||||||
|
case "👍":
|
||||||
|
title = "Thumbs Up"
|
||||||
|
case "👎":
|
||||||
|
title = "Thumbs Down"
|
||||||
|
case "🙄":
|
||||||
|
title = "Roll-eyes"
|
||||||
|
case "😎":
|
||||||
|
title = "Cool"
|
||||||
|
case "🥳":
|
||||||
|
title = "Party"
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
if emoji == "🥳" {
|
if emoji == "🥳" {
|
||||||
if #available(iOSApplicationExtension 12.1, iOS 12.1, *) {
|
if #available(iOSApplicationExtension 12.1, iOS 12.1, *) {
|
||||||
} else {
|
} else {
|
||||||
@ -165,12 +208,34 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.imageNode.image = nil
|
self.imageNode.image = nil
|
||||||
self.textNode.attributedText = NSAttributedString(string: emoji, font: Font.regular(27.0), textColor: .black)
|
self.textNode.attributedText = NSAttributedString(string: emoji, font: Font.regular(43.0), textColor: .black)
|
||||||
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||||
self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.textNodeContainer.bounds.width - textSize.width) / 2.0), y: floor((self.textNodeContainer.bounds.height - textSize.height) / 2.0)), size: textSize)
|
self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.textNodeContainer.bounds.width - textSize.width) / 2.0), y: floor((self.textNodeContainer.bounds.height - textSize.height) / 2.0)), size: textSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize)
|
||||||
|
self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize)
|
||||||
|
|
||||||
|
let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height)
|
||||||
|
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||||
|
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||||
|
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||||
|
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||||
|
|
||||||
|
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||||
|
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||||
|
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||||
|
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||||
|
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||||
|
expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001)
|
||||||
|
|
||||||
|
self.currentExpanded = expanded
|
||||||
|
|
||||||
|
expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.imageNode.position.x - highlightSize.width / 2.0, y: self.imageNode.position.y - highlightSize.height / 2.0), size: highlightSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateIsHighlighted() {
|
func updateIsHighlighted() {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ struct ChatMediaInputPanelTransition {
|
|||||||
let deletions: [ListViewDeleteItem]
|
let deletions: [ListViewDeleteItem]
|
||||||
let insertions: [ListViewInsertItem]
|
let insertions: [ListViewInsertItem]
|
||||||
let updates: [ListViewUpdateItem]
|
let updates: [ListViewUpdateItem]
|
||||||
|
let scrollToItem: ListViewScrollToItem?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ChatMediaInputGridTransition {
|
struct ChatMediaInputGridTransition {
|
||||||
@ -47,14 +48,14 @@ struct ChatMediaInputGridTransition {
|
|||||||
let animated: Bool
|
let animated: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func preparedChatMediaInputPanelEntryTransition(context: AccountContext, from fromEntries: [ChatMediaInputPanelEntry], to toEntries: [ChatMediaInputPanelEntry], inputNodeInteraction: ChatMediaInputNodeInteraction) -> ChatMediaInputPanelTransition {
|
func preparedChatMediaInputPanelEntryTransition(context: AccountContext, from fromEntries: [ChatMediaInputPanelEntry], to toEntries: [ChatMediaInputPanelEntry], inputNodeInteraction: ChatMediaInputNodeInteraction, scrollToItem: ListViewScrollToItem?) -> ChatMediaInputPanelTransition {
|
||||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, inputNodeInteraction: inputNodeInteraction), directionHint: nil) }
|
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, inputNodeInteraction: inputNodeInteraction), directionHint: nil) }
|
||||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, inputNodeInteraction: inputNodeInteraction), directionHint: nil) }
|
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, inputNodeInteraction: inputNodeInteraction), directionHint: nil) }
|
||||||
|
|
||||||
return ChatMediaInputPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
|
return ChatMediaInputPanelTransition(deletions: deletions, insertions: insertions, updates: updates, scrollToItem: scrollToItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemCollectionsView, from fromEntries: [ChatMediaInputGridEntry], to toEntries: [ChatMediaInputGridEntry], update: StickerPacksCollectionUpdate, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingInteraction: TrendingPaneInteraction) -> ChatMediaInputGridTransition {
|
func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemCollectionsView, from fromEntries: [ChatMediaInputGridEntry], to toEntries: [ChatMediaInputGridEntry], update: StickerPacksCollectionUpdate, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingInteraction: TrendingPaneInteraction) -> ChatMediaInputGridTransition {
|
||||||
@ -152,16 +153,16 @@ func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemColle
|
|||||||
return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated)
|
return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasUnreadTrending: Bool?, theme: PresentationTheme, hasGifs: Bool = true, hasSettings: Bool = true) -> [ChatMediaInputPanelEntry] {
|
func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasUnreadTrending: Bool?, theme: PresentationTheme, hasGifs: Bool = true, hasSettings: Bool = true, expanded: Bool = false) -> [ChatMediaInputPanelEntry] {
|
||||||
var entries: [ChatMediaInputPanelEntry] = []
|
var entries: [ChatMediaInputPanelEntry] = []
|
||||||
if hasGifs {
|
if hasGifs {
|
||||||
entries.append(.recentGifs(theme))
|
entries.append(.recentGifs(theme, expanded))
|
||||||
}
|
}
|
||||||
if let hasUnreadTrending = hasUnreadTrending {
|
if let hasUnreadTrending = hasUnreadTrending {
|
||||||
entries.append(.trending(hasUnreadTrending, theme))
|
entries.append(.trending(hasUnreadTrending, theme, expanded))
|
||||||
}
|
}
|
||||||
if let savedStickers = savedStickers, !savedStickers.items.isEmpty {
|
if let savedStickers = savedStickers, !savedStickers.items.isEmpty {
|
||||||
entries.append(.savedStickers(theme))
|
entries.append(.savedStickers(theme, expanded))
|
||||||
}
|
}
|
||||||
var savedStickerIds = Set<Int64>()
|
var savedStickerIds = Set<Int64>()
|
||||||
if let savedStickers = savedStickers, !savedStickers.items.isEmpty {
|
if let savedStickers = savedStickers, !savedStickers.items.isEmpty {
|
||||||
@ -182,40 +183,40 @@ func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: Ordere
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
entries.append(.recentPacks(theme))
|
entries.append(.recentPacks(theme, expanded))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let peerSpecificPack = peerSpecificPack {
|
if let peerSpecificPack = peerSpecificPack {
|
||||||
entries.append(.peerSpecific(theme: theme, peer: peerSpecificPack.peer))
|
entries.append(.peerSpecific(theme: theme, peer: peerSpecificPack.peer, expanded: expanded))
|
||||||
} else if case let .available(peer, false) = canInstallPeerSpecificPack {
|
} else if case let .available(peer, false) = canInstallPeerSpecificPack {
|
||||||
entries.append(.peerSpecific(theme: theme, peer: peer))
|
entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded))
|
||||||
}
|
}
|
||||||
var index = 0
|
var index = 0
|
||||||
for (_, info, item) in view.collectionInfos {
|
for (_, info, item) in view.collectionInfos {
|
||||||
if let info = info as? StickerPackCollectionInfo, item != nil {
|
if let info = info as? StickerPackCollectionInfo, item != nil {
|
||||||
entries.append(.stickerPack(index: index, info: info, topItem: item as? StickerPackItem, theme: theme))
|
entries.append(.stickerPack(index: index, info: info, topItem: item as? StickerPackItem, theme: theme, expanded: expanded))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if peerSpecificPack == nil, case let .available(peer, true) = canInstallPeerSpecificPack {
|
if peerSpecificPack == nil, case let .available(peer, true) = canInstallPeerSpecificPack {
|
||||||
entries.append(.peerSpecific(theme: theme, peer: peer))
|
entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded))
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasSettings {
|
if hasSettings {
|
||||||
entries.append(.settings(theme))
|
entries.append(.settings(theme, expanded))
|
||||||
}
|
}
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, reactions: [String]) -> [ChatMediaInputPanelEntry] {
|
func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, reactions: [String], expanded: Bool) -> [ChatMediaInputPanelEntry] {
|
||||||
var entries: [ChatMediaInputPanelEntry] = []
|
var entries: [ChatMediaInputPanelEntry] = []
|
||||||
entries.append(.stickersMode(theme))
|
entries.append(.stickersMode(theme, expanded))
|
||||||
entries.append(.savedGifs(theme))
|
entries.append(.savedGifs(theme, expanded))
|
||||||
entries.append(.trendingGifs(theme))
|
entries.append(.trendingGifs(theme, expanded))
|
||||||
|
|
||||||
for reaction in reactions {
|
for reaction in reactions {
|
||||||
entries.append(.gifEmotion(entries.count, theme, reaction))
|
entries.append(.gifEmotion(entries.count, theme, reaction, expanded))
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
@ -451,6 +452,15 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
private var currentView: ItemCollectionsView?
|
private var currentView: ItemCollectionsView?
|
||||||
private let dismissedPeerSpecificStickerPack = Promise<Bool>()
|
private let dismissedPeerSpecificStickerPack = Promise<Bool>()
|
||||||
|
|
||||||
|
private var panelCollapseScrollToIndex: Int?
|
||||||
|
private let panelExpandedPromise = ValuePromise<Bool>(false)
|
||||||
|
private var panelExpanded: Bool = false {
|
||||||
|
didSet {
|
||||||
|
self.panelExpandedPromise.set(self.panelExpanded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var panelCollapseTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
var requestDisableStickerAnimations: ((Bool) -> Void)?
|
var requestDisableStickerAnimations: ((Bool) -> Void)?
|
||||||
|
|
||||||
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, DeviceMetrics, Bool)?
|
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, DeviceMetrics, Bool)?
|
||||||
@ -495,6 +505,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
self.collectionListContainer.clipsToBounds = true
|
self.collectionListContainer.clipsToBounds = true
|
||||||
|
|
||||||
self.listView = ListView()
|
self.listView = ListView()
|
||||||
|
// self.listView.clipsToBounds = false
|
||||||
self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
||||||
self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false
|
self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false
|
||||||
self.listView.accessibilityPageScrolledString = { row, count in
|
self.listView.accessibilityPageScrolledString = { row, count in
|
||||||
@ -502,6 +513,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.gifListView = ListView()
|
self.gifListView = ListView()
|
||||||
|
// self.gifListView.clipsToBounds = false
|
||||||
self.gifListView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
self.gifListView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
||||||
self.gifListView.scroller.panGestureRecognizer.cancelsTouchesInView = false
|
self.gifListView.scroller.panGestureRecognizer.cancelsTouchesInView = false
|
||||||
self.gifListView.accessibilityPageScrolledString = { row, count in
|
self.gifListView.accessibilityPageScrolledString = { row, count in
|
||||||
@ -527,7 +539,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
|
|
||||||
var getItemIsPreviewedImpl: ((StickerPackItem) -> Bool)?
|
var getItemIsPreviewedImpl: ((StickerPackItem) -> Bool)?
|
||||||
|
|
||||||
self.paneArrangement = ChatMediaInputPaneArrangement(panes: [.gifs, .stickers, /*.trending*/], currentIndex: 1, indexTransition: 0.0)
|
self.paneArrangement = ChatMediaInputPaneArrangement(panes: [.gifs, .stickers], currentIndex: 1, indexTransition: 0.0)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -548,7 +560,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
//strongSelf.setCurrentPane(.trending, transition: .animated(duration: 0.25, curve: .spring))
|
|
||||||
} else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue {
|
} else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue {
|
||||||
strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace)
|
strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace)
|
||||||
strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId)
|
strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId)
|
||||||
@ -781,7 +792,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let trendingInteraction = TrendingPaneInteraction(installPack: { [weak self] info in
|
let trendingInteraction = TrendingPaneInteraction(installPack: { [weak self] info in
|
||||||
guard let strongSelf = self, let info = info as? StickerPackCollectionInfo else {
|
guard let info = info as? StickerPackCollectionInfo else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false)
|
let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false)
|
||||||
@ -846,8 +857,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
|
|
||||||
let previousView = Atomic<ItemCollectionsView?>(value: nil)
|
let previousView = Atomic<ItemCollectionsView?>(value: nil)
|
||||||
let transitionQueue = Queue()
|
let transitionQueue = Queue()
|
||||||
let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions)
|
let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions, self.panelExpandedPromise.get())
|
||||||
|> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
|
|> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions, panelExpanded -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
|
||||||
let (view, viewUpdate) = viewAndUpdate
|
let (view, viewUpdate) = viewAndUpdate
|
||||||
let previous = previousView.swap(view)
|
let previous = previousView.swap(view)
|
||||||
var update = viewUpdate
|
var update = viewUpdate
|
||||||
@ -882,8 +893,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, hasUnreadTrending: hasUnreadTrending, theme: theme)
|
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, hasUnreadTrending: hasUnreadTrending, theme: theme, expanded: panelExpanded)
|
||||||
let gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, reactions: reactions)
|
let gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, reactions: reactions, expanded: panelExpanded)
|
||||||
var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, strings: strings, theme: theme)
|
var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, strings: strings, theme: theme)
|
||||||
|
|
||||||
if view.higher == nil {
|
if view.higher == nil {
|
||||||
@ -903,7 +914,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (previousPanelEntries, previousGifPaneEntries, previousGridEntries) = previousEntries.swap((panelEntries, gifPaneEntries, gridEntries))
|
let (previousPanelEntries, previousGifPaneEntries, previousGridEntries) = previousEntries.swap((panelEntries, gifPaneEntries, gridEntries))
|
||||||
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction), preparedChatMediaInputPanelEntryTransition(context: context, from: previousGifPaneEntries, to: gifPaneEntries, inputNodeInteraction: inputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction, scrollToItem: nil), preparedChatMediaInputPanelEntryTransition(context: context, from: previousGifPaneEntries, to: gifPaneEntries, inputNodeInteraction: inputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.disposable.set((transitions
|
self.disposable.set((transitions
|
||||||
@ -970,7 +981,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
|
|
||||||
self.stickerPane.inputNodeInteraction = self.inputNodeInteraction
|
self.stickerPane.inputNodeInteraction = self.inputNodeInteraction
|
||||||
self.gifPane.inputNodeInteraction = self.inputNodeInteraction
|
self.gifPane.inputNodeInteraction = self.inputNodeInteraction
|
||||||
//self.trendingPane.inputNodeInteraction = self.inputNodeInteraction
|
|
||||||
|
|
||||||
paneDidScrollImpl = { [weak self] pane, state, transition in
|
paneDidScrollImpl = { [weak self] pane, state, transition in
|
||||||
self?.updatePaneDidScroll(pane: pane, state: state, transition: transition)
|
self?.updatePaneDidScroll(pane: pane, state: state, transition: transition)
|
||||||
@ -983,11 +993,59 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
openGifContextMenuImpl = { [weak self] file, sourceNode, sourceRect, gesture, isSaved in
|
openGifContextMenuImpl = { [weak self] file, sourceNode, sourceRect, gesture, isSaved in
|
||||||
self?.openGifContextMenu(file: file, sourceNode: sourceNode, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved)
|
self?.openGifContextMenu(file: file, sourceNode: sourceNode, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.listView.beganInteractiveDragging = { [weak self] position in
|
||||||
|
if let strongSelf = self, false {
|
||||||
|
if !strongSelf.panelExpanded, let index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) {
|
||||||
|
strongSelf.panelCollapseScrollToIndex = index
|
||||||
|
}
|
||||||
|
strongSelf.updateIsExpanded(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.listView.didEndScrolling = { [weak self] in
|
||||||
|
if let strongSelf = self, false {
|
||||||
|
strongSelf.setupCollapseTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gifListView.beganInteractiveDragging = { [weak self] position in
|
||||||
|
if let strongSelf = self, false {
|
||||||
|
if !strongSelf.panelExpanded, let index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) {
|
||||||
|
strongSelf.panelCollapseScrollToIndex = index
|
||||||
|
}
|
||||||
|
strongSelf.updateIsExpanded(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gifListView.didEndScrolling = { [weak self] in
|
||||||
|
if let strongSelf = self, false {
|
||||||
|
strongSelf.setupCollapseTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.disposable.dispose()
|
self.disposable.dispose()
|
||||||
self.searchContainerNodeLoadedDisposable.dispose()
|
self.searchContainerNodeLoadedDisposable.dispose()
|
||||||
|
self.panelCollapseTimer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateIsExpanded(_ isExpanded: Bool) {
|
||||||
|
self.panelCollapseTimer?.invalidate()
|
||||||
|
|
||||||
|
self.panelExpanded = isExpanded
|
||||||
|
self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: self.currentCollectionListPanelOffset(), transition: .animated(duration: 0.3, curve: .spring))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupCollapseTimer() {
|
||||||
|
self.panelCollapseTimer?.invalidate()
|
||||||
|
|
||||||
|
let timer = SwiftSignalKit.Timer(timeout: 1.5, repeat: false, completion: { [weak self] in
|
||||||
|
self?.updateIsExpanded(false)
|
||||||
|
}, queue: Queue.mainQueue())
|
||||||
|
self.panelCollapseTimer = timer
|
||||||
|
timer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openGifContextMenu(file: MultiplexedVideoNodeFile, sourceNode: ASDisplayNode, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) {
|
private func openGifContextMenu(file: MultiplexedVideoNodeFile, sourceNode: ASDisplayNode, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) {
|
||||||
@ -1215,7 +1273,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panes = [strongSelf.gifPane, strongSelf.stickerPane/*, strongSelf.trendingPane*/]
|
panes = [strongSelf.gifPane, strongSelf.stickerPane]
|
||||||
}
|
}
|
||||||
let panelPoint = strongSelf.view.convert(point, to: strongSelf.collectionListPanel.view)
|
let panelPoint = strongSelf.view.convert(point, to: strongSelf.collectionListPanel.view)
|
||||||
if panelPoint.y < strongSelf.collectionListPanel.frame.maxY {
|
if panelPoint.y < strongSelf.collectionListPanel.frame.maxY {
|
||||||
@ -1367,18 +1425,10 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setCurrentPane(_ pane: ChatMediaInputPaneType, transition: ContainedViewLayoutTransition, collectionIdHint: Int32? = nil) {
|
private func setCurrentPane(_ pane: ChatMediaInputPaneType, transition: ContainedViewLayoutTransition, collectionIdHint: Int32? = nil) {
|
||||||
var transition = transition
|
|
||||||
|
|
||||||
if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex {
|
if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex {
|
||||||
let previousGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs
|
let previousGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs
|
||||||
//let previousTrendingPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending
|
|
||||||
self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index)
|
self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index)
|
||||||
let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs
|
let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs
|
||||||
//let updatedTrendingPanelIsActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending
|
|
||||||
|
|
||||||
/*if updatedTrendingPanelIsActive != previousTrendingPanelWasActive {
|
|
||||||
transition = .immediate
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
|
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
|
||||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: transition, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: transition, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
||||||
@ -1396,23 +1446,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
} else if let collectionIdHint = collectionIdHint {
|
} else if let collectionIdHint = collectionIdHint {
|
||||||
self.setHighlightedItemCollectionId(ItemCollectionId(namespace: collectionIdHint, id: 0))
|
self.setHighlightedItemCollectionId(ItemCollectionId(namespace: collectionIdHint, id: 0))
|
||||||
}
|
}
|
||||||
/*case .trending:
|
|
||||||
self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0))*/
|
|
||||||
}
|
}
|
||||||
/*if updatedTrendingPanelIsActive != previousTrendingPanelWasActive {
|
|
||||||
self.controllerInteraction.updateInputMode { current in
|
|
||||||
switch current {
|
|
||||||
case let .media(mode, _):
|
|
||||||
if updatedTrendingPanelIsActive {
|
|
||||||
return .media(mode: mode, expanded: .content)
|
|
||||||
} else {
|
|
||||||
return .media(mode: mode, expanded: nil)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
} else {
|
} else {
|
||||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
|
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
|
||||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
||||||
@ -1425,10 +1459,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs {
|
if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs {
|
||||||
self.inputNodeInteraction.highlightedItemCollectionId = collectionId
|
self.inputNodeInteraction.highlightedItemCollectionId = collectionId
|
||||||
}
|
}
|
||||||
} else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue {
|
|
||||||
/*if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending {
|
|
||||||
self.inputNodeInteraction.highlightedItemCollectionId = collectionId
|
|
||||||
}*/
|
|
||||||
} else {
|
} else {
|
||||||
self.inputNodeInteraction.highlightedStickerItemCollectionId = collectionId
|
self.inputNodeInteraction.highlightedStickerItemCollectionId = collectionId
|
||||||
if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .stickers {
|
if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .stickers {
|
||||||
@ -1469,31 +1499,56 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
itemNode.updateIsHighlighted()
|
itemNode.updateIsHighlighted()
|
||||||
if itemNode.currentCollectionId == collectionId {
|
if itemNode.currentCollectionId == collectionId {
|
||||||
|
if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||||
|
self.panelCollapseScrollToIndex = targetIndex
|
||||||
|
self.updateIsExpanded(false)
|
||||||
|
} else {
|
||||||
self.listView.ensureItemNodeVisible(itemNode)
|
self.listView.ensureItemNodeVisible(itemNode)
|
||||||
|
}
|
||||||
ensuredNodeVisible = true
|
ensuredNodeVisible = true
|
||||||
}
|
}
|
||||||
} else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode {
|
} else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode {
|
||||||
itemNode.updateIsHighlighted()
|
itemNode.updateIsHighlighted()
|
||||||
if itemNode.currentCollectionId == collectionId {
|
if itemNode.currentCollectionId == collectionId {
|
||||||
|
if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||||
|
self.panelCollapseScrollToIndex = targetIndex
|
||||||
|
self.updateIsExpanded(false)
|
||||||
|
} else {
|
||||||
self.listView.ensureItemNodeVisible(itemNode)
|
self.listView.ensureItemNodeVisible(itemNode)
|
||||||
|
}
|
||||||
ensuredNodeVisible = true
|
ensuredNodeVisible = true
|
||||||
}
|
}
|
||||||
} else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode {
|
} else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode {
|
||||||
itemNode.updateIsHighlighted()
|
itemNode.updateIsHighlighted()
|
||||||
if itemNode.currentCollectionId == collectionId {
|
if itemNode.currentCollectionId == collectionId {
|
||||||
|
if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||||
|
self.panelCollapseScrollToIndex = targetIndex
|
||||||
|
self.updateIsExpanded(false)
|
||||||
|
} else {
|
||||||
self.listView.ensureItemNodeVisible(itemNode)
|
self.listView.ensureItemNodeVisible(itemNode)
|
||||||
|
}
|
||||||
ensuredNodeVisible = true
|
ensuredNodeVisible = true
|
||||||
}
|
}
|
||||||
} else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode {
|
} else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode {
|
||||||
itemNode.updateIsHighlighted()
|
itemNode.updateIsHighlighted()
|
||||||
if itemNode.currentCollectionId == collectionId {
|
if itemNode.currentCollectionId == collectionId {
|
||||||
|
if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||||
|
self.panelCollapseScrollToIndex = targetIndex
|
||||||
|
self.updateIsExpanded(false)
|
||||||
|
} else {
|
||||||
self.listView.ensureItemNodeVisible(itemNode)
|
self.listView.ensureItemNodeVisible(itemNode)
|
||||||
|
}
|
||||||
ensuredNodeVisible = true
|
ensuredNodeVisible = true
|
||||||
}
|
}
|
||||||
} else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode {
|
} else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode {
|
||||||
itemNode.updateIsHighlighted()
|
itemNode.updateIsHighlighted()
|
||||||
if itemNode.currentCollectionId == collectionId {
|
if itemNode.currentCollectionId == collectionId {
|
||||||
|
if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||||
|
self.panelCollapseScrollToIndex = targetIndex
|
||||||
|
self.updateIsExpanded(false)
|
||||||
|
} else {
|
||||||
self.listView.ensureItemNodeVisible(itemNode)
|
self.listView.ensureItemNodeVisible(itemNode)
|
||||||
|
}
|
||||||
ensuredNodeVisible = true
|
ensuredNodeVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1504,10 +1559,15 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
let firstVisibleIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == firstVisibleCollectionId })
|
let firstVisibleIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == firstVisibleCollectionId })
|
||||||
if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex {
|
if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex {
|
||||||
let toRight = targetIndex > firstVisibleIndex
|
let toRight = targetIndex > firstVisibleIndex
|
||||||
|
if self.panelExpanded {
|
||||||
|
self.panelCollapseScrollToIndex = targetIndex
|
||||||
|
self.updateIsExpanded(false)
|
||||||
|
} else {
|
||||||
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [], scrollToItem: ListViewScrollToItem(index: targetIndex, position: toRight ? .bottom(0.0) : .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: toRight ? .Down : .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil)
|
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [], scrollToItem: ListViewScrollToItem(index: targetIndex, position: toRight ? .bottom(0.0) : .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: toRight ? .Down : .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func currentCollectionListPanelOffset() -> CGFloat {
|
private func currentCollectionListPanelOffset() -> CGFloat {
|
||||||
let paneOffsets = self.paneArrangement.panes.map { pane -> CGFloat in
|
let paneOffsets = self.paneArrangement.panes.map { pane -> CGFloat in
|
||||||
@ -1516,8 +1576,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
return self.stickerPane.collectionListPanelOffset
|
return self.stickerPane.collectionListPanelOffset
|
||||||
case .gifs:
|
case .gifs:
|
||||||
return self.gifPane.collectionListPanelOffset
|
return self.gifPane.collectionListPanelOffset
|
||||||
/*case .trending:
|
|
||||||
return self.trendingPane.collectionListPanelOffset*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1611,7 +1669,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
self.stickerPane.collectionListPanelOffset = 0.0
|
self.stickerPane.collectionListPanelOffset = 0.0
|
||||||
self.gifPane.collectionListPanelOffset = 0.0
|
self.gifPane.collectionListPanelOffset = 0.0
|
||||||
//self.trendingPane.collectionListPanelOffset = 0.0
|
|
||||||
self.updateAppearanceTransition(transition: transition)
|
self.updateAppearanceTransition(transition: transition)
|
||||||
} else {
|
} else {
|
||||||
panelHeight = standardInputHeight
|
panelHeight = standardInputHeight
|
||||||
@ -1642,11 +1699,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .trending:
|
case .trending:
|
||||||
/*self.trendingPane.gridNode.forEachItemNode { itemNode in
|
|
||||||
if let itemNode = itemNode as? PaneSearchBarPlaceholderNode {
|
|
||||||
placeholderNode = itemNode
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1666,14 +1718,14 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: CGSize(width: width, height: 41.0)))
|
transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: CGSize(width: width, height: 41.0)))
|
||||||
transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 + collectionListPanelOffset), size: CGSize(width: width, height: separatorHeight)))
|
transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 + collectionListPanelOffset), size: CGSize(width: width, height: separatorHeight)))
|
||||||
|
|
||||||
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0, height: width)
|
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 20.0, height: width)
|
||||||
transition.updatePosition(node: self.listView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0))
|
transition.updatePosition(node: self.listView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0))
|
||||||
|
|
||||||
self.gifListView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0, height: width)
|
self.gifListView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 20.0, height: width)
|
||||||
transition.updatePosition(node: self.gifListView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0))
|
transition.updatePosition(node: self.gifListView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0))
|
||||||
|
|
||||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 41.0, height: width), insets: UIEdgeInsets(top: 4.0 + leftInset, left: 0.0, bottom: 4.0 + rightInset, right: 0.0), duration: duration, curve: curve)
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 41.0 + 31.0 + 20.0, height: width), insets: UIEdgeInsets(top: 4.0 + leftInset, left: 0.0, bottom: 4.0 + rightInset, right: 0.0), duration: duration, curve: curve)
|
||||||
|
|
||||||
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
|
|
||||||
@ -1721,7 +1773,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
self.gifPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, deviceMetrics: deviceMetrics, transition: transition)
|
self.gifPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, deviceMetrics: deviceMetrics, transition: transition)
|
||||||
self.trendingInteraction?.itemContext.canPlayMedia = isVisible
|
self.trendingInteraction?.itemContext.canPlayMedia = isVisible
|
||||||
self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible && visiblePanes.contains(where: { $0.0 == .stickers }), deviceMetrics: deviceMetrics, transition: transition)
|
self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible && visiblePanes.contains(where: { $0.0 == .stickers }), deviceMetrics: deviceMetrics, transition: transition)
|
||||||
//self.trendingPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, deviceMetrics: deviceMetrics, transition: transition)
|
|
||||||
|
|
||||||
if self.gifPane.supernode != nil {
|
if self.gifPane.supernode != nil {
|
||||||
if !visiblePanes.contains(where: { $0.0 == .gifs }) {
|
if !visiblePanes.contains(where: { $0.0 == .gifs }) {
|
||||||
@ -1773,31 +1824,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
self.animatingStickerPaneOut = false
|
self.animatingStickerPaneOut = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if self.trendingPane.supernode != nil {
|
|
||||||
if !visiblePanes.contains(where: { $0.0 == .trending }) {
|
|
||||||
if case .animated = transition {
|
|
||||||
if !self.animatingTrendingPaneOut {
|
|
||||||
self.animatingTrendingPaneOut = true
|
|
||||||
var toLeft = false
|
|
||||||
if let index = self.paneArrangement.panes.firstIndex(of: .trending), index < self.paneArrangement.currentIndex {
|
|
||||||
toLeft = true
|
|
||||||
}
|
|
||||||
transition.animatePosition(node: self.trendingPane, to: CGPoint(x: (toLeft ? -width : width) + width / 2.0, y: self.trendingPane.layer.position.y), removeOnCompletion: false, completion: { [weak self] value in
|
|
||||||
if let strongSelf = self, value {
|
|
||||||
strongSelf.animatingTrendingPaneOut = false
|
|
||||||
strongSelf.trendingPane.removeFromSupernode()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.animatingTrendingPaneOut = false
|
|
||||||
self.trendingPane.removeFromSupernode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.animatingTrendingPaneOut = false
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if !displaySearch, let searchContainerNode = self.searchContainerNode {
|
if !displaySearch, let searchContainerNode = self.searchContainerNode {
|
||||||
self.searchContainerNode = nil
|
self.searchContainerNode = nil
|
||||||
self.searchContainerNodeLoadedDisposable.set(nil)
|
self.searchContainerNodeLoadedDisposable.set(nil)
|
||||||
@ -1820,12 +1846,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .trending:
|
case .trending:
|
||||||
/*self.trendingPane.gridNode.forEachItemNode { itemNode in
|
|
||||||
if let itemNode = itemNode as? PaneSearchBarPlaceholderNode {
|
|
||||||
placeholderNode = itemNode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
paneIsEmpty = true*/
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1866,7 +1886,20 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
} else {
|
} else {
|
||||||
options.insert(.AnimateInsertion)
|
options.insert(.AnimateInsertion)
|
||||||
}
|
}
|
||||||
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
|
|
||||||
|
var scrollToItem: ListViewScrollToItem?
|
||||||
|
if let targetIndex = self.panelCollapseScrollToIndex {
|
||||||
|
var position: ListViewScrollPosition
|
||||||
|
if self.panelExpanded {
|
||||||
|
position = .center(.top)
|
||||||
|
} else {
|
||||||
|
position = .top(self.listView.frame.height / 2.0 + 96.0)
|
||||||
|
}
|
||||||
|
scrollToItem = ListViewScrollToItem(index: targetIndex, position: position, animated: true, curve: .Default(duration: nil), directionHint: .Down)
|
||||||
|
self.panelCollapseScrollToIndex = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime)
|
strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime)
|
||||||
if !strongSelf.didSetReady {
|
if !strongSelf.didSetReady {
|
||||||
@ -1904,7 +1937,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.searchContainerNode?.contentNode.updatePreviewing(animated: animated)
|
self.searchContainerNode?.contentNode.updatePreviewing(animated: animated)
|
||||||
//self.trendingPane.updatePreviewing(animated: animated)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1921,11 +1953,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
self.animatingStickerPaneOut = false
|
self.animatingStickerPaneOut = false
|
||||||
self.stickerPane.removeFromSupernode()
|
self.stickerPane.removeFromSupernode()
|
||||||
}
|
}
|
||||||
/*self.trendingPane.layer.removeAllAnimations()
|
|
||||||
if self.animatingTrendingPaneOut {
|
|
||||||
self.animatingTrendingPaneOut = false
|
|
||||||
self.trendingPane.removeFromSupernode()
|
|
||||||
}*/
|
|
||||||
case .changed:
|
case .changed:
|
||||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
|
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
|
||||||
let translationX = -recognizer.translation(in: self.view).x
|
let translationX = -recognizer.translation(in: self.view).x
|
||||||
@ -1990,20 +2017,31 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let collectionListPanelOffset = self.currentCollectionListPanelOffset()
|
var collectionListPanelOffset = self.currentCollectionListPanelOffset()
|
||||||
|
if self.panelExpanded {
|
||||||
|
collectionListPanelOffset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
var listPanelOffset = collectionListPanelOffset * 2.0
|
||||||
|
|
||||||
self.updateAppearanceTransition(transition: transition)
|
self.updateAppearanceTransition(transition: transition)
|
||||||
transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: self.collectionListPanel.bounds.size))
|
transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: listPanelOffset), size: self.collectionListPanel.bounds.size))
|
||||||
transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 + collectionListPanelOffset), size: self.collectionListSeparator.bounds.size))
|
transition.updatePosition(node: self.listView, position: CGPoint(x: self.listView.position.x, y: (41.0 - listPanelOffset) / 2.0))
|
||||||
transition.updatePosition(node: self.listView, position: CGPoint(x: self.listView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0))
|
transition.updatePosition(node: self.gifListView, position: CGPoint(x: self.gifListView.position.x, y: (41.0 - listPanelOffset) / 2.0))
|
||||||
transition.updatePosition(node: self.gifListView, position: CGPoint(x: self.gifListView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0))
|
|
||||||
|
|
||||||
self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: collectionListPanelOffset, transition: transition)
|
self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: collectionListPanelOffset, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePaneClippingContainer(size: CGSize, offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
private func updatePaneClippingContainer(size: CGSize, offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
transition.updateFrame(node: self.paneClippingContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0), size: size))
|
var offset = offset
|
||||||
transition.updateSublayerTransformOffset(layer: self.paneClippingContainer.layer, offset: CGPoint(x: 0.0, y: -offset - 41.0))
|
var additionalOffset: CGFloat = 0.0
|
||||||
|
if self.panelExpanded {
|
||||||
|
offset = 0.0
|
||||||
|
additionalOffset = 31.0
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0 + additionalOffset), size: self.collectionListSeparator.bounds.size))
|
||||||
|
transition.updateFrame(node: self.paneClippingContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0 + additionalOffset), size: size))
|
||||||
|
transition.updateSublayerTransformOffset(layer: self.paneClippingContainer.layer, offset: CGPoint(x: 0.0, y: -offset - 41.0 - additionalOffset))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fixPaneScroll(pane: ChatMediaInputPane, state: ChatMediaInputPaneScrollState) {
|
private func fixPaneScroll(pane: ChatMediaInputPane, state: ChatMediaInputPaneScrollState) {
|
||||||
@ -2017,12 +2055,14 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let collectionListPanelOffset = self.currentCollectionListPanelOffset()
|
var collectionListPanelOffset = self.currentCollectionListPanelOffset()
|
||||||
|
if self.panelExpanded {
|
||||||
|
collectionListPanelOffset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .spring)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .spring)
|
||||||
self.updateAppearanceTransition(transition: transition)
|
self.updateAppearanceTransition(transition: transition)
|
||||||
transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: self.collectionListPanel.bounds.size))
|
transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: self.collectionListPanel.bounds.size))
|
||||||
transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 + collectionListPanelOffset), size: self.collectionListSeparator.bounds.size))
|
|
||||||
transition.updatePosition(node: self.listView, position: CGPoint(x: self.listView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0))
|
transition.updatePosition(node: self.listView, position: CGPoint(x: self.listView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0))
|
||||||
transition.updatePosition(node: self.gifListView, position: CGPoint(x: self.gifListView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0))
|
transition.updatePosition(node: self.gifListView, position: CGPoint(x: self.gifListView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0))
|
||||||
|
|
||||||
|
|||||||
@ -32,18 +32,18 @@ enum ChatMediaInputPanelEntryStableId: Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
||||||
case recentGifs(PresentationTheme)
|
case recentGifs(PresentationTheme, Bool)
|
||||||
case savedStickers(PresentationTheme)
|
case savedStickers(PresentationTheme, Bool)
|
||||||
case recentPacks(PresentationTheme)
|
case recentPacks(PresentationTheme, Bool)
|
||||||
case trending(Bool, PresentationTheme)
|
case trending(Bool, PresentationTheme, Bool)
|
||||||
case settings(PresentationTheme)
|
case settings(PresentationTheme, Bool)
|
||||||
case peerSpecific(theme: PresentationTheme, peer: Peer)
|
case peerSpecific(theme: PresentationTheme, peer: Peer, expanded: Bool)
|
||||||
case stickerPack(index: Int, info: StickerPackCollectionInfo, topItem: StickerPackItem?, theme: PresentationTheme)
|
case stickerPack(index: Int, info: StickerPackCollectionInfo, topItem: StickerPackItem?, theme: PresentationTheme, expanded: Bool)
|
||||||
|
|
||||||
case stickersMode(PresentationTheme)
|
case stickersMode(PresentationTheme, Bool)
|
||||||
case savedGifs(PresentationTheme)
|
case savedGifs(PresentationTheme, Bool)
|
||||||
case trendingGifs(PresentationTheme)
|
case trendingGifs(PresentationTheme, Bool)
|
||||||
case gifEmotion(Int, PresentationTheme, String)
|
case gifEmotion(Int, PresentationTheme, String, Bool)
|
||||||
|
|
||||||
var stableId: ChatMediaInputPanelEntryStableId {
|
var stableId: ChatMediaInputPanelEntryStableId {
|
||||||
switch self {
|
switch self {
|
||||||
@ -59,7 +59,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
|||||||
return .settings
|
return .settings
|
||||||
case .peerSpecific:
|
case .peerSpecific:
|
||||||
return .peerSpecific
|
return .peerSpecific
|
||||||
case let .stickerPack(_, info, _, _):
|
case let .stickerPack(_, info, _, _, _):
|
||||||
return .stickerPack(info.id.id)
|
return .stickerPack(info.id.id)
|
||||||
case .stickersMode:
|
case .stickersMode:
|
||||||
return .stickersMode
|
return .stickersMode
|
||||||
@ -67,75 +67,75 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
|||||||
return .savedGifs
|
return .savedGifs
|
||||||
case .trendingGifs:
|
case .trendingGifs:
|
||||||
return .trendingGifs
|
return .trendingGifs
|
||||||
case let .gifEmotion(_, _, emoji):
|
case let .gifEmotion(_, _, emoji, _):
|
||||||
return .gifEmotion(emoji)
|
return .gifEmotion(emoji)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: ChatMediaInputPanelEntry, rhs: ChatMediaInputPanelEntry) -> Bool {
|
static func ==(lhs: ChatMediaInputPanelEntry, rhs: ChatMediaInputPanelEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case let .recentGifs(lhsTheme):
|
case let .recentGifs(lhsTheme, lhsExpanded):
|
||||||
if case let .recentGifs(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
if case let .recentGifs(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .savedStickers(lhsTheme):
|
case let .savedStickers(lhsTheme, lhsExpanded):
|
||||||
if case let .savedStickers(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
if case let .savedStickers(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .recentPacks(lhsTheme):
|
case let .recentPacks(lhsTheme, lhsExpanded):
|
||||||
if case let .recentPacks(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
if case let .recentPacks(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .trending(lhsElevated, lhsTheme):
|
case let .trending(lhsElevated, lhsTheme, lhsExpanded):
|
||||||
if case let .trending(rhsElevated, rhsTheme) = rhs, lhsTheme === rhsTheme, lhsElevated == rhsElevated {
|
if case let .trending(rhsElevated, rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsElevated == rhsElevated, lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .settings(lhsTheme):
|
case let .settings(lhsTheme, lhsExpanded):
|
||||||
if case let .settings(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
if case let .settings(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .peerSpecific(lhsTheme, lhsPeer):
|
case let .peerSpecific(lhsTheme, lhsPeer, lhsExpanded):
|
||||||
if case let .peerSpecific(rhsTheme, rhsPeer) = rhs, lhsTheme === rhsTheme, lhsPeer.isEqual(rhsPeer) {
|
if case let .peerSpecific(rhsTheme, rhsPeer, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsPeer.isEqual(rhsPeer), lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .stickerPack(index, info, topItem, lhsTheme):
|
case let .stickerPack(index, info, topItem, lhsTheme, lhsExpanded):
|
||||||
if case let .stickerPack(rhsIndex, rhsInfo, rhsTopItem, rhsTheme) = rhs, index == rhsIndex, info == rhsInfo, topItem == rhsTopItem, lhsTheme === rhsTheme {
|
if case let .stickerPack(rhsIndex, rhsInfo, rhsTopItem, rhsTheme, rhsExpanded) = rhs, index == rhsIndex, info == rhsInfo, topItem == rhsTopItem, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .stickersMode(lhsTheme):
|
case let .stickersMode(lhsTheme, lhsExpanded):
|
||||||
if case let .stickersMode(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
if case let .stickersMode(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .savedGifs(lhsTheme):
|
case let .savedGifs(lhsTheme, lhsExpanded):
|
||||||
if case let .savedGifs(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
if case let .savedGifs(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .trendingGifs(lhsTheme):
|
case let .trendingGifs(lhsTheme, lhsExpanded):
|
||||||
if case let .trendingGifs(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
if case let .trendingGifs(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .gifEmotion(lhsIndex, lhsTheme, lhsEmoji):
|
case let .gifEmotion(lhsIndex, lhsTheme, lhsEmoji, lhsExpanded):
|
||||||
if case let .gifEmotion(rhsIndex, rhsTheme, rhsEmoji) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsEmoji == rhsEmoji {
|
if case let .gifEmotion(rhsIndex, rhsTheme, rhsEmoji, rhsExpanded) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsEmoji == rhsEmoji, lhsExpanded == rhsExpanded {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -156,7 +156,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
|||||||
switch rhs {
|
switch rhs {
|
||||||
case .recentGifs, savedStickers:
|
case .recentGifs, savedStickers:
|
||||||
return false
|
return false
|
||||||
case let .trending(elevated, _) where elevated:
|
case let .trending(elevated, _, _) where elevated:
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
@ -165,7 +165,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
|||||||
switch rhs {
|
switch rhs {
|
||||||
case .recentGifs, .savedStickers, recentPacks:
|
case .recentGifs, .savedStickers, recentPacks:
|
||||||
return false
|
return false
|
||||||
case let .trending(elevated, _) where elevated:
|
case let .trending(elevated, _, _) where elevated:
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
@ -174,16 +174,16 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
|||||||
switch rhs {
|
switch rhs {
|
||||||
case .recentGifs, .savedStickers, recentPacks, .peerSpecific:
|
case .recentGifs, .savedStickers, recentPacks, .peerSpecific:
|
||||||
return false
|
return false
|
||||||
case let .trending(elevated, _) where elevated:
|
case let .trending(elevated, _, _) where elevated:
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .stickerPack(lhsIndex, lhsInfo, _, _):
|
case let .stickerPack(lhsIndex, lhsInfo, _, _, _):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .recentGifs, .savedStickers, .recentPacks, .peerSpecific:
|
case .recentGifs, .savedStickers, .recentPacks, .peerSpecific:
|
||||||
return false
|
return false
|
||||||
case let .trending(elevated, _):
|
case let .trending(elevated, _, _):
|
||||||
if elevated {
|
if elevated {
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
@ -191,7 +191,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
case .settings:
|
case .settings:
|
||||||
return true
|
return true
|
||||||
case let .stickerPack(rhsIndex, rhsInfo, _, _):
|
case let .stickerPack(rhsIndex, rhsInfo, _, _, _):
|
||||||
if lhsIndex == rhsIndex {
|
if lhsIndex == rhsIndex {
|
||||||
return lhsInfo.id.id < rhsInfo.id.id
|
return lhsInfo.id.id < rhsInfo.id.id
|
||||||
} else {
|
} else {
|
||||||
@ -200,7 +200,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
|||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .trending(elevated, _):
|
case let .trending(elevated, _, _):
|
||||||
if elevated {
|
if elevated {
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .recentGifs, .trending:
|
case .recentGifs, .trending:
|
||||||
@ -231,11 +231,11 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
|||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .gifEmotion(lhsIndex, _, _):
|
case let .gifEmotion(lhsIndex, _, _, _):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .stickersMode, .savedGifs, .trendingGifs:
|
case .stickersMode, .savedGifs, .trendingGifs:
|
||||||
return false
|
return false
|
||||||
case let .gifEmotion(rhsIndex, _, _):
|
case let .gifEmotion(rhsIndex, _, _, _):
|
||||||
return lhsIndex < rhsIndex
|
return lhsIndex < rhsIndex
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
@ -251,53 +251,53 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
func item(context: AccountContext, inputNodeInteraction: ChatMediaInputNodeInteraction) -> ListViewItem {
|
func item(context: AccountContext, inputNodeInteraction: ChatMediaInputNodeInteraction) -> ListViewItem {
|
||||||
switch self {
|
switch self {
|
||||||
case let .recentGifs(theme):
|
case let .recentGifs(theme, expanded):
|
||||||
return ChatMediaInputRecentGifsItem(inputNodeInteraction: inputNodeInteraction, theme: theme, selected: {
|
return ChatMediaInputRecentGifsItem(inputNodeInteraction: inputNodeInteraction, theme: theme, expanded: expanded, selected: {
|
||||||
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue, id: 0)
|
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue, id: 0)
|
||||||
inputNodeInteraction.navigateToCollectionId(collectionId)
|
inputNodeInteraction.navigateToCollectionId(collectionId)
|
||||||
})
|
})
|
||||||
case let .savedStickers(theme):
|
case let .savedStickers(theme, expanded):
|
||||||
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedStickers, theme: theme, selected: {
|
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedStickers, theme: theme, expanded: expanded, selected: {
|
||||||
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0)
|
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0)
|
||||||
inputNodeInteraction.navigateToCollectionId(collectionId)
|
inputNodeInteraction.navigateToCollectionId(collectionId)
|
||||||
})
|
})
|
||||||
case let .recentPacks(theme):
|
case let .recentPacks(theme, expanded):
|
||||||
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .recentStickers, theme: theme, selected: {
|
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .recentStickers, theme: theme, expanded: expanded, selected: {
|
||||||
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0)
|
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0)
|
||||||
inputNodeInteraction.navigateToCollectionId(collectionId)
|
inputNodeInteraction.navigateToCollectionId(collectionId)
|
||||||
})
|
})
|
||||||
case let .trending(elevated, theme):
|
case let .trending(elevated, theme, expanded):
|
||||||
return ChatMediaInputTrendingItem(inputNodeInteraction: inputNodeInteraction, elevated: elevated, theme: theme, selected: {
|
return ChatMediaInputTrendingItem(inputNodeInteraction: inputNodeInteraction, elevated: elevated, theme: theme, expanded: expanded, selected: {
|
||||||
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0)
|
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0)
|
||||||
inputNodeInteraction.navigateToCollectionId(collectionId)
|
inputNodeInteraction.navigateToCollectionId(collectionId)
|
||||||
})
|
})
|
||||||
case let .settings(theme):
|
case let .settings(theme, expanded):
|
||||||
return ChatMediaInputSettingsItem(inputNodeInteraction: inputNodeInteraction, theme: theme, selected: {
|
return ChatMediaInputSettingsItem(inputNodeInteraction: inputNodeInteraction, theme: theme, expanded: expanded, selected: {
|
||||||
inputNodeInteraction.openSettings()
|
inputNodeInteraction.openSettings()
|
||||||
})
|
})
|
||||||
case let .peerSpecific(theme, peer):
|
case let .peerSpecific(theme, peer, expanded):
|
||||||
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0)
|
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0)
|
||||||
return ChatMediaInputPeerSpecificItem(context: context, inputNodeInteraction: inputNodeInteraction, collectionId: collectionId, peer: peer, theme: theme, selected: {
|
return ChatMediaInputPeerSpecificItem(context: context, inputNodeInteraction: inputNodeInteraction, collectionId: collectionId, peer: peer, theme: theme, expanded: expanded, selected: {
|
||||||
inputNodeInteraction.navigateToCollectionId(collectionId)
|
inputNodeInteraction.navigateToCollectionId(collectionId)
|
||||||
})
|
})
|
||||||
case let .stickerPack(index, info, topItem, theme):
|
case let .stickerPack(index, info, topItem, theme, expanded):
|
||||||
return ChatMediaInputStickerPackItem(account: context.account, inputNodeInteraction: inputNodeInteraction, collectionId: info.id, collectionInfo: info, stickerPackItem: topItem, index: index, theme: theme, selected: {
|
return ChatMediaInputStickerPackItem(account: context.account, inputNodeInteraction: inputNodeInteraction, collectionId: info.id, collectionInfo: info, stickerPackItem: topItem, index: index, theme: theme, expanded: expanded, selected: {
|
||||||
inputNodeInteraction.navigateToCollectionId(info.id)
|
inputNodeInteraction.navigateToCollectionId(info.id)
|
||||||
})
|
})
|
||||||
case let .stickersMode(theme):
|
case let .stickersMode(theme, expanded):
|
||||||
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .stickersMode, theme: theme, selected: {
|
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .stickersMode, theme: theme, expanded: expanded, selected: {
|
||||||
inputNodeInteraction.navigateBackToStickers()
|
inputNodeInteraction.navigateBackToStickers()
|
||||||
})
|
})
|
||||||
case let .savedGifs(theme):
|
case let .savedGifs(theme, expanded):
|
||||||
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedGifs, theme: theme, selected: {
|
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedGifs, theme: theme, expanded: expanded, selected: {
|
||||||
inputNodeInteraction.setGifMode(.recent)
|
inputNodeInteraction.setGifMode(.recent)
|
||||||
})
|
})
|
||||||
case let .trendingGifs(theme):
|
case let .trendingGifs(theme, expanded):
|
||||||
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .trendingGifs, theme: theme, selected: {
|
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .trendingGifs, theme: theme, expanded: expanded, selected: {
|
||||||
inputNodeInteraction.setGifMode(.trending)
|
inputNodeInteraction.setGifMode(.trending)
|
||||||
})
|
})
|
||||||
case let .gifEmotion(_, theme, emoji):
|
case let .gifEmotion(_, theme, emoji, expanded):
|
||||||
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji), theme: theme, selected: {
|
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji), theme: theme, expanded: expanded, selected: {
|
||||||
inputNodeInteraction.setGifMode(.emojiSearch(emoji))
|
inputNodeInteraction.setGifMode(.emojiSearch(emoji))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ final class ChatMediaInputPeerSpecificItem: ListViewItem {
|
|||||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||||
let collectionId: ItemCollectionId
|
let collectionId: ItemCollectionId
|
||||||
let peer: Peer
|
let peer: Peer
|
||||||
|
let expanded: Bool
|
||||||
let selectedItem: () -> Void
|
let selectedItem: () -> Void
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
|
||||||
@ -22,12 +23,13 @@ final class ChatMediaInputPeerSpecificItem: ListViewItem {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(context: AccountContext, inputNodeInteraction: ChatMediaInputNodeInteraction, collectionId: ItemCollectionId, peer: Peer, theme: PresentationTheme, selected: @escaping () -> Void) {
|
init(context: AccountContext, inputNodeInteraction: ChatMediaInputNodeInteraction, collectionId: ItemCollectionId, peer: Peer, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.inputNodeInteraction = inputNodeInteraction
|
self.inputNodeInteraction = inputNodeInteraction
|
||||||
self.collectionId = collectionId
|
self.collectionId = collectionId
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.selectedItem = selected
|
self.selectedItem = selected
|
||||||
|
self.expanded = expanded
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,28 +11,30 @@ import TelegramPresentationData
|
|||||||
final class ChatMediaInputRecentGifsItem: ListViewItem {
|
final class ChatMediaInputRecentGifsItem: ListViewItem {
|
||||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||||
let selectedItem: () -> Void
|
let selectedItem: () -> Void
|
||||||
|
let expanded: Bool
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
|
||||||
var selectable: Bool {
|
var selectable: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, selected: @escaping () -> Void) {
|
init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) {
|
||||||
self.inputNodeInteraction = inputNodeInteraction
|
self.inputNodeInteraction = inputNodeInteraction
|
||||||
self.selectedItem = selected
|
self.selectedItem = selected
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.expanded = expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
async {
|
async {
|
||||||
let node = ChatMediaInputRecentGifsItemNode()
|
let node = ChatMediaInputRecentGifsItemNode()
|
||||||
node.contentSize = CGSize(width: 41.0, height: 41.0)
|
node.contentSize = self.expanded ? expandedBoundingSize : boundingSize
|
||||||
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||||
node.inputNodeInteraction = self.inputNodeInteraction
|
node.inputNodeInteraction = self.inputNodeInteraction
|
||||||
node.updateTheme(theme: self.theme)
|
|
||||||
node.updateIsHighlighted()
|
node.updateIsHighlighted()
|
||||||
node.updateAppearanceTransition(transition: .immediate)
|
node.updateAppearanceTransition(transition: .immediate)
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
|
node.updateTheme(theme: self.theme, expanded: self.expanded)
|
||||||
completion(node, {
|
completion(node, {
|
||||||
return (nil, { _ in })
|
return (nil, { _ in })
|
||||||
})
|
})
|
||||||
@ -42,8 +44,8 @@ final class ChatMediaInputRecentGifsItem: ListViewItem {
|
|||||||
|
|
||||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in
|
completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in
|
||||||
(node() as? ChatMediaInputRecentGifsItemNode)?.updateTheme(theme: self.theme)
|
(node() as? ChatMediaInputRecentGifsItemNode)?.updateTheme(theme: self.theme, expanded: self.expanded)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,14 +55,20 @@ final class ChatMediaInputRecentGifsItem: ListViewItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let boundingSize = CGSize(width: 41.0, height: 41.0)
|
private let boundingSize = CGSize(width: 72.0, height: 41.0)
|
||||||
private let boundingImageSize = CGSize(width: 30.0, height: 30.0)
|
private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0)
|
||||||
private let highlightSize = CGSize(width: 35.0, height: 35.0)
|
private let boundingImageScale: CGFloat = 0.625
|
||||||
|
private let highlightSize = CGSize(width: 56.0, height: 56.0)
|
||||||
private let verticalOffset: CGFloat = 3.0 + UIScreenPixel
|
private let verticalOffset: CGFloat = 3.0 + UIScreenPixel
|
||||||
|
|
||||||
final class ChatMediaInputRecentGifsItemNode: ListViewItemNode {
|
final class ChatMediaInputRecentGifsItemNode: ListViewItemNode {
|
||||||
|
private let containerNode: ASDisplayNode
|
||||||
|
private let scalingNode: ASDisplayNode
|
||||||
private let imageNode: ASImageNode
|
private let imageNode: ASImageNode
|
||||||
private let highlightNode: ASImageNode
|
private let highlightNode: ASImageNode
|
||||||
|
private let titleNode: ImmediateTextNode
|
||||||
|
|
||||||
|
private var currentExpanded = false
|
||||||
|
|
||||||
var currentCollectionId: ItemCollectionId?
|
var currentCollectionId: ItemCollectionId?
|
||||||
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||||
@ -68,40 +76,68 @@ final class ChatMediaInputRecentGifsItemNode: ListViewItemNode {
|
|||||||
var theme: PresentationTheme?
|
var theme: PresentationTheme?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self.containerNode = ASDisplayNode()
|
||||||
|
self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
|
||||||
|
self.scalingNode = ASDisplayNode()
|
||||||
|
|
||||||
self.highlightNode = ASImageNode()
|
self.highlightNode = ASImageNode()
|
||||||
self.highlightNode.isLayerBacked = true
|
self.highlightNode.isLayerBacked = true
|
||||||
self.highlightNode.isHidden = true
|
self.highlightNode.isHidden = true
|
||||||
|
|
||||||
self.imageNode = ASImageNode()
|
self.imageNode = ASImageNode()
|
||||||
self.imageNode.isLayerBacked = true
|
self.imageNode.isLayerBacked = true
|
||||||
self.imageNode.contentMode = .center
|
|
||||||
self.imageNode.contentsScale = UIScreenScale
|
|
||||||
|
|
||||||
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - highlightSize.height) / 2.0)), size: highlightSize)
|
self.titleNode = ImmediateTextNode()
|
||||||
|
|
||||||
self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.highlightNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.addSubnode(self.imageNode)
|
self.containerNode.addSubnode(self.scalingNode)
|
||||||
|
|
||||||
|
self.scalingNode.addSubnode(self.highlightNode)
|
||||||
|
self.scalingNode.addSubnode(self.titleNode)
|
||||||
|
self.scalingNode.addSubnode(self.imageNode)
|
||||||
|
|
||||||
self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue, id: 0)
|
self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue, id: 0)
|
||||||
|
|
||||||
let imageSize = CGSize(width: 26.0, height: 26.0)
|
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTheme(theme: PresentationTheme) {
|
func updateTheme(theme: PresentationTheme, expanded: Bool) {
|
||||||
if self.theme !== theme {
|
if self.theme !== theme {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme)
|
self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme)
|
||||||
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentGifsIconImage(theme)
|
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentGifsIconImage(theme)
|
||||||
|
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: "GIFs", font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let imageSize = CGSize(width: 26.0 * 1.6, height: 26.0 * 1.6)
|
||||||
|
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((expandedBoundingSize.width - imageSize.width) / 2.0), y: floor((expandedBoundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
||||||
|
|
||||||
|
self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize)
|
||||||
|
self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize)
|
||||||
|
|
||||||
|
let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height)
|
||||||
|
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||||
|
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||||
|
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||||
|
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||||
|
|
||||||
|
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||||
|
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||||
|
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||||
|
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||||
|
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||||
|
expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001)
|
||||||
|
|
||||||
|
self.currentExpanded = expanded
|
||||||
|
|
||||||
|
expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.imageNode.position.x - highlightSize.width / 2.0, y: self.imageNode.position.y - highlightSize.height / 2.0), size: highlightSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateIsHighlighted() {
|
func updateIsHighlighted() {
|
||||||
|
|||||||
@ -11,27 +11,29 @@ import TelegramPresentationData
|
|||||||
final class ChatMediaInputSettingsItem: ListViewItem {
|
final class ChatMediaInputSettingsItem: ListViewItem {
|
||||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||||
let selectedItem: () -> Void
|
let selectedItem: () -> Void
|
||||||
|
let expanded: Bool
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
|
||||||
var selectable: Bool {
|
var selectable: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, selected: @escaping () -> Void) {
|
init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) {
|
||||||
self.inputNodeInteraction = inputNodeInteraction
|
self.inputNodeInteraction = inputNodeInteraction
|
||||||
self.selectedItem = selected
|
self.selectedItem = selected
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.expanded = expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
async {
|
async {
|
||||||
let node = ChatMediaInputSettingsItemNode()
|
let node = ChatMediaInputSettingsItemNode()
|
||||||
node.contentSize = CGSize(width: 41.0, height: 41.0)
|
node.contentSize = self.expanded ? expandedBoundingSize : boundingSize
|
||||||
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||||
node.inputNodeInteraction = self.inputNodeInteraction
|
node.inputNodeInteraction = self.inputNodeInteraction
|
||||||
node.updateTheme(theme: self.theme)
|
|
||||||
node.updateAppearanceTransition(transition: .immediate)
|
node.updateAppearanceTransition(transition: .immediate)
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
|
node.updateTheme(theme: self.theme, expanded: self.expanded)
|
||||||
completion(node, {
|
completion(node, {
|
||||||
return (nil, { _ in })
|
return (nil, { _ in })
|
||||||
})
|
})
|
||||||
@ -41,8 +43,8 @@ final class ChatMediaInputSettingsItem: ListViewItem {
|
|||||||
|
|
||||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in
|
completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in
|
||||||
(node() as? ChatMediaInputSettingsItemNode)?.updateTheme(theme: self.theme)
|
(node() as? ChatMediaInputSettingsItemNode)?.updateTheme(theme: self.theme, expanded: self.expanded)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,14 +54,19 @@ final class ChatMediaInputSettingsItem: ListViewItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let boundingSize = CGSize(width: 41.0, height: 41.0)
|
private let boundingSize = CGSize(width: 72.0, height: 41.0)
|
||||||
private let boundingImageSize = CGSize(width: 30.0, height: 30.0)
|
private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0)
|
||||||
private let highlightSize = CGSize(width: 35.0, height: 35.0)
|
private let boundingImageScale: CGFloat = 0.625
|
||||||
private let verticalOffset: CGFloat = 3.0 + UIScreenPixel
|
private let verticalOffset: CGFloat = 3.0 + UIScreenPixel
|
||||||
|
|
||||||
final class ChatMediaInputSettingsItemNode: ListViewItemNode {
|
final class ChatMediaInputSettingsItemNode: ListViewItemNode {
|
||||||
|
private let containerNode: ASDisplayNode
|
||||||
|
private let scalingNode: ASDisplayNode
|
||||||
private let buttonNode: HighlightableButtonNode
|
private let buttonNode: HighlightableButtonNode
|
||||||
private let imageNode: ASImageNode
|
private let imageNode: ASImageNode
|
||||||
|
private let titleNode: ImmediateTextNode
|
||||||
|
|
||||||
|
private var currentExpanded = false
|
||||||
|
|
||||||
var currentCollectionId: ItemCollectionId?
|
var currentCollectionId: ItemCollectionId?
|
||||||
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||||
@ -67,37 +74,59 @@ final class ChatMediaInputSettingsItemNode: ListViewItemNode {
|
|||||||
var theme: PresentationTheme?
|
var theme: PresentationTheme?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self.containerNode = ASDisplayNode()
|
||||||
|
self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
|
||||||
|
self.scalingNode = ASDisplayNode()
|
||||||
|
|
||||||
self.buttonNode = HighlightableButtonNode()
|
self.buttonNode = HighlightableButtonNode()
|
||||||
|
|
||||||
self.imageNode = ASImageNode()
|
self.imageNode = ASImageNode()
|
||||||
self.imageNode.isLayerBacked = true
|
self.imageNode.isLayerBacked = true
|
||||||
self.imageNode.contentMode = .center
|
|
||||||
self.imageNode.contentsScale = UIScreenScale
|
|
||||||
|
|
||||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: boundingSize)
|
self.titleNode = ImmediateTextNode()
|
||||||
|
|
||||||
self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
|
||||||
self.imageNode.contentMode = .center
|
|
||||||
self.imageNode.contentsScale = UIScreenScale
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.buttonNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.buttonNode.addSubnode(self.imageNode)
|
self.containerNode.addSubnode(self.scalingNode)
|
||||||
|
|
||||||
let imageSize = CGSize(width: 26.0, height: 26.0)
|
self.scalingNode.addSubnode(self.buttonNode)
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
self.scalingNode.addSubnode(self.imageNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
func updateTheme(theme: PresentationTheme, expanded: Bool) {
|
||||||
}
|
let imageSize = CGSize(width: 26.0 * 1.6, height: 26.0 * 1.6)
|
||||||
|
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((expandedBoundingSize.width - imageSize.width) / 2.0), y: floor((expandedBoundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
||||||
|
|
||||||
func updateTheme(theme: PresentationTheme) {
|
|
||||||
if self.theme !== theme {
|
if self.theme !== theme {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelSettingsIconImage(theme)
|
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelSettingsIconImage(theme)
|
||||||
|
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: "Settings", font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize)
|
||||||
|
self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize)
|
||||||
|
self.buttonNode.frame = self.scalingNode.bounds
|
||||||
|
|
||||||
|
let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height)
|
||||||
|
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||||
|
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||||
|
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||||
|
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||||
|
|
||||||
|
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||||
|
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||||
|
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||||
|
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||||
|
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||||
|
expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001)
|
||||||
|
|
||||||
|
self.currentExpanded = expanded
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateAppearanceTransition(transition: ContainedViewLayoutTransition) {
|
func updateAppearanceTransition(transition: ContainedViewLayoutTransition) {
|
||||||
|
|||||||
@ -22,12 +22,13 @@ final class ChatMediaInputStickerPackItem: ListViewItem {
|
|||||||
let selectedItem: () -> Void
|
let selectedItem: () -> Void
|
||||||
let index: Int
|
let index: Int
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
let expanded: Bool
|
||||||
|
|
||||||
var selectable: Bool {
|
var selectable: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction, collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo, stickerPackItem: StickerPackItem?, index: Int, theme: PresentationTheme, selected: @escaping () -> Void) {
|
init(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction, collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo, stickerPackItem: StickerPackItem?, index: Int, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.inputNodeInteraction = inputNodeInteraction
|
self.inputNodeInteraction = inputNodeInteraction
|
||||||
self.collectionId = collectionId
|
self.collectionId = collectionId
|
||||||
@ -36,18 +37,19 @@ final class ChatMediaInputStickerPackItem: ListViewItem {
|
|||||||
self.selectedItem = selected
|
self.selectedItem = selected
|
||||||
self.index = index
|
self.index = index
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.expanded = expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
async {
|
async {
|
||||||
let node = ChatMediaInputStickerPackItemNode()
|
let node = ChatMediaInputStickerPackItemNode()
|
||||||
node.contentSize = boundingSize
|
node.contentSize = self.expanded ? expandedBoundingSize : boundingSize
|
||||||
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||||
node.inputNodeInteraction = self.inputNodeInteraction
|
node.inputNodeInteraction = self.inputNodeInteraction
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(node, {
|
completion(node, {
|
||||||
return (nil, { _ in
|
return (nil, { _ in
|
||||||
node.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, theme: self.theme)
|
node.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, theme: self.theme, expanded: self.expanded)
|
||||||
node.updateAppearanceTransition(transition: .immediate)
|
node.updateAppearanceTransition(transition: .immediate)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -57,8 +59,8 @@ final class ChatMediaInputStickerPackItem: ListViewItem {
|
|||||||
|
|
||||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in
|
completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in
|
||||||
(node() as? ChatMediaInputStickerPackItemNode)?.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, theme: self.theme)
|
(node() as? ChatMediaInputStickerPackItemNode)?.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, theme: self.theme, expanded: self.expanded)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,20 +70,26 @@ final class ChatMediaInputStickerPackItem: ListViewItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let boundingSize = CGSize(width: 41.0, height: 41.0)
|
private let boundingSize = CGSize(width: 72.0, height: 41.0)
|
||||||
private let boundingImageSize = CGSize(width: 28.0, height: 28.0)
|
private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0)
|
||||||
private let highlightSize = CGSize(width: 35.0, height: 35.0)
|
private let boundingImageSize = CGSize(width: 45.0, height: 45.0)
|
||||||
private let verticalOffset: CGFloat = 3.0
|
private let boundingImageScale: CGFloat = 0.625
|
||||||
|
private let highlightSize = CGSize(width: 56.0, height: 56.0)
|
||||||
|
private let verticalOffset: CGFloat = -3.0
|
||||||
|
|
||||||
final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||||
|
private let containerNode: ASDisplayNode
|
||||||
|
private let scalingNode: ASDisplayNode
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
private var animatedStickerNode: AnimatedStickerNode?
|
private var animatedStickerNode: AnimatedStickerNode?
|
||||||
private var placeholderNode: StickerShimmerEffectNode?
|
private var placeholderNode: StickerShimmerEffectNode?
|
||||||
private let highlightNode: ASImageNode
|
private let highlightNode: ASImageNode
|
||||||
|
private let titleNode: ImmediateTextNode
|
||||||
|
|
||||||
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||||
var currentCollectionId: ItemCollectionId?
|
var currentCollectionId: ItemCollectionId?
|
||||||
private var currentThumbnailItem: StickerPackThumbnailItem?
|
private var currentThumbnailItem: StickerPackThumbnailItem?
|
||||||
|
private var currentExpanded = false
|
||||||
private var theme: PresentationTheme?
|
private var theme: PresentationTheme?
|
||||||
|
|
||||||
private let stickerFetchedDisposable = MetaDisposable()
|
private let stickerFetchedDisposable = MetaDisposable()
|
||||||
@ -102,6 +110,11 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self.containerNode = ASDisplayNode()
|
||||||
|
self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
|
||||||
|
self.scalingNode = ASDisplayNode()
|
||||||
|
|
||||||
self.highlightNode = ASImageNode()
|
self.highlightNode = ASImageNode()
|
||||||
self.highlightNode.isLayerBacked = true
|
self.highlightNode.isLayerBacked = true
|
||||||
self.highlightNode.isHidden = true
|
self.highlightNode.isHidden = true
|
||||||
@ -110,18 +123,19 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||||
|
|
||||||
self.placeholderNode = StickerShimmerEffectNode()
|
self.placeholderNode = StickerShimmerEffectNode()
|
||||||
self.placeholderNode?.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
|
||||||
|
|
||||||
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset - UIScreenPixel, y: floor((boundingSize.height - highlightSize.height) / 2.0) - UIScreenPixel), size: highlightSize)
|
self.titleNode = ImmediateTextNode()
|
||||||
|
|
||||||
self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.highlightNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.addSubnode(self.imageNode)
|
self.containerNode.addSubnode(self.scalingNode)
|
||||||
|
|
||||||
|
self.scalingNode.addSubnode(self.highlightNode)
|
||||||
|
self.scalingNode.addSubnode(self.titleNode)
|
||||||
|
self.scalingNode.addSubnode(self.imageNode)
|
||||||
if let placeholderNode = self.placeholderNode {
|
if let placeholderNode = self.placeholderNode {
|
||||||
self.addSubnode(placeholderNode)
|
self.scalingNode.addSubnode(placeholderNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstTime = true
|
var firstTime = true
|
||||||
@ -157,11 +171,13 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, theme: PresentationTheme) {
|
func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, theme: PresentationTheme, expanded: Bool) {
|
||||||
self.currentCollectionId = collectionId
|
self.currentCollectionId = collectionId
|
||||||
|
|
||||||
|
var themeUpdated = false
|
||||||
if self.theme !== theme {
|
if self.theme !== theme {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
themeUpdated = true
|
||||||
|
|
||||||
self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme)
|
self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme)
|
||||||
}
|
}
|
||||||
@ -186,22 +202,26 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if themeUpdated || self.titleNode.attributedText?.string != info.title {
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: info.title, font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height)
|
||||||
|
var imageSize = boundingImageSize
|
||||||
|
|
||||||
if self.currentThumbnailItem != thumbnailItem {
|
if self.currentThumbnailItem != thumbnailItem {
|
||||||
self.currentThumbnailItem = thumbnailItem
|
self.currentThumbnailItem = thumbnailItem
|
||||||
if let thumbnailItem = thumbnailItem {
|
if let thumbnailItem = thumbnailItem {
|
||||||
switch thumbnailItem {
|
switch thumbnailItem {
|
||||||
case let .still(representation):
|
case let .still(representation):
|
||||||
let imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize)
|
imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize)
|
||||||
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
||||||
imageApply()
|
imageApply()
|
||||||
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true))
|
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true))
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
|
||||||
case let .animated(resource):
|
case let .animated(resource):
|
||||||
let imageSize = boundingImageSize
|
|
||||||
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
||||||
imageApply()
|
imageApply()
|
||||||
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: resource, animated: true, nilIfEmpty: true))
|
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: resource, animated: true, nilIfEmpty: true))
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
|
||||||
|
|
||||||
let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false
|
let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false
|
||||||
self.imageNode.isHidden = loopAnimatedStickers
|
self.imageNode.isHidden = loopAnimatedStickers
|
||||||
@ -212,18 +232,14 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
} else {
|
} else {
|
||||||
animatedStickerNode = AnimatedStickerNode()
|
animatedStickerNode = AnimatedStickerNode()
|
||||||
self.animatedStickerNode = animatedStickerNode
|
self.animatedStickerNode = animatedStickerNode
|
||||||
animatedStickerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
|
||||||
if let placeholderNode = self.placeholderNode {
|
if let placeholderNode = self.placeholderNode {
|
||||||
self.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode)
|
self.scalingNode.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode)
|
||||||
} else {
|
} else {
|
||||||
self.addSubnode(animatedStickerNode)
|
self.scalingNode.addSubnode(animatedStickerNode)
|
||||||
}
|
}
|
||||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 80, height: 80, mode: .cached)
|
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 80, height: 80, mode: .cached)
|
||||||
}
|
}
|
||||||
animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers
|
animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers
|
||||||
if let animatedStickerNode = self.animatedStickerNode {
|
|
||||||
animatedStickerNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let resourceReference = resourceReference {
|
if let resourceReference = resourceReference {
|
||||||
self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: resourceReference).start())
|
self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: resourceReference).start())
|
||||||
@ -232,14 +248,41 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
if let placeholderNode = self.placeholderNode {
|
if let placeholderNode = self.placeholderNode {
|
||||||
let imageSize = boundingImageSize
|
let imageSize = boundingImageSize
|
||||||
let placeholderFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
|
||||||
placeholderNode.frame = placeholderFrame
|
|
||||||
|
|
||||||
placeholderNode.update(backgroundColor: nil, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), data: info.immediateThumbnailData, size: imageSize, imageSize: CGSize(width: 100.0, height: 100.0))
|
placeholderNode.update(backgroundColor: nil, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), data: info.immediateThumbnailData, size: imageSize, imageSize: CGSize(width: 100.0, height: 100.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateIsHighlighted()
|
self.updateIsHighlighted()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize)
|
||||||
|
self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize)
|
||||||
|
|
||||||
|
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||||
|
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||||
|
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||||
|
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||||
|
|
||||||
|
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||||
|
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||||
|
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||||
|
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||||
|
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||||
|
expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001)
|
||||||
|
|
||||||
|
self.currentExpanded = expanded
|
||||||
|
|
||||||
|
self.imageNode.bounds = CGRect(origin: CGPoint(), size: imageSize)
|
||||||
|
self.imageNode.position = CGPoint(x: expandedBoundingSize.height / 2.0, y: expandedBoundingSize.width / 2.0)
|
||||||
|
if let animatedStickerNode = self.animatedStickerNode {
|
||||||
|
animatedStickerNode.frame = self.imageNode.frame
|
||||||
|
animatedStickerNode.updateLayout(size: self.imageNode.frame.size)
|
||||||
|
}
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
placeholderNode.bounds = CGRect(origin: CGPoint(), size: boundingImageSize)
|
||||||
|
placeholderNode.position = self.imageNode.position
|
||||||
|
}
|
||||||
|
expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.imageNode.position.x - highlightSize.width / 2.0, y: self.imageNode.position.y - highlightSize.height / 2.0), size: highlightSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
|||||||
@ -12,29 +12,31 @@ final class ChatMediaInputTrendingItem: ListViewItem {
|
|||||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||||
let selectedItem: () -> Void
|
let selectedItem: () -> Void
|
||||||
let elevated: Bool
|
let elevated: Bool
|
||||||
|
let expanded: Bool
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
|
||||||
var selectable: Bool {
|
var selectable: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(inputNodeInteraction: ChatMediaInputNodeInteraction, elevated: Bool, theme: PresentationTheme, selected: @escaping () -> Void) {
|
init(inputNodeInteraction: ChatMediaInputNodeInteraction, elevated: Bool, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) {
|
||||||
self.inputNodeInteraction = inputNodeInteraction
|
self.inputNodeInteraction = inputNodeInteraction
|
||||||
self.elevated = elevated
|
self.elevated = elevated
|
||||||
self.selectedItem = selected
|
self.selectedItem = selected
|
||||||
|
self.expanded = expanded
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
async {
|
async {
|
||||||
let node = ChatMediaInputTrendingItemNode()
|
let node = ChatMediaInputTrendingItemNode()
|
||||||
node.contentSize = CGSize(width: 41.0, height: 41.0)
|
node.contentSize = self.expanded ? expandedBoundingSize : boundingSize
|
||||||
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||||
node.inputNodeInteraction = self.inputNodeInteraction
|
node.inputNodeInteraction = self.inputNodeInteraction
|
||||||
node.updateTheme(elevated: self.elevated, theme: self.theme)
|
|
||||||
node.updateIsHighlighted()
|
node.updateIsHighlighted()
|
||||||
node.updateAppearanceTransition(transition: .immediate)
|
node.updateAppearanceTransition(transition: .immediate)
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
|
node.updateTheme(elevated: self.elevated, theme: self.theme, expanded: self.expanded)
|
||||||
completion(node, {
|
completion(node, {
|
||||||
return (nil, { _ in })
|
return (nil, { _ in })
|
||||||
})
|
})
|
||||||
@ -44,8 +46,8 @@ final class ChatMediaInputTrendingItem: ListViewItem {
|
|||||||
|
|
||||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in
|
completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in
|
||||||
(node() as? ChatMediaInputTrendingItemNode)?.updateTheme(elevated: self.elevated, theme: self.theme)
|
(node() as? ChatMediaInputTrendingItemNode)?.updateTheme(elevated: self.elevated, theme: self.theme, expanded: self.expanded)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,14 +57,20 @@ final class ChatMediaInputTrendingItem: ListViewItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let boundingSize = CGSize(width: 41.0, height: 41.0)
|
private let boundingSize = CGSize(width: 72.0, height: 41.0)
|
||||||
private let boundingImageSize = CGSize(width: 30.0, height: 30.0)
|
private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0)
|
||||||
private let highlightSize = CGSize(width: 35.0, height: 35.0)
|
private let boundingImageScale: CGFloat = 0.625
|
||||||
|
private let highlightSize = CGSize(width: 56.0, height: 56.0)
|
||||||
private let verticalOffset: CGFloat = 3.0 + UIScreenPixel
|
private let verticalOffset: CGFloat = 3.0 + UIScreenPixel
|
||||||
|
|
||||||
final class ChatMediaInputTrendingItemNode: ListViewItemNode {
|
final class ChatMediaInputTrendingItemNode: ListViewItemNode {
|
||||||
|
private let containerNode: ASDisplayNode
|
||||||
|
private let scalingNode: ASDisplayNode
|
||||||
private let imageNode: ASImageNode
|
private let imageNode: ASImageNode
|
||||||
private let highlightNode: ASImageNode
|
private let highlightNode: ASImageNode
|
||||||
|
private let titleNode: ImmediateTextNode
|
||||||
|
|
||||||
|
private var currentExpanded = false
|
||||||
|
|
||||||
var currentCollectionId: ItemCollectionId?
|
var currentCollectionId: ItemCollectionId?
|
||||||
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||||
@ -73,37 +81,43 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode {
|
|||||||
let badgeBackground: ASImageNode
|
let badgeBackground: ASImageNode
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self.containerNode = ASDisplayNode()
|
||||||
|
self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
|
||||||
|
self.scalingNode = ASDisplayNode()
|
||||||
|
|
||||||
self.highlightNode = ASImageNode()
|
self.highlightNode = ASImageNode()
|
||||||
self.highlightNode.isLayerBacked = true
|
self.highlightNode.isLayerBacked = true
|
||||||
self.highlightNode.isHidden = true
|
self.highlightNode.isHidden = true
|
||||||
|
|
||||||
self.imageNode = ASImageNode()
|
self.imageNode = ASImageNode()
|
||||||
self.imageNode.isLayerBacked = true
|
self.imageNode.isLayerBacked = true
|
||||||
self.imageNode.contentMode = .center
|
|
||||||
self.imageNode.contentsScale = UIScreenScale
|
|
||||||
|
|
||||||
self.badgeBackground = ASImageNode()
|
self.badgeBackground = ASImageNode()
|
||||||
self.badgeBackground.displaysAsynchronously = false
|
self.badgeBackground.displaysAsynchronously = false
|
||||||
self.badgeBackground.displayWithoutProcessing = true
|
self.badgeBackground.displayWithoutProcessing = true
|
||||||
self.badgeBackground.isHidden = true
|
self.badgeBackground.isHidden = true
|
||||||
|
|
||||||
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - highlightSize.height) / 2.0)), size: highlightSize)
|
self.titleNode = ImmediateTextNode()
|
||||||
|
|
||||||
self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.highlightNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.addSubnode(self.imageNode)
|
self.containerNode.addSubnode(self.scalingNode)
|
||||||
self.addSubnode(self.badgeBackground)
|
|
||||||
|
self.scalingNode.addSubnode(self.highlightNode)
|
||||||
|
self.scalingNode.addSubnode(self.titleNode)
|
||||||
|
self.scalingNode.addSubnode(self.imageNode)
|
||||||
|
self.scalingNode.addSubnode(self.badgeBackground)
|
||||||
|
|
||||||
self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0)
|
self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
func updateTheme(elevated: Bool, theme: PresentationTheme, expanded: Bool) {
|
||||||
}
|
let imageSize = CGSize(width: 26.0 * 1.85, height: 26.0 * 1.85)
|
||||||
|
let imageFrame = CGRect(origin: CGPoint(x: floor((expandedBoundingSize.width - imageSize.width) / 2.0), y: floor((expandedBoundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
||||||
|
self.imageNode.frame = imageFrame
|
||||||
|
|
||||||
func updateTheme(elevated: Bool, theme: PresentationTheme) {
|
|
||||||
if self.theme !== theme {
|
if self.theme !== theme {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
@ -111,19 +125,37 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode {
|
|||||||
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingIconImage(theme)
|
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingIconImage(theme)
|
||||||
self.badgeBackground.image = generateFilledCircleImage(diameter: 10.0, color: theme.chat.inputPanel.mediaRecordingDotColor)
|
self.badgeBackground.image = generateFilledCircleImage(diameter: 10.0, color: theme.chat.inputPanel.mediaRecordingDotColor)
|
||||||
|
|
||||||
let imageSize = CGSize(width: 26.0, height: 26.0)
|
|
||||||
let imageFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
|
||||||
self.imageNode.frame = imageFrame
|
|
||||||
|
|
||||||
if let image = self.badgeBackground.image {
|
if let image = self.badgeBackground.image {
|
||||||
self.badgeBackground.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - image.size.width - 1.0, y: imageFrame.maxY - image.size.width + 1.0), size: image.size)
|
self.badgeBackground.frame = CGRect(origin: CGPoint(x: floor(imageFrame.maxX - image.size.width - 7.0), y: 18.0), size: image.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: "Trending", font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.elevated != elevated {
|
if self.elevated != elevated {
|
||||||
self.elevated = elevated
|
self.elevated = elevated
|
||||||
self.badgeBackground.isHidden = !self.elevated
|
self.badgeBackground.isHidden = !self.elevated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize)
|
||||||
|
self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize)
|
||||||
|
|
||||||
|
let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height)
|
||||||
|
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||||
|
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||||
|
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||||
|
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||||
|
|
||||||
|
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||||
|
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||||
|
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||||
|
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||||
|
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||||
|
|
||||||
|
self.currentExpanded = expanded
|
||||||
|
|
||||||
|
expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.imageNode.position.x - highlightSize.width / 2.0, y: self.imageNode.position.y - highlightSize.height / 2.0), size: highlightSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateIsHighlighted() {
|
func updateIsHighlighted() {
|
||||||
|
|||||||
@ -1278,7 +1278,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
for attribute in item.content.firstMessage.attributes {
|
for attribute in item.content.firstMessage.attributes {
|
||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
openPeerId = attribute.messageId.peerId
|
openPeerId = attribute.messageId.peerId
|
||||||
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true), peekData: nil)
|
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true, timecode: nil), peekData: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3012,7 +3012,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
for attribute in item.content.firstMessage.attributes {
|
for attribute in item.content.firstMessage.attributes {
|
||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
openPeerId = attribute.messageId.peerId
|
openPeerId = attribute.messageId.peerId
|
||||||
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true), peekData: nil)
|
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true, timecode: nil), peekData: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -731,7 +731,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
|
|||||||
for attribute in item.content.firstMessage.attributes {
|
for attribute in item.content.firstMessage.attributes {
|
||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
openPeerId = attribute.messageId.peerId
|
openPeerId = attribute.messageId.peerId
|
||||||
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true), peekData: nil)
|
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true, timecode: nil), peekData: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -884,7 +884,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
for attribute in item.content.firstMessage.attributes {
|
for attribute in item.content.firstMessage.attributes {
|
||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
openPeerId = attribute.messageId.peerId
|
openPeerId = attribute.messageId.peerId
|
||||||
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true), peekData: nil)
|
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true, timecode: nil), peekData: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -905,13 +905,13 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
break
|
break
|
||||||
case .groupBotStart:
|
case .groupBotStart:
|
||||||
break
|
break
|
||||||
case let .channelMessage(peerId, messageId):
|
case let .channelMessage(peerId, messageId, timecode):
|
||||||
if let navigationController = strongSelf.getNavigationController() {
|
if let navigationController = strongSelf.getNavigationController() {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true)))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true, timecode: timecode)))
|
||||||
}
|
}
|
||||||
case let .replyThreadMessage(replyThreadMessage, messageId):
|
case let .replyThreadMessage(replyThreadMessage, messageId):
|
||||||
if let navigationController = strongSelf.getNavigationController() {
|
if let navigationController = strongSelf.getNavigationController() {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadMessage), subject: .message(id: messageId, highlight: true)))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadMessage), subject: .message(id: messageId, highlight: true, timecode: nil)))
|
||||||
}
|
}
|
||||||
case let .stickerPack(name):
|
case let .stickerPack(name):
|
||||||
let packReference: StickerPackReference = .name(name)
|
let packReference: StickerPackReference = .name(name)
|
||||||
|
|||||||
@ -205,7 +205,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||||||
switch item.content {
|
switch item.content {
|
||||||
case let .peer(peer):
|
case let .peer(peer):
|
||||||
if let message = peer.messages.first {
|
if let message = peer.messages.first {
|
||||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peer.peerId), subject: .message(id: message.id, highlight: true), botStart: nil, mode: .standard(previewing: true))
|
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peer.peerId), subject: .message(id: message.id, highlight: true, timecode: nil), botStart: nil, mode: .standard(previewing: true))
|
||||||
chatController.canReadHistory.set(false)
|
chatController.canReadHistory.set(false)
|
||||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single([]), reactionItems: [], gesture: gesture)
|
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single([]), reactionItems: [], gesture: gesture)
|
||||||
presentInGlobalOverlay(contextController)
|
presentInGlobalOverlay(contextController)
|
||||||
|
|||||||
@ -608,24 +608,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
|||||||
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasUnreadTrending: nil, theme: theme, hasGifs: false, hasSettings: false)
|
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasUnreadTrending: nil, theme: theme, hasGifs: false, hasSettings: false)
|
||||||
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasSearch: false, hasAccessories: false, strings: strings, theme: theme)
|
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasSearch: false, hasAccessories: false, strings: strings, theme: theme)
|
||||||
|
|
||||||
// if view.higher == nil {
|
|
||||||
// var hasTopSeparator = true
|
|
||||||
// if gridEntries.count == 1, case .search = gridEntries[0] {
|
|
||||||
// hasTopSeparator = false
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var index = 0
|
|
||||||
// for item in trendingPacks {
|
|
||||||
// if !installedPacks.contains(item.info.id) {
|
|
||||||
// gridEntries.append(.trending(TrendingPanePackEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread, topSeparator: hasTopSeparator)))
|
|
||||||
// hasTopSeparator = true
|
|
||||||
// index += 1
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
let (previousPanelEntries, previousGridEntries) = previousStickerEntries.swap((panelEntries, gridEntries))
|
let (previousPanelEntries, previousGridEntries) = previousStickerEntries.swap((panelEntries, gridEntries))
|
||||||
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: stickersInputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: stickersInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: stickersInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: stickersInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.disposable.set((stickerTransitions
|
self.disposable.set((stickerTransitions
|
||||||
@ -660,7 +644,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
|||||||
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasSearch: false, hasAccessories: false, strings: strings, theme: theme)
|
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasSearch: false, hasAccessories: false, strings: strings, theme: theme)
|
||||||
|
|
||||||
let (previousPanelEntries, previousGridEntries) = previousMaskEntries.swap((panelEntries, gridEntries))
|
let (previousPanelEntries, previousGridEntries) = previousMaskEntries.swap((panelEntries, gridEntries))
|
||||||
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: masksInputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: masksInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: masksInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: masksInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.maskDisposable.set((maskTransitions
|
self.maskDisposable.set((maskTransitions
|
||||||
|
|||||||
@ -417,7 +417,7 @@ public final class MediaManagerImpl: NSObject, MediaManager {
|
|||||||
if let (account, stateOrLoading, type) = accountStateAndType {
|
if let (account, stateOrLoading, type) = accountStateAndType {
|
||||||
switch type {
|
switch type {
|
||||||
case .music:
|
case .music:
|
||||||
minimumStoreDuration = 15.0 * 60.0
|
minimumStoreDuration = 10.0 * 60.0
|
||||||
case .voice:
|
case .voice:
|
||||||
minimumStoreDuration = 5.0 * 60.0
|
minimumStoreDuration = 5.0 * 60.0
|
||||||
case .file:
|
case .file:
|
||||||
|
|||||||
@ -20,10 +20,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
|||||||
if let updateTextInputState = params.updateTextInputState {
|
if let updateTextInputState = params.updateTextInputState {
|
||||||
controller.updateTextInputState(updateTextInputState)
|
controller.updateTextInputState(updateTextInputState)
|
||||||
}
|
}
|
||||||
if let subject = params.subject, case let .message(messageId, _) = subject {
|
if let subject = params.subject, case let .message(messageId, _, timecode) = subject {
|
||||||
let navigationController = params.navigationController
|
let navigationController = params.navigationController
|
||||||
let animated = params.animated
|
let animated = params.animated
|
||||||
controller.navigateToMessage(messageLocation: .id(messageId), animated: isFirst, completion: { [weak navigationController, weak controller] in
|
controller.navigateToMessage(messageLocation: .id(messageId, timecode), animated: isFirst, completion: { [weak navigationController, weak controller] in
|
||||||
if let navigationController = navigationController, let controller = controller {
|
if let navigationController = navigationController, let controller = controller {
|
||||||
let _ = navigationController.popToViewController(controller, animated: animated)
|
let _ = navigationController.popToViewController(controller, animated: animated)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,8 +97,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
}
|
}
|
||||||
dismissInput()
|
dismissInput()
|
||||||
navigationController?.pushViewController(controller)
|
navigationController?.pushViewController(controller)
|
||||||
case let .channelMessage(peerId, messageId):
|
case let .channelMessage(peerId, messageId, timecode):
|
||||||
openPeer(peerId, .chat(textInputState: nil, subject: .message(id: messageId, highlight: true), peekData: nil))
|
openPeer(peerId, .chat(textInputState: nil, subject: .message(id: messageId, highlight: true, timecode: timecode), peekData: nil))
|
||||||
case let .replyThreadMessage(replyThreadMessage, messageId):
|
case let .replyThreadMessage(replyThreadMessage, messageId):
|
||||||
if let navigationController = navigationController {
|
if let navigationController = navigationController {
|
||||||
let _ = ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in
|
let _ = ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in
|
||||||
|
|||||||
@ -187,7 +187,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
self.isGlobalSearch = false
|
self.isGlobalSearch = false
|
||||||
}
|
}
|
||||||
|
|
||||||
self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, source: source, subject: .message(id: initialMessageId, highlight: true), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
|
self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, source: source, subject: .message(id: initialMessageId, highlight: true, timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -263,7 +263,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
self.contentNode.addSubnode(self.historyNode)
|
self.contentNode.addSubnode(self.historyNode)
|
||||||
self.contentNode.addSubnode(self.controlsNode)
|
self.contentNode.addSubnode(self.controlsNode)
|
||||||
|
|
||||||
self.historyNode.beganInteractiveDragging = { [weak self] in
|
self.historyNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.controlsNode.collapse()
|
self?.controlsNode.collapse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -528,7 +528,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
}
|
}
|
||||||
|
|
||||||
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
||||||
let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(id: messageId, highlight: true), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
|
let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(id: messageId, highlight: true, timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
|
||||||
historyNode.preloadPages = true
|
historyNode.preloadPages = true
|
||||||
historyNode.stackFromBottom = true
|
historyNode.stackFromBottom = true
|
||||||
historyNode.updateFloatingHeaderOffset = { [weak self] offset, _ in
|
historyNode.updateFloatingHeaderOffset = { [weak self] offset, _ in
|
||||||
@ -628,7 +628,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.historyNode.beganInteractiveDragging = { [weak self] in
|
self.historyNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.controlsNode.collapse()
|
self?.controlsNode.collapse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1738,7 +1738,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||||
let currentPeerId = strongSelf.peerId
|
let currentPeerId = strongSelf.peerId
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(currentPeerId), subject: .message(id: message.id, highlight: true), keepStack: .always, useExisting: false, purposefulAction: {
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(currentPeerId), subject: .message(id: message.id, highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {
|
||||||
var viewControllers = navigationController.viewControllers
|
var viewControllers = navigationController.viewControllers
|
||||||
var indexesToRemove = Set<Int>()
|
var indexesToRemove = Set<Int>()
|
||||||
var keptCurrentChatController = false
|
var keptCurrentChatController = false
|
||||||
@ -1884,7 +1884,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||||
let currentPeerId = strongSelf.peerId
|
let currentPeerId = strongSelf.peerId
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(currentPeerId), subject: .message(id: message.id, highlight: true), keepStack: .always, useExisting: false, purposefulAction: {
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(currentPeerId), subject: .message(id: message.id, highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {
|
||||||
var viewControllers = navigationController.viewControllers
|
var viewControllers = navigationController.viewControllers
|
||||||
var indexesToRemove = Set<Int>()
|
var indexesToRemove = Set<Int>()
|
||||||
var keptCurrentChatController = false
|
var keptCurrentChatController = false
|
||||||
@ -2775,7 +2775,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
|
|
||||||
if copyUsername, let username = user.username, !username.isEmpty {
|
if copyUsername, let username = user.username, !username.isEmpty {
|
||||||
actions.append(ContextMenuAction(content: .text(title: strongSelf.presentationData.strings.Settings_CopyUsername, accessibilityLabel: strongSelf.presentationData.strings.Settings_CopyUsername), action: { [weak self] in
|
actions.append(ContextMenuAction(content: .text(title: strongSelf.presentationData.strings.Settings_CopyUsername, accessibilityLabel: strongSelf.presentationData.strings.Settings_CopyUsername), action: { [weak self] in
|
||||||
UIPasteboard.general.string = username
|
UIPasteboard.general.string = "@\(username)"
|
||||||
|
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|||||||
@ -58,9 +58,9 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
|
|||||||
context.sharedContext.applicationBindings.openUrl(url)
|
context.sharedContext.applicationBindings.openUrl(url)
|
||||||
case let .peer(peerId, navigation):
|
case let .peer(peerId, navigation):
|
||||||
openResolvedPeerImpl(peerId, navigation)
|
openResolvedPeerImpl(peerId, navigation)
|
||||||
case let .channelMessage(peerId, messageId):
|
case let .channelMessage(peerId, messageId, timecode):
|
||||||
if let navigationController = controller.navigationController as? NavigationController {
|
if let navigationController = controller.navigationController as? NavigationController {
|
||||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true)))
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true, timecode: timecode)))
|
||||||
}
|
}
|
||||||
case let .replyThreadMessage(replyThreadMessage, messageId):
|
case let .replyThreadMessage(replyThreadMessage, messageId):
|
||||||
if let navigationController = controller.navigationController as? NavigationController {
|
if let navigationController = controller.navigationController as? NavigationController {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ private let baseTelegraPhPaths = ["telegra.ph/", "te.legra.ph/", "graph.org/", "
|
|||||||
public enum ParsedInternalPeerUrlParameter {
|
public enum ParsedInternalPeerUrlParameter {
|
||||||
case botStart(String)
|
case botStart(String)
|
||||||
case groupBotStart(String)
|
case groupBotStart(String)
|
||||||
case channelMessage(Int32)
|
case channelMessage(Int32, Double?)
|
||||||
case replyThread(Int32, Int32)
|
case replyThread(Int32, Int32)
|
||||||
case voiceChat(String?)
|
case voiceChat(String?)
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@ public enum ParsedInternalPeerUrlParameter {
|
|||||||
public enum ParsedInternalUrl {
|
public enum ParsedInternalUrl {
|
||||||
case peerName(String, ParsedInternalPeerUrlParameter?)
|
case peerName(String, ParsedInternalPeerUrlParameter?)
|
||||||
case peerId(PeerId)
|
case peerId(PeerId)
|
||||||
case privateMessage(messageId: MessageId, threadId: Int32?)
|
case privateMessage(messageId: MessageId, threadId: Int32?, timecode: Double?)
|
||||||
case stickerPack(String)
|
case stickerPack(String)
|
||||||
case join(String)
|
case join(String)
|
||||||
case localization(String)
|
case localization(String)
|
||||||
@ -281,37 +281,44 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
} else if pathComponents.count == 3 && pathComponents[0] == "c" {
|
} else if pathComponents.count == 3 && pathComponents[0] == "c" {
|
||||||
if let channelId = Int32(pathComponents[1]), let messageId = Int32(pathComponents[2]) {
|
if let channelId = Int32(pathComponents[1]), let messageId = Int32(pathComponents[2]) {
|
||||||
var threadId: Int32?
|
var threadId: Int32?
|
||||||
|
var timecode: Double?
|
||||||
if let queryItems = components.queryItems {
|
if let queryItems = components.queryItems {
|
||||||
for queryItem in queryItems {
|
for queryItem in queryItems {
|
||||||
if let value = queryItem.value {
|
if let value = queryItem.value {
|
||||||
if queryItem.name == "thread" {
|
if queryItem.name == "thread" {
|
||||||
if let intValue = Int32(value) {
|
if let intValue = Int32(value) {
|
||||||
threadId = intValue
|
threadId = intValue
|
||||||
break
|
}
|
||||||
|
} else if queryItem.name == "t" {
|
||||||
|
if let doubleValue = Double(value) {
|
||||||
|
timecode = doubleValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return .privateMessage(messageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt32Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId)
|
return .privateMessage(messageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt32Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId, timecode: timecode)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else if let value = Int32(pathComponents[1]) {
|
} else if let value = Int32(pathComponents[1]) {
|
||||||
var threadId: Int32?
|
var threadId: Int32?
|
||||||
var commentId: Int32?
|
var commentId: Int32?
|
||||||
|
var timecode: Double?
|
||||||
if let queryItems = components.queryItems {
|
if let queryItems = components.queryItems {
|
||||||
for queryItem in queryItems {
|
for queryItem in queryItems {
|
||||||
if let value = queryItem.value {
|
if let value = queryItem.value {
|
||||||
if queryItem.name == "thread" {
|
if queryItem.name == "thread" {
|
||||||
if let intValue = Int32(value) {
|
if let intValue = Int32(value) {
|
||||||
threadId = intValue
|
threadId = intValue
|
||||||
break
|
|
||||||
}
|
}
|
||||||
} else if queryItem.name == "comment" {
|
} else if queryItem.name == "comment" {
|
||||||
if let intValue = Int32(value) {
|
if let intValue = Int32(value) {
|
||||||
commentId = intValue
|
commentId = intValue
|
||||||
break
|
}
|
||||||
|
} else if queryItem.name == "t" {
|
||||||
|
if let doubleValue = Double(value) {
|
||||||
|
timecode = doubleValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,7 +329,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
} else if let commentId = commentId {
|
} else if let commentId = commentId {
|
||||||
return .peerName(peerName, .replyThread(value, commentId))
|
return .peerName(peerName, .replyThread(value, commentId))
|
||||||
} else {
|
} else {
|
||||||
return .peerName(peerName, .channelMessage(value))
|
return .peerName(peerName, .channelMessage(value, timecode))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
@ -357,8 +364,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||||||
return .single(.botStart(peerId: peer.id, payload: payload))
|
return .single(.botStart(peerId: peer.id, payload: payload))
|
||||||
case let .groupBotStart(payload):
|
case let .groupBotStart(payload):
|
||||||
return .single(.groupBotStart(peerId: peer.id, payload: payload))
|
return .single(.groupBotStart(peerId: peer.id, payload: payload))
|
||||||
case let .channelMessage(id):
|
case let .channelMessage(id, timecode):
|
||||||
return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)))
|
return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode))
|
||||||
case let .replyThread(id, replyId):
|
case let .replyThread(id, replyId):
|
||||||
let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)
|
let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)
|
||||||
return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil)
|
return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil)
|
||||||
@ -368,7 +375,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||||||
}
|
}
|
||||||
|> map { result -> ResolvedUrl? in
|
|> map { result -> ResolvedUrl? in
|
||||||
guard let result = result else {
|
guard let result = result else {
|
||||||
return .channelMessage(peerId: peer.id, messageId: replyThreadMessageId)
|
return .channelMessage(peerId: peer.id, messageId: replyThreadMessageId, timecode: nil)
|
||||||
}
|
}
|
||||||
return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId))
|
return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId))
|
||||||
}
|
}
|
||||||
@ -397,7 +404,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||||||
return .single(.inaccessiblePeer)
|
return .single(.inaccessiblePeer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .privateMessage(messageId, threadId):
|
case let .privateMessage(messageId, threadId, timecode):
|
||||||
return context.account.postbox.transaction { transaction -> Peer? in
|
return context.account.postbox.transaction { transaction -> Peer? in
|
||||||
return transaction.getPeer(messageId.peerId)
|
return transaction.getPeer(messageId.peerId)
|
||||||
}
|
}
|
||||||
@ -420,12 +427,12 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||||||
}
|
}
|
||||||
|> map { result -> ResolvedUrl? in
|
|> map { result -> ResolvedUrl? in
|
||||||
guard let result = result else {
|
guard let result = result else {
|
||||||
return .channelMessage(peerId: foundPeer.id, messageId: replyThreadMessageId)
|
return .channelMessage(peerId: foundPeer.id, messageId: replyThreadMessageId, timecode: timecode)
|
||||||
}
|
}
|
||||||
return .replyThreadMessage(replyThreadMessage: result, messageId: messageId)
|
return .replyThreadMessage(replyThreadMessage: result, messageId: messageId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(.peer(foundPeer.id, .chat(textInputState: nil, subject: .message(id: messageId, highlight: true), peekData: nil)))
|
return .single(.peer(foundPeer.id, .chat(textInputState: nil, subject: .message(id: messageId, highlight: true, timecode: timecode), peekData: nil)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(.inaccessiblePeer)
|
return .single(.inaccessiblePeer)
|
||||||
|
|||||||
@ -31,7 +31,7 @@ public func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concea
|
|||||||
latin.insert(charactersIn: "a"..."z")
|
latin.insert(charactersIn: "a"..."z")
|
||||||
latin.insert(charactersIn: "0"..."9")
|
latin.insert(charactersIn: "0"..."9")
|
||||||
var punctuation = CharacterSet()
|
var punctuation = CharacterSet()
|
||||||
punctuation.insert(charactersIn: ".-/+_")
|
punctuation.insert(charactersIn: ".-/+_?=")
|
||||||
var hasLatin = false
|
var hasLatin = false
|
||||||
var hasNonLatin = false
|
var hasNonLatin = false
|
||||||
for c in rawHost {
|
for c in rawHost {
|
||||||
|
|||||||
@ -284,7 +284,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.recentQueriesNode.beganInteractiveDragging = { [weak self] in
|
self.recentQueriesNode.beganInteractiveDragging = { [weak self] _ in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user