diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 99f95956be..373ec545ed 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -171,7 +171,7 @@ public enum ResolvedUrl { case inaccessiblePeer case botStart(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 stickerPack(name: String) case instantView(TelegramMediaWebpage, String?) diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 1ddd5bbe7f..a6d2dfe764 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -339,7 +339,7 @@ public struct ChatTextInputStateText: PostboxCoding, Equatable { } public enum ChatControllerSubject: Equatable { - case message(id: MessageId, highlight: Bool) + case message(id: MessageId, highlight: Bool, timecode: Double?) case scheduledMessages case pinnedMessages(id: MessageId?) } diff --git a/submodules/AppLock/Sources/AppLock.swift b/submodules/AppLock/Sources/AppLock.swift index b771fc82de..993663bce7 100644 --- a/submodules/AppLock/Sources/AppLock.swift +++ b/submodules/AppLock/Sources/AppLock.swift @@ -184,7 +184,7 @@ public final class AppLockContextImpl: AppLockContext { } passcodeController.ensureInputFocused() } 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() { return lockViewFrame } else { diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 0bdf7ae0cc..9270cd6585 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -68,7 +68,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch items.append(.separator) case .recentSearch: items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in - let _ = (removeRecentlySearchedPeer(postbox: context.account.postbox, peerId: peerId) + let _ = (context.engine.peers.removeRecentlySearchedPeer(peerId: peerId) |> deliverOnMainQueue).start(completed: { f(.default) }) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 61dff0358e..79a9730c38 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -692,7 +692,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass { 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 { self?.deactivateSearch(animated: false) } @@ -862,7 +862,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } else { var subject: ChatControllerSubject? 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)) chatController.canReadHistory.set(false) @@ -2004,11 +2004,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController |> delay(0.8, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() - let signal: Signal = strongSelf.context.account.postbox.transaction { transaction -> Void in - for peerId in peerIds { - removePeerChat(account: context.account, transaction: transaction, mediaBox: context.account.postbox.mediaBox, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: peerId.namespace == Namespaces.Peer.SecretChat) - } - } + let signal: Signal = strongSelf.context.engine.peers.removePeerChats(peerIds: Array(peerIds)) |> afterDisposed { Queue.mainQueue().async { progressDisposable.dispose() @@ -2693,7 +2689,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let channel = chatPeer as? TelegramChannel { strongSelf.context.peerChannelMemberCategoriesContextsManager.externallyRemoved(peerId: channel.id, memberId: strongSelf.context.account.peerId) } - let _ = removePeerChat(account: strongSelf.context.account, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: deleteGloballyIfPossible).start(completed: { + let _ = strongSelf.context.engine.peers.removePeerChat(peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: deleteGloballyIfPossible).start(completed: { guard let strongSelf = self else { return } diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index cafbbffdd3..041708249f 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -191,7 +191,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo guard let strongSelf = self else { return } - let _ = (clearRecentlySearchedPeers(postbox: strongSelf.context.account.postbox) + let _ = (strongSelf.context.engine.peers.clearRecentlySearchedPeers() |> deliverOnMainQueue).start() }) ]), ActionSheetItemGroup(items: [ diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 598447e931..8d63e604f5 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -965,7 +965,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let resolvedMessage = .single(nil) |> then(context.sharedContext.resolveUrl(context: context, peerId: nil, url: finalQuery, skipUrlAuth: true) |> mapToSignal { resolvedUrl -> Signal in - if case let .channelMessage(_, messageId) = resolvedUrl { + if case let .channelMessage(_, messageId, _) = resolvedUrl { return context.engine.messages.downloadMessage(messageId: messageId) } else { return .single(nil) @@ -1213,7 +1213,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, peerSelected: { [weak self] peer, _ in interaction.dismissInput() interaction.openPeer(peer, false) - let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start() + let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).start() self?.listNode.clearHighlightAnimated(true) }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in @@ -1407,7 +1407,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { |> distinctUntilChanged let previousRecentlySearchedPeerOrder = Atomic<[PeerId]>(value: []) - let fixedRecentlySearchedPeers = recentlySearchedPeers(postbox: context.account.postbox) + let fixedRecentlySearchedPeers = context.engine.peers.recentlySearchedPeers() |> map { peers -> [RecentlySearchedPeer] in var result: [RecentlySearchedPeer] = [] let _ = previousRecentlySearchedPeerOrder.modify { current in @@ -1479,7 +1479,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let firstTime = previousEntries == nil let transition = chatListSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, filter: peersFilter, peerSelected: { peer in interaction.openPeer(peer, true) - let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start() + let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).start() self?.recentListNode.clearHighlightAnimated(true) }, disabledPeerSelected: { peer in interaction.openDisabledPeer(peer) @@ -1492,7 +1492,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, clearRecentlySearchedPeers: { interaction.clearRecentSearch() }, deletePeer: { peerId in - let _ = removeRecentlySearchedPeer(postbox: context.account.postbox, peerId: peerId).start() + let _ = context.engine.peers.removeRecentlySearchedPeer(peerId: peerId).start() }) strongSelf.enqueueRecentTransition(transition, firstTime: firstTime) } @@ -1518,11 +1518,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } }) - self.recentListNode.beganInteractiveDragging = { + self.recentListNode.beganInteractiveDragging = { _ in interaction.dismissInput() } - self.listNode.beganInteractiveDragging = { + self.listNode.beganInteractiveDragging = { _ in interaction.dismissInput() } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index f8ef4bbeeb..ca1b21806a 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1220,7 +1220,7 @@ public final class ChatListNode: ListView { } var startedScrollingAtUpperBound = false - self.beganInteractiveDragging = { [weak self] in + self.beganInteractiveDragging = { [weak self] _ in guard let strongSelf = self else { return } diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index a97308a242..d337d880b6 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -414,7 +414,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo } })) - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } } diff --git a/submodules/Display/Source/Keyboard.swift b/submodules/Display/Source/Keyboard.swift index 53c3b5d048..d2699a42e6 100644 --- a/submodules/Display/Source/Keyboard.swift +++ b/submodules/Display/Source/Keyboard.swift @@ -2,7 +2,7 @@ import Foundation import UIKitRuntimeUtils public enum Keyboard { - public static func applyAutocorrection() { - applyKeyboardAutocorrection() + public static func applyAutocorrection(textView: UITextView) { + applyKeyboardAutocorrection(textView) } } diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 9599f748a4..a50d1e8eef 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -286,7 +286,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture public final var visibleContentOffsetChanged: (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 didEndScrolling: (() -> Void)? @@ -683,7 +683,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } self.scrolledToItem = nil - self.beganInteractiveDragging() + self.beganInteractiveDragging(self.touchesPosition) for itemNode in self.itemNodes { if !itemNode.isLayerBacked { @@ -4039,7 +4039,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.updateOverlayHighlight(transition: transition) } - private func itemIndexAtPoint(_ point: CGPoint) -> Int? { + public func itemIndexAtPoint(_ point: CGPoint) -> Int? { for itemNode in self.itemNodes { if itemNode.apparentContentFrame.contains(point) { return itemNode.index @@ -4057,6 +4057,15 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture 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) { for itemNode in self.itemNodes { if itemNode.index != nil { diff --git a/submodules/Display/Source/WindowContent.swift b/submodules/Display/Source/WindowContent.swift index 23b2b84cb5..6bb5dbc6f7 100644 --- a/submodules/Display/Source/WindowContent.swift +++ b/submodules/Display/Source/WindowContent.swift @@ -117,14 +117,6 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout, deviceMetric return ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.onScreenNavigationHeight ?? 0.0, right: 0.0), safeInsets: resolvedSafeInsets, additionalInsets: UIEdgeInsets(), statusBarHeight: resolvedStatusBarHeight, inputHeight: updatedInputHeight, inputHeightIsInteractivellyChanging: layout.upperKeyboardInputPositionBound != nil && layout.upperKeyboardInputPositionBound != layout.size.height && layout.inputHeight != nil, inVoiceOver: layout.inVoiceOver) } -private func encodeText(_ string: String, _ key: Int) -> String { - var result = "" - for c in string.unicodeScalars { - result.append(Character(UnicodeScalar(UInt32(Int(c.value) + key))!)) - } - return result -} - public func doesViewTreeDisableInteractiveTransitionGestureRecognizer(_ view: UIView, keyboardOnly: Bool = false) -> Bool { if view.disablesInteractiveTransitionGestureRecognizer && !keyboardOnly { return true @@ -237,7 +229,7 @@ public class Window1 { private var deviceMetrics: DeviceMetrics - private let statusBarHost: StatusBarHost? + public let statusBarHost: StatusBarHost? private let keyboardManager: KeyboardManager? private let keyboardViewManager: KeyboardViewManager? private var statusBarChangeObserver: AnyObject? diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 24c653e88a..217fbbd6ee 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -639,7 +639,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } 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? if status.timestamp > 5.0 && status.timestamp < status.duration - 5.0 { timestamp = status.timestamp diff --git a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift index 9ff8e0da6d..ba498852d8 100644 --- a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift +++ b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift @@ -450,7 +450,7 @@ public final class SecretMediaPreviewController: ViewController { self?.didSetReady = true } self._ready.set(ready |> map { true }) - self.markMessageAsConsumedDisposable.set(markMessageContentAsConsumedInteractively(postbox: self.context.account.postbox, messageId: message.id).start()) + self.markMessageAsConsumedDisposable.set(self.context.engine.messages.markMessageContentAsConsumedInteractively(messageId: message.id).start()) } else { var beginTimeAndTimeout: (Double, Double)? var videoDuration: Int32? diff --git a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift index df02af0f17..5635a76076 100644 --- a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift +++ b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift @@ -219,16 +219,6 @@ public final class GradientBackgroundNode: ASDisplayNode { } 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>() private let useSharedAnimationPhase: Bool @@ -259,7 +249,7 @@ public final class GradientBackgroundNode: ASDisplayNode { 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 self.validLayout = size @@ -273,7 +263,20 @@ public final class GradientBackgroundNode: ASDisplayNode { self.invalidated = false 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 var stepPhase = (self.phase + phaseCount) % 8 for _ in 0 ... phaseCount { @@ -332,13 +335,13 @@ public final class GradientBackgroundNode: ASDisplayNode { let animation = CAKeyframeAnimation(keyPath: "contents") animation.values = images.map { $0.cgImage! } animation.duration = duration * UIView.animationDurationFactor() - if extendAnimation { + if backwards || extendAnimation { animation.calculationMode = .discrete } else { animation.calculationMode = .linear } animation.isRemovedOnCompletion = true - if extendAnimation { + if extendAnimation && !backwards { animation.fillMode = .backwards 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") if !self.cloneNodes.isEmpty { - let animation = CAKeyframeAnimation(keyPath: "contents") - animation.values = dimmedImages.map { $0.cgImage! } - animation.duration = duration * UIView.animationDurationFactor() - if extendAnimation { - animation.calculationMode = .discrete - } else { - animation.calculationMode = .linear - } - animation.isRemovedOnCompletion = true - if extendAnimation { - animation.fillMode = .backwards - animation.beginTime = self.contentView.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.25 - } + let cloneAnimation = CAKeyframeAnimation(keyPath: "contents") + cloneAnimation.values = dimmedImages.map { $0.cgImage! } + cloneAnimation.duration = animation.duration + cloneAnimation.calculationMode = animation.calculationMode + cloneAnimation.isRemovedOnCompletion = animation.isRemovedOnCompletion + cloneAnimation.fillMode = animation.fillMode + cloneAnimation.beginTime = animation.beginTime self._dimmedImage = dimmedImages.last @@ -367,7 +364,7 @@ public final class GradientBackgroundNode: ASDisplayNode { if let value = cloneNode.value { value.image = dimmedImages.last 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 { return } - if extendAnimation { + if extendAnimation || backwards { self.invalidated = true } else { if self.phase == 0 { @@ -440,7 +437,7 @@ public final class GradientBackgroundNode: ASDisplayNode { GradientBackgroundNode.sharedPhase = self.phase } if let size = self.validLayout { - self.updateLayout(size: size, transition: transition, extendAnimation: extendAnimation) + self.updateLayout(size: size, transition: transition, extendAnimation: extendAnimation, backwards: backwards) } } } diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index 034972000f..5be46fde59 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -57,7 +57,7 @@ public final class HashtagSearchController: TelegramBaseController { if let strongSelf = self { 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 { - 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) diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift index 6104159bd1..d07f6b3078 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift @@ -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 var theme: PresentationTheme 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.clipsToBounds = true 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.keyboardType = keyboardType self.textInputNode.autocapitalizationType = .sentences @@ -358,6 +365,39 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF self.complete?() 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) return true } diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 25c3bcc01c..1fc89122e8 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -347,7 +347,7 @@ open class ItemListControllerNode: ASDisplayNode { self?.contentOffsetChanged?(offset, inVoiceOver) } - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in if let strongSelf = self { strongSelf.beganInteractiveDragging?() } diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/HPTextViewInternal.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/HPTextViewInternal.h index f837bd957e..0ad1e8080e 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/HPTextViewInternal.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/HPTextViewInternal.h @@ -46,8 +46,6 @@ - (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController; -+ (void)addTextViewMethods; - - (void)textViewEnsureSelectionVisible; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGHacks.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGHacks.h index e12bf35732..916a57346c 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGHacks.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGHacks.h @@ -30,16 +30,9 @@ typedef enum { + (void)setSecondaryAnimationDurationFactor:(float)factor; + (void)setForceSystemCurve:(bool)forceSystemCurve; -+ (CGFloat)applicationStatusBarOffset; -+ (void)setApplicationStatusBarOffset:(CGFloat)offset; -+ (void)animateApplicationStatusBarStyleTransitionWithDuration:(NSTimeInterval)duration; -+ (CGFloat)statusBarHeightForOrientation:(UIInterfaceOrientation)orientation; - + (bool)isKeyboardVisible; -+ (CGFloat)keyboardHeightForOrientation:(UIInterfaceOrientation)orientation; -+ (void)applyCurrentKeyboardAutocorrectionVariant; ++ (void)applyCurrentKeyboardAutocorrectionVariant:(UITextView *)textView; + (UIWindow *)applicationKeyboardWindow; -+ (UIView *)applicationKeyboardView; + (void)setApplicationKeyboardOffset:(CGFloat)offset; + (void)forcePerformWithAnimation:(dispatch_block_t)block; diff --git a/submodules/LegacyComponents/Sources/HPGrowingTextView.m b/submodules/LegacyComponents/Sources/HPGrowingTextView.m index c31e3cbc5e..99e53f2370 100644 --- a/submodules/LegacyComponents/Sources/HPGrowingTextView.m +++ b/submodules/LegacyComponents/Sources/HPGrowingTextView.m @@ -63,12 +63,6 @@ NSString *TGMentionBoldAttributeName = @"TGMentionBoldAttributeName"; - (void)commonInitialiser { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - [HPTextViewInternal addTextViewMethods]; - }); - CGRect frame = self.frame; frame.origin = CGPointZero; _internalTextView = [[HPTextViewInternal alloc] initWithKeyCommandController:_keyCommandController]; diff --git a/submodules/LegacyComponents/Sources/HPTextViewInternal.m b/submodules/LegacyComponents/Sources/HPTextViewInternal.m index 7515e8a770..a7a02ae7bd 100644 --- a/submodules/LegacyComponents/Sources/HPTextViewInternal.m +++ b/submodules/LegacyComponents/Sources/HPTextViewInternal.m @@ -29,15 +29,6 @@ return self; } -+ (void)addTextViewMethods -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - InjectInstanceMethodFromAnotherClass([HPTextViewInternal class], [HPTextViewInternal class], @selector(textViewAdjustScrollRange:animated:), NSSelectorFromString(TGEncodeText(@"`tdspmmSbohfUpWjtjcmf;bojnbufe;", -1))); - }); -} - - (void)setText:(NSString *)text { BOOL originalValue = self.scrollEnabled; @@ -64,25 +55,6 @@ [super setScrollEnabled:isScrollable]; } -- (void)textViewAdjustScrollRange:(NSRange)range animated:(BOOL)animated -{ - static SEL selector = NULL; - static void (*impl)(id, SEL, NSRange, BOOL) = NULL; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - Method method = class_getInstanceMethod([UITextView class], selector); - if (method != NULL) - impl = (void (*)(id, SEL, NSRange, BOOL))method_getImplementation(method); - }); - - animated = false; - - if (impl != NULL) - impl(self, selector, range, animated); -} - - (void)scrollRectToVisible:(CGRect)__unused rect animated:(BOOL)__unused animated { diff --git a/submodules/LegacyComponents/Sources/LegacyComponentsInternal.h b/submodules/LegacyComponents/Sources/LegacyComponentsInternal.h index 0fac5b8aa4..7feaa28fb6 100644 --- a/submodules/LegacyComponents/Sources/LegacyComponentsInternal.h +++ b/submodules/LegacyComponents/Sources/LegacyComponentsInternal.h @@ -14,8 +14,6 @@ void TGLegacyLog(NSString *format, ...); int iosMajorVersion(); int iosMinorVersion(); -NSString *TGEncodeText(NSString *string, int key); - void TGDispatchOnMainThread(dispatch_block_t block); void TGDispatchAfter(double delay, dispatch_queue_t queue, dispatch_block_t block); diff --git a/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m b/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m index 494622075b..a79b0e7f37 100644 --- a/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m +++ b/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m @@ -70,20 +70,6 @@ int iosMinorVersion() return version; } -NSString *TGEncodeText(NSString *string, int key) -{ - NSMutableString *result = [[NSMutableString alloc] init]; - - for (int i = 0; i < (int)[string length]; i++) - { - unichar c = [string characterAtIndex:i]; - c += key; - [result appendString:[NSString stringWithCharacters:&c length:1]]; - } - - return result; -} - int deviceMemorySize() { static int memorySize = 0; diff --git a/submodules/LegacyComponents/Sources/TGHacks.m b/submodules/LegacyComponents/Sources/TGHacks.m index 0b1f88a6bc..e8211c63a8 100644 --- a/submodules/LegacyComponents/Sources/TGHacks.m +++ b/submodules/LegacyComponents/Sources/TGHacks.m @@ -215,209 +215,6 @@ void InjectInstanceMethodFromAnotherClass(Class toClass, Class fromClass, SEL fr window.alpha = alpha; } -+ (CGFloat)applicationStatusBarOffset -{ - UIWindow *window = [[LegacyComponentsGlobals provider] applicationStatusBarWindow]; - return window.bounds.origin.y; -} - -+ (void)setApplicationStatusBarOffset:(CGFloat)offset { - UIWindow *window = [[LegacyComponentsGlobals provider] applicationStatusBarWindow]; - CGRect bounds = window.bounds; - bounds.origin = CGPointMake(0.0f, -offset); - window.bounds = bounds; -} - -static UIView *findStatusBarView() -{ - static Class viewClass = nil; - static SEL selector = NULL; - if (selector == NULL) - { - NSString *str1 = @"rs`str"; - NSString *str2 = @"A`qVhmcnv"; - - selector = NSSelectorFromString([[NSString alloc] initWithFormat:@"%@%@", TGEncodeText(str1, 1), TGEncodeText(str2, 1)]); - - viewClass = NSClassFromString(TGEncodeText(@"VJTubuvtCbs", -1)); - } - - UIWindow *window = [[LegacyComponentsGlobals provider] applicationStatusBarWindow]; - - for (UIView *subview in window.subviews) - { - if ([subview isKindOfClass:viewClass]) - { - return subview; - } - } - - return nil; -} - -+ (void)animateApplicationStatusBarAppearance:(int)statusBarAnimation duration:(NSTimeInterval)duration completion:(void (^)())completion -{ - [self animateApplicationStatusBarAppearance:statusBarAnimation delay:0.0 duration:duration completion:completion]; -} - -+ (void)animateApplicationStatusBarAppearance:(int)statusBarAnimation delay:(NSTimeInterval)delay duration:(NSTimeInterval)duration completion:(void (^)())completion -{ - UIView *view = findStatusBarView(); - - if (view != nil) - { - if ((statusBarAnimation & TGStatusBarAppearanceAnimationSlideDown) || (statusBarAnimation & TGStatusBarAppearanceAnimationSlideUp)) - { - CGPoint startPosition = view.layer.position; - CGPoint position = view.layer.position; - - CGPoint normalPosition = CGPointMake(CGFloor(view.frame.size.width / 2), CGFloor(view.frame.size.height / 2)); - - CGFloat viewHeight = view.frame.size.height; - - if (statusBarAnimation & TGStatusBarAppearanceAnimationSlideDown) - { - startPosition = CGPointMake(CGFloor(view.frame.size.width / 2), CGFloor(view.frame.size.height / 2) - viewHeight); - position = CGPointMake(CGFloor(view.frame.size.width / 2), CGFloor(view.frame.size.height / 2)); - } - else if (statusBarAnimation & TGStatusBarAppearanceAnimationSlideUp) - { - startPosition = CGPointMake(CGFloor(view.frame.size.width / 2), CGFloor(view.frame.size.height / 2)); - position = CGPointMake(CGFloor(view.frame.size.width / 2), CGFloor(view.frame.size.height / 2) - viewHeight); - } - - CABasicAnimation *animation = [[CABasicAnimation alloc] init]; - animation.duration = duration; - animation.fromValue = [NSValue valueWithCGPoint:startPosition]; - animation.toValue = [NSValue valueWithCGPoint:position]; - animation.removedOnCompletion = true; - animation.fillMode = kCAFillModeForwards; - animation.beginTime = delay; - animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - - TGAnimationBlockDelegate *delegate = [[TGAnimationBlockDelegate alloc] initWithLayer:view.layer]; - delegate.completion = ^(BOOL finished) - { - if (finished) - view.layer.position = normalPosition; - if (completion) - completion(); - }; - animation.delegate = delegate; - [view.layer addAnimation:animation forKey:@"position"]; - - view.layer.position = position; - } - else if ((statusBarAnimation & TGStatusBarAppearanceAnimationFadeIn) || (statusBarAnimation & TGStatusBarAppearanceAnimationFadeOut)) - { - float startOpacity = view.layer.opacity; - float opacity = view.layer.opacity; - - if (statusBarAnimation & TGStatusBarAppearanceAnimationFadeIn) - { - startOpacity = 0.0f; - opacity = 1.0f; - } - else if (statusBarAnimation & TGStatusBarAppearanceAnimationFadeOut) - { - startOpacity = 1.0f; - opacity = 0.0f; - } - - CABasicAnimation *animation = [[CABasicAnimation alloc] init]; - animation.duration = duration; - animation.fromValue = @(startOpacity); - animation.toValue = @(opacity); - animation.removedOnCompletion = true; - animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - TGAnimationBlockDelegate *delegate = [[TGAnimationBlockDelegate alloc] initWithLayer:view.layer]; - delegate.completion = ^(__unused BOOL finished) - { - if (completion) - completion(); - }; - animation.delegate = delegate; - - [view.layer addAnimation:animation forKey:@"opacity"]; - } - } - else - { - if (completion) - completion(); - } -} - -+ (void)animateApplicationStatusBarStyleTransitionWithDuration:(NSTimeInterval)duration -{ - UIView *view = findStatusBarView(); - - if (view != nil) - { - UIView *snapshotView = [view snapshotViewAfterScreenUpdates:false]; - [view addSubview:snapshotView]; - - [UIView animateWithDuration:duration animations:^ - { - snapshotView.alpha = 0.0f; - } completion:^(__unused BOOL finished) - { - [snapshotView removeFromSuperview]; - }]; - } -} - -+ (CGFloat)statusBarHeightForOrientation:(UIInterfaceOrientation)orientation -{ - UIWindow *window = [[LegacyComponentsGlobals provider] applicationStatusBarWindow]; - - Class statusBarClass = NSClassFromString(TGEncodeText(@"VJTubuvtCbs", -1)); - - for (UIView *view in window.subviews) - { - if ([view isKindOfClass:statusBarClass]) - { - SEL selector = NSSelectorFromString(TGEncodeText(@"dvssfouTuzmf", -1)); - NSMethodSignature *signature = [statusBarClass instanceMethodSignatureForSelector:selector]; - if (signature == nil) - { - TGLegacyLog(@"***** Method not found"); - return 20.0f; - } - - NSInvocation *inv = [NSInvocation invocationWithMethodSignature:signature]; - [inv setSelector:selector]; - [inv setTarget:view]; - [inv invoke]; - - NSInteger result = 0; - [inv getReturnValue:&result]; - - SEL selector2 = NSSelectorFromString(TGEncodeText(@"ifjhiuGpsTuzmf;psjfoubujpo;", -1)); - NSMethodSignature *signature2 = [statusBarClass methodSignatureForSelector:selector2]; - if (signature2 == nil) - { - TGLegacyLog(@"***** Method not found"); - return 20.0f; - } - NSInvocation *inv2 = [NSInvocation invocationWithMethodSignature:signature2]; - [inv2 setSelector:selector2]; - [inv2 setTarget:[view class]]; - [inv2 setArgument:&result atIndex:2]; - NSInteger argOrientation = orientation; - [inv2 setArgument:&argOrientation atIndex:3]; - [inv2 invoke]; - - CGFloat result2 = 0; - [inv2 getReturnValue:&result2]; - - return result2; - } - } - - return 20.0f; -} - + (bool)isKeyboardVisible { return [self isKeyboardVisibleAlt]; @@ -445,66 +242,9 @@ static bool keyboardHidden = true; return !keyboardHidden; } -+ (CGFloat)keyboardHeightForOrientation:(UIInterfaceOrientation)orientation ++ (void)applyCurrentKeyboardAutocorrectionVariant:(UITextView *)textView { - static NSInvocation *invocation = nil; - static Class keyboardClass = NULL; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - keyboardClass = NSClassFromString(TGEncodeText(@"VJLfzcpbse", -1)); - - SEL selector = NSSelectorFromString(TGEncodeText(@"tj{fGpsJoufsgbdfPsjfoubujpo;", -1)); - NSMethodSignature *signature = [keyboardClass methodSignatureForSelector:selector]; - if (signature == nil) - TGLegacyLog(@"***** Method not found"); - else - { - invocation = [NSInvocation invocationWithMethodSignature:signature]; - [invocation setSelector:selector]; - } - }); - - if (invocation != nil) - { - [invocation setTarget:[keyboardClass class]]; - [invocation setArgument:&orientation atIndex:2]; - [invocation invoke]; - - CGSize result = CGSizeZero; - [invocation getReturnValue:&result]; - - return MIN(result.width, result.height); - } - - return 0.0f; -} - -+ (void)applyCurrentKeyboardAutocorrectionVariant -{ - static Class keyboardClass = NULL; - static SEL currentInstanceSelector = NULL; - static SEL applyVariantSelector = NULL; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - keyboardClass = NSClassFromString(TGEncodeText(@"VJLfzcpbse", -1)); - - currentInstanceSelector = NSSelectorFromString(TGEncodeText(@"bdujwfLfzcpbse", -1)); - applyVariantSelector = NSSelectorFromString(TGEncodeText(@"bddfquBvupdpssfdujpo", -1)); - }); - - if ([keyboardClass respondsToSelector:currentInstanceSelector]) - { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - id currentInstance = [keyboardClass performSelector:currentInstanceSelector]; - if ([currentInstance respondsToSelector:applyVariantSelector]) - [currentInstance performSelector:applyVariantSelector]; -#pragma clang diagnostic pop - } + [textView unmarkText]; } + (UIWindow *)applicationKeyboardWindow @@ -518,32 +258,6 @@ static bool keyboardHidden = true; keyboardWindow.frame = CGRectOffset(keyboardWindow.bounds, 0.0f, offset); } -+ (UIView *)applicationKeyboardView -{ - static Class keyboardViewClass = Nil; - static Class keyboardViewContainerClass = Nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - keyboardViewClass = NSClassFromString(TGEncodeText(@"VJJoqvuTfuIptuWjfx", -1)); - keyboardViewContainerClass = NSClassFromString(TGEncodeText(@"VJJoqvuTfuDpoubjofsWjfx", -1)); - }); - - for (UIView *view in [self applicationKeyboardWindow].subviews) - { - if ([view isKindOfClass:keyboardViewContainerClass]) - { - for (UIView *subview in view.subviews) - { - if ([subview isKindOfClass:keyboardViewClass]) - return subview; - } - } - } - - return nil; -} - + (void)setForceMovieAnimatedScaleMode:(bool)force { forceMovieAnimatedScaleMode = force; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 23674be13c..37058dca22 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -1407,8 +1407,8 @@ if (_searchController == nil) return; - UIView *backArrow = [self _findBackArrow:self.navigationBar]; - UIView *backButton = [self _findBackButton:self.navigationBar parentView:self.navigationBar]; + UIView *backArrow = nil; + UIView *backButton = nil; if ([viewController isKindOfClass:[TGPhotoEditorController class]]) { @@ -1440,50 +1440,13 @@ _searchSnapshotView = nil; _searchController.view.hidden = false; - UIView *backArrow = [self _findBackArrow:self.navigationBar]; - UIView *backButton = [self _findBackButton:self.navigationBar parentView:self.navigationBar]; + UIView *backArrow = nil; + UIView *backButton = nil; backArrow.alpha = 1.0f; backButton.alpha = 1.0f; } } -- (UIView *)_findBackArrow:(UIView *)view -{ - Class backArrowClass = NSClassFromString(TGEncodeText(@"`VJObwjhbujpoCbsCbdlJoejdbupsWjfx", -1)); - - if ([view isKindOfClass:backArrowClass]) - return view; - - for (UIView *subview in view.subviews) - { - UIView *result = [self _findBackArrow:subview]; - if (result != nil) - return result; - } - - return nil; -} - -- (UIView *)_findBackButton:(UIView *)view parentView:(UIView *)parentView -{ - Class backButtonClass = NSClassFromString(TGEncodeText(@"VJObwjhbujpoJufnCvuupoWjfx", -1)); - - if ([view isKindOfClass:backButtonClass]) - { - if (view.center.x < parentView.frame.size.width / 2.0f) - return view; - } - - for (UIView *subview in view.subviews) - { - UIView *result = [self _findBackButton:subview parentView:parentView]; - if (result != nil) - return result; - } - - return nil; -} - #pragma mark - + (TGMediaAssetType)assetTypeForIntent:(TGMediaAssetsControllerIntent)intent diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerCaptionInputPanel.m b/submodules/LegacyComponents/Sources/TGMediaPickerCaptionInputPanel.m index 5e86ebd887..c253a45589 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerCaptionInputPanel.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerCaptionInputPanel.m @@ -252,7 +252,7 @@ static void setViewFrame(UIView *view, CGRect frame) } if (_inputField.internalTextView.isFirstResponder) - [TGHacks applyCurrentKeyboardAutocorrectionVariant]; + [TGHacks applyCurrentKeyboardAutocorrectionVariant:_inputField.internalTextView]; NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithAttributedString:_inputField.text == nil ? [[NSAttributedString alloc] initWithString:@""] : _inputField.attributedText]; NSMutableString *usualString = [text.string mutableCopy]; diff --git a/submodules/LegacyComponents/Sources/TGNavigationController.m b/submodules/LegacyComponents/Sources/TGNavigationController.m index 440d7325f3..d3bb25fb1b 100644 --- a/submodules/LegacyComponents/Sources/TGNavigationController.m +++ b/submodules/LegacyComponents/Sources/TGNavigationController.m @@ -352,40 +352,6 @@ } } -- (void)setShowCallStatusBar:(bool)showCallStatusBar -{ - if (_showCallStatusBar == showCallStatusBar) - return; - - _showCallStatusBar = showCallStatusBar; - - int screenHeight = (int)TGScreenSize().height; - CGFloat statusBarHeight = (screenHeight == 812 || screenHeight == 896) ? 0.0f : 20.0f; - - _currentAdditionalStatusBarHeight = _showCallStatusBar ? statusBarHeight : 0.0f; - [(TGNavigationBar *)self.navigationBar setVerticalOffset:_currentAdditionalStatusBarHeight]; - - [UIView animateWithDuration:0.25 animations:^ - { - static SEL selector = NULL; - static void (*impl)(id, SEL) = NULL; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - selector = NSSelectorFromString(TGEncodeText(@"`vqebufCbstGpsDvssfouJoufsgbdfPsjfoubujpo", -1)); - Method method = class_getInstanceMethod([UINavigationController class], selector); - impl = (void (*)(id, SEL))method_getImplementation(method); - }); - - if (impl != NULL) - impl(self, selector); - - [self updateStatusBarOnControllers]; - }]; -} - - - (void)setupStatusBarOnControllers:(NSArray *)controllers { if ([[self navigationBar] isKindOfClass:[TGNavigationBar class]]) @@ -416,11 +382,6 @@ TGViewController *viewController = (TGViewController *)maybeController; [viewController setAdditionalStatusBarHeight:_currentAdditionalStatusBarHeight]; [viewController setNeedsStatusBarAppearanceUpdate]; - - if ([viewController.presentedViewController isKindOfClass:[TGNavigationController class]] && viewController.presentedViewController.modalPresentationStyle != UIModalPresentationPopover) - { - [(TGNavigationController *)viewController.presentedViewController setShowCallStatusBar:_showCallStatusBar]; - } } else if ([maybeController isKindOfClass:[UITabBarController class]] && [maybeController conformsToProtocol:@protocol(TGNavigationControllerTabsController)]) { @@ -438,54 +399,9 @@ } } -static UIView *findDimmingView(UIView *view) -{ - static NSString *encodedString = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - encodedString = TGEncodeText(@"VJEjnnjohWjfx", -1); - }); - - if ([NSStringFromClass(view.class) isEqualToString:encodedString]) - return view; - - for (UIView *subview in view.subviews) - { - UIView *result = findDimmingView(subview); - if (result != nil) - return result; - } - - return nil; -} - - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - - if (self.modalPresentationStyle == UIModalPresentationFormSheet) - { - UIView *dimmingView = findDimmingView(self.view.window); - bool tapSetup = false; - if (_dimmingTapRecognizer != nil) - { - for (UIGestureRecognizer *recognizer in dimmingView.gestureRecognizers) - { - if (recognizer == _dimmingTapRecognizer) - { - tapSetup = true; - break; - } - } - } - - if (!tapSetup) - { - _dimmingTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dimmingViewTapped:)]; - [dimmingView addGestureRecognizer:_dimmingTapRecognizer]; - } - } } - (void)dimmingViewTapped:(UITapGestureRecognizer *)recognizer @@ -931,74 +847,16 @@ TGNavigationController *findNavigationController() - (void)updateInteractiveTransition:(CGFloat)percentComplete { - TGNavigationController *navigationController = findNavigationController(); - if (navigationController != nil) - { - if (!navigationController.disableInteractiveKeyboardTransition && [TGHacks applicationKeyboardWindow] != nil && ![TGHacks applicationKeyboardWindow].hidden) - { - CGSize screenSize = [TGViewController screenSizeForInterfaceOrientation:navigationController.interfaceOrientation]; - CGFloat keyboardOffset = MAX(0.0f, percentComplete * screenSize.width); - - UIView *keyboardView = [TGHacks applicationKeyboardView]; - CGRect keyboardViewFrame = keyboardView.frame; - keyboardViewFrame.origin.x = keyboardOffset; - - keyboardView.frame = keyboardViewFrame; - } - } - [super updateInteractiveTransition:percentComplete]; } - (void)finishInteractiveTransition { - CGFloat value = self.percentComplete; - UIView *keyboardView = [TGHacks applicationKeyboardView]; - CGRect keyboardViewFrame = keyboardView.frame; - [super finishInteractiveTransition]; - - TGNavigationController *navigationController = findNavigationController(); - if (navigationController != nil) - { - if (!navigationController.disableInteractiveKeyboardTransition) - { - keyboardView.frame = keyboardViewFrame; - - CGSize screenSize = [TGViewController screenSizeForInterfaceOrientation:navigationController.interfaceOrientation]; - CGFloat keyboardOffset = 1.0f * screenSize.width; - - keyboardViewFrame.origin.x = keyboardOffset; - NSTimeInterval duration = (1.0 - value) * [navigationController myNominalTransitionAnimationDuration]; - [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^ - { - keyboardView.frame = keyboardViewFrame; - } completion:nil]; - } - } } - (void)cancelInteractiveTransition { - CGFloat value = self.percentComplete; - - TGNavigationController *navigationController = findNavigationController(); - if (navigationController != nil) - { - if (!navigationController.disableInteractiveKeyboardTransition && [TGHacks applicationKeyboardWindow] != nil && ![TGHacks applicationKeyboardWindow].hidden) - { - UIView *keyboardView = [TGHacks applicationKeyboardView]; - CGRect keyboardViewFrame = keyboardView.frame; - keyboardViewFrame.origin.x = 0.0f; - - NSTimeInterval duration = value * [navigationController myNominalTransitionAnimationDuration]; - [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^ - { - keyboardView.frame = keyboardViewFrame; - } completion:nil]; - } - } - [super cancelInteractiveTransition]; } diff --git a/submodules/LegacyComponents/Sources/TGOverlayControllerWindow.m b/submodules/LegacyComponents/Sources/TGOverlayControllerWindow.m index eb45317107..1d7d18f920 100644 --- a/submodules/LegacyComponents/Sources/TGOverlayControllerWindow.m +++ b/submodules/LegacyComponents/Sources/TGOverlayControllerWindow.m @@ -68,28 +68,7 @@ } - (BOOL)shouldAutorotate -{ - static NSArray *nonRotateableWindowClasses = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - NSMutableArray *array = [[NSMutableArray alloc] init]; - Class alertClass = NSClassFromString(TGEncodeText(@"`VJBmfsuPwfsmbzXjoepx", -1)); - if (alertClass != nil) - [array addObject:alertClass]; - - nonRotateableWindowClasses = array; - }); - - for (UIWindow *window in [[LegacyComponentsGlobals provider] applicationWindows].reverseObjectEnumerator) - { - for (Class classInfo in nonRotateableWindowClasses) - { - if ([window isKindOfClass:classInfo]) - return false; - } - } - +{ UIViewController *rootController = [[LegacyComponentsGlobals provider] applicationWindows].firstObject.rootViewController; if (rootController.presentedViewController != nil) diff --git a/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.h b/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.h index 0cb6464f84..796a49c812 100644 --- a/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.h +++ b/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.h @@ -15,6 +15,9 @@ @property (nonatomic, copy) void (^micLevel)(CGFloat); +@property (nonatomic, readonly) bool isZoomAvailable; +@property (nonatomic, assign) CGFloat zoomLevel; + - (instancetype)initWithDelegate:(id)delegate position:(AVCaptureDevicePosition)position callbackQueue:(dispatch_queue_t)queue liveUploadInterface:(id)liveUploadInterface; - (void)startRunning; diff --git a/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.m b/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.m index f249087a91..fde3cd666c 100644 --- a/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.m +++ b/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.m @@ -864,6 +864,52 @@ static CGFloat angleOffsetFromPortraitOrientationToOrientation(AVCaptureVideoOri 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 { @synchronized (self) diff --git a/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m b/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m index 9285d031bc..ea744f75fc 100644 --- a/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m +++ b/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m @@ -91,6 +91,8 @@ typedef enum TGVideoCameraGLView *_previewView; TGVideoMessageRingView *_ringView; + UIPinchGestureRecognizer *_pinchGestureRecognizer; + UIView *_separatorView; UIImageView *_placeholderView; @@ -344,7 +346,6 @@ typedef enum [_circleWrapperView addSubview:_ringView]; CGRect controlsFrame = _controlsFrame; -// controlsFrame.size.width = _wrapperView.frame.size.width; _controlsView = [[TGVideoMessageControls alloc] initWithFrame:controlsFrame assets:_assets slowmodeTimestamp:_slowmodeTimestamp slowmodeView:_slowmodeView]; _controlsView.pallete = self.pallete; @@ -417,12 +418,43 @@ typedef enum [self.view addSubview:_switchButton]; } + _pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)]; + _pinchGestureRecognizer.delegate = self; + [self.view addGestureRecognizer:_pinchGestureRecognizer]; + void (^voidBlock)(void) = ^{}; _buttonHandler = [[PGCameraVolumeButtonHandler alloc] initWithUpButtonPressedBlock:voidBlock upButtonReleasedBlock:voidBlock downButtonPressedBlock:voidBlock downButtonReleasedBlock:voidBlock]; [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 { static dispatch_once_t onceToken; diff --git a/submodules/LegacyComponents/Sources/TGViewController.mm b/submodules/LegacyComponents/Sources/TGViewController.mm index 4ec8d72d20..0a35d3fb26 100644 --- a/submodules/LegacyComponents/Sources/TGViewController.mm +++ b/submodules/LegacyComponents/Sources/TGViewController.mm @@ -695,7 +695,7 @@ static id _defaultContext = nil; { float additionalKeyboardHeight = [self _keyboardAdditionalDeltaHeightWhenRotatingFrom:_viewControllerRotatingFromOrientation toOrientation:toInterfaceOrientation]; - CGFloat statusBarHeight = [TGHacks statusBarHeightForOrientation:toInterfaceOrientation]; + CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height; [self _updateControllerInsetForOrientation:toInterfaceOrientation statusBarHeight:statusBarHeight keyboardHeight:[self _currentKeyboardHeight:toInterfaceOrientation] + additionalKeyboardHeight force:false notify:true]; } @@ -768,9 +768,6 @@ static id _defaultContext = nil; if ([self isViewLoaded] && !_viewControllerHasEverAppeared && ([self findFirstResponder:self.view] == nil && ![self willCaptureInputShortly])) return 0.0f; - if ([TGHacks isKeyboardVisible]) - return [TGHacks keyboardHeightForOrientation:orientation]; - return 0.0f; } @@ -1264,7 +1261,7 @@ static id _defaultContext = nil; if (navigationBarHidden != self.navigationController.navigationBarHidden) { CGFloat barHeight = [self navigationBarHeightForInterfaceOrientation:self.interfaceOrientation]; - CGFloat statusBarHeight = [TGHacks statusBarHeightForOrientation:self.interfaceOrientation]; + CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height; if ([self shouldIgnoreStatusBarInOrientation:self.interfaceOrientation]) statusBarHeight = 0.0f; @@ -1435,13 +1432,6 @@ static id _defaultContext = nil; if (TGIsPad() && iosMajorVersion() >= 7) viewControllerToPresent.preferredContentSize = [self.navigationController preferredContentSize]; - if ([viewControllerToPresent isKindOfClass:[TGNavigationController class]]) - { - TGNavigationController *navController = (TGNavigationController *)self.navigationController; - if (navController.showCallStatusBar) - [(TGNavigationController *)viewControllerToPresent setShowCallStatusBar:true]; - } - if (iosMajorVersion() >= 8 && self.presentedViewController != nil && [self.presentedViewController isKindOfClass:[UIAlertController class]]) { dispatch_async(dispatch_get_main_queue(), ^ diff --git a/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift b/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift index 1912b472ca..79984fa147 100644 --- a/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift +++ b/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift @@ -67,25 +67,25 @@ private final class LegacyComponentsAccessCheckerImpl: NSObject, LegacyComponent } } -private func encodeText(_ string: String, _ key: Int) -> String { - var result = "" - for c in string.unicodeScalars { - result.append(Character(UnicodeScalar(UInt32(Int(c.value) + key))!)) - } - return result -} - -private let keyboardWindowClass: AnyClass? = { +private func isKeyboardWindow(window: NSObject) -> Bool { + let typeName = NSStringFromClass(type(of: window)) if #available(iOS 9.0, *) { - return NSClassFromString(encodeText("VJSfnpufLfzcpbseXjoepx", -1)) + if typeName.hasPrefix("UI") && typeName.hasSuffix("RemoteKeyboardWindow") { + return true + } } else { - return NSClassFromString(encodeText("VJUfyuFggfdutXjoepx", -1)) + if typeName.hasPrefix("UI") && typeName.hasSuffix("TextEffectsWindow") { + return true + } } -}() + return false +} private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyComponentsGlobalsProvider { func log(_ string: String!) { - print(string) + if let string = string { + print("\(string)") + } } public func effectiveLocalization() -> TGLocalization! { @@ -101,12 +101,8 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone } public func applicationKeyboardWindow() -> UIWindow! { - guard let keyboardWindowClass = keyboardWindowClass else { - return nil - } - for window in legacyComponentsApplication?.windows ?? [] { - if window.isKind(of: keyboardWindowClass) { + if isKeyboardWindow(window: window) { return window } } diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index a16dfdf565..e43a09a232 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -640,7 +640,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM strongSelf.layoutEmptyResultsPlaceholder(transition: listTransition) } - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in guard let strongSelf = self else { return } diff --git a/submodules/LocationUI/Sources/LocationSearchContainerNode.swift b/submodules/LocationUI/Sources/LocationSearchContainerNode.swift index 28a38b4999..75fe5a5e7c 100644 --- a/submodules/LocationUI/Sources/LocationSearchContainerNode.swift +++ b/submodules/LocationUI/Sources/LocationSearchContainerNode.swift @@ -224,7 +224,7 @@ final class LocationSearchContainerNode: ASDisplayNode { } })) - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in self?.interaction.dismissInput() } } diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index d2bb4fa7cb..0bd8ad9564 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -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) } - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in guard let strongSelf = self else { return } diff --git a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m index bf41d5d14c..cc9c72705a 100644 --- a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m +++ b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m @@ -24,17 +24,15 @@ @interface MTDatacenterAuthPublicKey : NSObject @property (nonatomic, strong, readonly) NSString *publicKey; -@property (nonatomic, readonly) bool usesModernPaddingScheme; @end @implementation MTDatacenterAuthPublicKey -- (instancetype)initWithPublicKey:(NSString *)publicKey usesModernPaddingScheme:(bool)usesModernPaddingScheme { +- (instancetype)initWithPublicKey:(NSString *)publicKey { self = [super init]; if (self != nil) { _publicKey = publicKey; - _usesModernPaddingScheme = usesModernPaddingScheme; } return self; } @@ -50,78 +48,6 @@ static NSArray *defaultPublicKeys() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ serverPublicKeys = @[ - [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" - "MIIBCgKCAQEAxq7aeLAqJR20tkQQMfRn+ocfrtMlJsQ2Uksfs7Xcoo77jAid0bRt\n" - "ksiVmT2HEIJUlRxfABoPBV8wY9zRTUMaMA654pUX41mhyVN+XoerGxFvrs9dF1Ru\n" - "vCHbI02dM2ppPvyytvvMoefRoL5BTcpAihFgm5xCaakgsJ/tH5oVl74CdhQw8J5L\n" - "xI/K++KJBUyZ26Uba1632cOiq05JBUW0Z2vWIOk4BLysk7+U9z+SxynKiZR3/xdi\n" - "XvFKk01R3BHV+GUKM2RYazpS/P8v7eyKhAbKxOdRcFpHLlVwfjyM1VlDQrEZxsMp\n" - "NTLYXb6Sce1Uov0YtNx5wEowlREH1WOTlwIDAQAB\n" - "-----END RSA PUBLIC KEY-----" usesModernPaddingScheme:false], - [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" - "MIIBCgKCAQEAsQZnSWVZNfClk29RcDTJQ76n8zZaiTGuUsi8sUhW8AS4PSbPKDm+\n" - "DyJgdHDWdIF3HBzl7DHeFrILuqTs0vfS7Pa2NW8nUBwiaYQmPtwEa4n7bTmBVGsB\n" - "1700/tz8wQWOLUlL2nMv+BPlDhxq4kmJCyJfgrIrHlX8sGPcPA4Y6Rwo0MSqYn3s\n" - "g1Pu5gOKlaT9HKmE6wn5Sut6IiBjWozrRQ6n5h2RXNtO7O2qCDqjgB2vBxhV7B+z\n" - "hRbLbCmW0tYMDsvPpX5M8fsO05svN+lKtCAuz1leFns8piZpptpSCFn7bWxiA9/f\n" - "x5x17D7pfah3Sy2pA+NDXyzSlGcKdaUmwQIDAQAB\n" - "-----END RSA PUBLIC KEY-----" usesModernPaddingScheme:false], - [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" - "MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6\n" - "lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS\n" - "an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw\n" - "Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+\n" - "8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n\n" - "Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB\n" - "-----END RSA PUBLIC KEY-----" usesModernPaddingScheme:false], - [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" - "MIIBCgKCAQEAwqjFW0pi4reKGbkc9pK83Eunwj/k0G8ZTioMMPbZmW99GivMibwa\n" - "xDM9RDWabEMyUtGoQC2ZcDeLWRK3W8jMP6dnEKAlvLkDLfC4fXYHzFO5KHEqF06i\n" - "qAqBdmI1iBGdQv/OQCBcbXIWCGDY2AsiqLhlGQfPOI7/vvKc188rTriocgUtoTUc\n" - "/n/sIUzkgwTqRyvWYynWARWzQg0I9olLBBC2q5RQJJlnYXZwyTL3y9tdb7zOHkks\n" - "WV9IMQmZmyZh/N7sMbGWQpt4NMchGpPGeJ2e5gHBjDnlIf2p1yZOYeUYrdbwcS0t\n" - "UiggS4UeE8TzIuXFQxw7fzEIlmhIaq3FnwIDAQAB\n" - "-----END RSA PUBLIC KEY-----" usesModernPaddingScheme:false], - [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" - "MIIBCgKCAQEAruw2yP/BCcsJliRoW5eBVBVle9dtjJw+OYED160Wybum9SXtBBLX\n" - "riwt4rROd9csv0t0OHCaTmRqBcQ0J8fxhN6/cpR1GWgOZRUAiQxoMnlt0R93LCX/\n" - "j1dnVa/gVbCjdSxpbrfY2g2L4frzjJvdl84Kd9ORYjDEAyFnEA7dD556OptgLQQ2\n" - "e2iVNq8NZLYTzLp5YpOdO1doK+ttrltggTCy5SrKeLoCPPbOgGsdxJxyz5KKcZnS\n" - "Lj16yE5HvJQn0CNpRdENvRUXe6tBP78O39oJ8BTHp9oIjd6XWXAsp2CvK45Ol8wF\n" - "XGF710w9lwCGNbmNxNYhtIkdqfsEcwR5JwIDAQAB\n" - "-----END RSA PUBLIC KEY-----" usesModernPaddingScheme:false], - [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" - "MIIBCgKCAQEAvfLHfYH2r9R70w8prHblWt/nDkh+XkgpflqQVcnAfSuTtO05lNPs\n" - "pQmL8Y2XjVT4t8cT6xAkdgfmmvnvRPOOKPi0OfJXoRVylFzAQG/j83u5K3kRLbae\n" - "7fLccVhKZhY46lvsueI1hQdLgNV9n1cQ3TDS2pQOCtovG4eDl9wacrXOJTG2990V\n" - "jgnIKNA0UMoP+KF03qzryqIt3oTvZq03DyWdGK+AZjgBLaDKSnC6qD2cFY81UryR\n" - "WOab8zKkWAnhw2kFpcqhI0jdV5QaSCExvnsjVaX0Y1N0870931/5Jb9ICe4nweZ9\n" - "kSDF/gip3kWLG0o8XQpChDfyvsqB9OLV/wIDAQAB\n" - "-----END RSA PUBLIC KEY-----" usesModernPaddingScheme:false], - [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" - "MIIBCgKCAQEAs/ditzm+mPND6xkhzwFIz6J/968CtkcSE/7Z2qAJiXbmZ3UDJPGr\n" - "zqTDHkO30R8VeRM/Kz2f4nR05GIFiITl4bEjvpy7xqRDspJcCFIOcyXm8abVDhF+\n" - "th6knSU0yLtNKuQVP6voMrnt9MV1X92LGZQLgdHZbPQz0Z5qIpaKhdyA8DEvWWvS\n" - "Uwwc+yi1/gGaybwlzZwqXYoPOhwMebzKUk0xW14htcJrRrq+PXXQbRzTMynseCoP\n" - "Ioke0dtCodbA3qQxQovE16q9zz4Otv2k4j63cz53J+mhkVWAeWxVGI0lltJmWtEY\n" - "K6er8VqqWot3nqmWMXogrgRLggv/NbbooQIDAQAB\n" - "-----END RSA PUBLIC KEY-----" usesModernPaddingScheme:false], - [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" - "MIIBCgKCAQEAvmpxVY7ld/8DAjz6F6q05shjg8/4p6047bn6/m8yPy1RBsvIyvuD\n" - "uGnP/RzPEhzXQ9UJ5Ynmh2XJZgHoE9xbnfxL5BXHplJhMtADXKM9bWB11PU1Eioc\n" - "3+AXBB8QiNFBn2XI5UkO5hPhbb9mJpjA9Uhw8EdfqJP8QetVsI/xrCEbwEXe0xvi\n" - "fRLJbY08/Gp66KpQvy7g8w7VB8wlgePexW3pT13Ap6vuC+mQuJPyiHvSxjEKHgqe\n" - "Pji9NP3tJUFQjcECqcm0yV7/2d0t/pbCm+ZH1sadZspQCEPPrtbkQBlvHb4OLiIW\n" - "PGHKSMeRFvp3IWcmdJqXahxLCUS1Eh6MAQIDAQAB\n" - "-----END RSA PUBLIC KEY-----" usesModernPaddingScheme:false], - [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" - "MIIBCgKCAQEAr4v4wxMDXIaMOh8bayF/NyoYdpcysn5EbjTIOZC0RkgzsRj3SGlu\n" - "52QSz+ysO41dQAjpFLgxPVJoOlxXokaOq827IfW0bGCm0doT5hxtedu9UCQKbE8j\n" - "lDOk+kWMXHPZFJKWRgKgTu9hcB3y3Vk+JFfLpq3d5ZB48B4bcwrRQnzkx5GhWOFX\n" - "x73ZgjO93eoQ2b/lDyXxK4B4IS+hZhjzezPZTI5upTRbs5ljlApsddsHrKk6jJNj\n" - "8Ygs/ps8e6ct82jLXbnndC9s8HjEvDvBPH9IPjv5JUlmHMBFZ5vFQIfbpo0u0+1P\n" - "n6bkEi5o7/ifoyVv2pAZTRwppTz0EuXD8QIDAQAB\n" - "-----END RSA PUBLIC KEY-----" usesModernPaddingScheme:false], [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" "MIIBCgKCAQEAyMEdY1aR+sCR3ZSJrtztKTKqigvO/vBfqACJLZtS7QMgCGXJ6XIR\n" "yy7mx66W0/sOFa7/1mAZtEoIokDP3ShoqF4fVNb6XeqgQfaUHd8wJpDWHcR2OFwv\n" @@ -129,20 +55,23 @@ static NSArray *defaultPublicKeys() { "j25sIWeYPHYeOrFp/eXaqhISP6G+q2IeTaWTXpwZj4LzXq5YOpk4bYEQ6mvRq7D1\n" "aHWfYmlEGepfaYR8Q0YqvvhYtMte3ITnuSJs171+GDqpdKcSwHnd6FudwGO4pcCO\n" "j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB\n" - "-----END RSA PUBLIC KEY-----" usesModernPaddingScheme:true] + "-----END RSA PUBLIC KEY-----"], + [[MTDatacenterAuthPublicKey alloc] initWithPublicKey:@"-----BEGIN RSA PUBLIC KEY-----\n" + "MIIBCgKCAQEA6LszBcC1LGzyr992NzE0ieY+BSaOW622Aa9Bd4ZHLl+TuFQ4lo4g\n" + "5nKaMBwK/BIb9xUfg0Q29/2mgIR6Zr9krM7HjuIcCzFvDtr+L0GQjae9H0pRB2OO\n" + "62cECs5HKhT5DZ98K33vmWiLowc621dQuwKWSQKjWf50XYFw42h21P2KXUGyp2y/\n" + "+aEyZ+uVgLLQbRA1dEjSDZ2iGRy12Mk5gpYc397aYp438fsJoHIgJ2lgMv5h7WY9\n" + "t6N/byY9Nw9p21Og3AoXSL2q/2IJ1WRUhebgAdGVMlV1fkuOQoEzR7EdpqtQD9Cs\n" + "5+bfo3Nhmcyvk5ftB0WkJ9z6bNZ7yxrP8wIDAQAB\n" + "-----END RSA PUBLIC KEY-----"] ]; }); return serverPublicKeys; } -static MTDatacenterAuthPublicKey *selectPublicKey(id encryptionProvider, NSArray *fingerprints, NSArray *publicKeys, bool onlyModernPadding) { +static MTDatacenterAuthPublicKey *selectPublicKey(id encryptionProvider, NSArray *fingerprints, NSArray *publicKeys) { for (NSNumber *nFingerprint in fingerprints) { for (MTDatacenterAuthPublicKey *key in publicKeys) { - if (onlyModernPadding) { - if (!key.usesModernPaddingScheme) { - continue; - } - } uint64_t keyFingerprint = [key fingerprintWithEncryptionProvider:encryptionProvider]; if ([nFingerprint unsignedLongLongValue] == keyFingerprint) { @@ -208,7 +137,7 @@ typedef enum { for (NSDictionary *dict in list) { NSString *key = dict[@"key"]; if ([key isKindOfClass:[NSString class]]) { - [cdnKeys addObject:[[MTDatacenterAuthPublicKey alloc] initWithPublicKey:key usesModernPaddingScheme:false]]; + [cdnKeys addObject:[[MTDatacenterAuthPublicKey alloc] initWithPublicKey:key]]; } } return cdnKeys; @@ -434,7 +363,7 @@ static NSData *reversedBytes(NSData *data) { static NSData *encryptRSAModernPadding(id encryptionProvider, NSData *pqInnerData, NSString *publicKey) { NSMutableData *dataWithPadding = [[NSMutableData alloc] init]; [dataWithPadding appendData:pqInnerData]; - if (dataWithPadding.length > 192) { + if (dataWithPadding.length > 144) { return nil; } if (dataWithPadding.length != 192) { @@ -538,10 +467,7 @@ static NSData *encryptRSAModernPadding(id encryptionProvider if ([_nonce isEqualToData:resPqMessage.nonce]) { - MTDatacenterAuthPublicKey *publicKey = selectPublicKey(_encryptionProvider, resPqMessage.serverPublicKeyFingerprints, _publicKeys, true); - if (publicKey == nil) { - publicKey = selectPublicKey(mtProto.context.encryptionProvider, resPqMessage.serverPublicKeyFingerprints, _publicKeys, false); - } + MTDatacenterAuthPublicKey *publicKey = selectPublicKey(_encryptionProvider, resPqMessage.serverPublicKeyFingerprints, _publicKeys); if (publicKey == nil && mtProto.cdn && resPqMessage.serverPublicKeyFingerprints.count == 1 && _publicKeys.count == 1) { publicKey = _publicKeys[0]; @@ -618,11 +544,8 @@ static NSData *encryptRSAModernPadding(id encryptionProvider NSData *innerDataBytes = innerDataBuffer.data; NSData *encryptedData = nil; - if (publicKey.usesModernPaddingScheme) { - encryptedData = encryptRSAModernPadding(_encryptionProvider, innerDataBytes, publicKey.publicKey); - } else { - encryptedData = encryptRSALegacy(_encryptionProvider, innerDataBytes, publicKey.publicKey); - } + + encryptedData = encryptRSAModernPadding(_encryptionProvider, innerDataBytes, publicKey.publicKey); if (MTLogEnabled()) { MTLog(@"[MTDatacenterAuthMessageService#%p encryptedData length %d]", self, (int)encryptedData.length); @@ -642,11 +565,8 @@ static NSData *encryptRSAModernPadding(id encryptionProvider NSData *innerDataBytes = innerDataBuffer.data; NSData *encryptedData = nil; - if (publicKey.usesModernPaddingScheme) { - encryptedData = encryptRSAModernPadding(_encryptionProvider, innerDataBytes, publicKey.publicKey); - } else { - encryptedData = encryptRSALegacy(_encryptionProvider, innerDataBytes, publicKey.publicKey); - } + + encryptedData = encryptRSAModernPadding(_encryptionProvider, innerDataBytes, publicKey.publicKey); _dhEncryptedData = encryptedData; } diff --git a/submodules/PasscodeUI/BUILD b/submodules/PasscodeUI/BUILD index 60825d4832..cadf3b48ea 100644 --- a/submodules/PasscodeUI/BUILD +++ b/submodules/PasscodeUI/BUILD @@ -23,6 +23,7 @@ swift_library( "//submodules/AppBundle:AppBundle", "//submodules/PasscodeInputFieldNode:PasscodeInputFieldNode", "//submodules/MonotonicTime:MonotonicTime", + "//submodules/GradientBackground:GradientBackground", ], visibility = [ "//visibility:public", diff --git a/submodules/PasscodeUI/Sources/PasscodeBackground.swift b/submodules/PasscodeUI/Sources/PasscodeBackground.swift index 1845af212d..fbe8da29c6 100644 --- a/submodules/PasscodeUI/Sources/PasscodeBackground.swift +++ b/submodules/PasscodeUI/Sources/PasscodeBackground.swift @@ -1,19 +1,40 @@ import Foundation import UIKit +import AsyncDisplayKit import Display import ImageBlur import FastBlur +import GradientBackground protocol PasscodeBackground { var size: CGSize { get } - var backgroundImage: UIImage { get } - var foregroundImage: UIImage { get } + var backgroundImage: 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 { public private(set) var size: CGSize - public private(set) var backgroundImage: UIImage - public private(set) var foregroundImage: UIImage + public private(set) var backgroundImage: UIImage? + public private(set) var foregroundImage: UIImage? init(size: CGSize, backgroundColors: (UIColor, UIColor), buttonColor: UIColor) { self.size = size @@ -35,12 +56,16 @@ final class GradientPasscodeBackground: PasscodeBackground { context.fill(bounds) })! } + + func makeBackgroundNode() -> ASDisplayNode? { + return nil + } } final class ImageBasedPasscodeBackground: PasscodeBackground { public private(set) var size: CGSize - public private(set) var backgroundImage: UIImage - public private(set) var foregroundImage: UIImage + public private(set) var backgroundImage: UIImage? + public private(set) var foregroundImage: UIImage? init(image: UIImage, size: CGSize) { self.size = size @@ -82,4 +107,8 @@ final class ImageBasedPasscodeBackground: PasscodeBackground { } self.backgroundImage = backgroundContext.generateImage()! } + + func makeBackgroundNode() -> ASDisplayNode? { + return nil + } } diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryController.swift b/submodules/PasscodeUI/Sources/PasscodeEntryController.swift index 7c61f2a950..78a2f2c8b6 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryController.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryController.swift @@ -58,7 +58,10 @@ public final class PasscodeEntryController: ViewController { private var inBackground: Bool = false private var inBackgroundDisposable: Disposable? - public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, appLockContext: AppLockContext, presentationData: PresentationData, presentationDataSignal: Signal, 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, statusBarHost: StatusBarHost?, challengeData: PostboxAccessChallengeData, biometrics: PasscodeEntryControllerBiometricsMode, arguments: PasscodeEntryControllerPresentationArguments) { self.applicationBindings = applicationBindings self.accountManager = accountManager self.appLockContext = appLockContext @@ -68,10 +71,12 @@ public final class PasscodeEntryController: ViewController { self.biometrics = biometrics self.arguments = arguments + self.statusBarHost = statusBarHost + self.previousStatusBarStyle = statusBarHost?.statusBarStyle super.init(navigationBarPresentationData: nil) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - self.statusBar.statusBarStyle = .White + statusBarHost?.setStatusBarStyle(.lightContent, animated: true) self.presentationDataDisposable = (presentationDataSignal |> deliverOnMainQueue).start(next: { [weak self] presentationData in @@ -128,7 +133,7 @@ public final class PasscodeEntryController: ViewController { } else { 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() let _ = (self.appLockContext.invalidAttempts @@ -271,6 +276,9 @@ public final class PasscodeEntryController: ViewController { } 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.controllerNode.animateOut { [weak self] in guard let strongSelf = self else { diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift index 6272a4c17b..851a02b094 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift @@ -12,6 +12,7 @@ import LocalAuth import AppBundle import PasscodeInputFieldNode import MonotonicTime +import GradientBackground private let titleFont = Font.regular(20.0) private let subtitleFont = Font.regular(15.0) @@ -19,6 +20,7 @@ private let buttonFont = Font.regular(17.0) final class PasscodeEntryControllerNode: ASDisplayNode { private let accountManager: AccountManager + private var presentationData: PresentationData private var theme: PresentationTheme private var strings: PresentationStrings private var wallpaper: TelegramWallpaper @@ -27,11 +29,11 @@ final class PasscodeEntryControllerNode: ASDisplayNode { private let arguments: PasscodeEntryControllerPresentationArguments private var background: PasscodeBackground? - private let statusBar: StatusBar - 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 titleNode: PasscodeEntryLabelNode private let inputFieldNode: PasscodeInputFieldNode @@ -52,20 +54,24 @@ final class PasscodeEntryControllerNode: ASDisplayNode { var checkPasscode: ((String) -> 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.presentationData = presentationData self.theme = theme self.strings = strings self.wallpaper = wallpaper self.passcodeType = passcodeType self.biometricsType = biometricsType self.arguments = arguments - self.statusBar = statusBar self.modalPresentation = modalPresentation - self.backgroundNode = ASImageNode() - self.backgroundNode.contentMode = .scaleToFill + self.backgroundImageNode = ASImageNode() + self.backgroundImageNode.contentMode = .scaleToFill + self.backgroundDimNode = ASDisplayNode() + self.backgroundDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.15) + self.backgroundDimNode.isHidden = true + self.iconNode = PasscodeLockIconNode() self.titleNode = PasscodeEntryLabelNode() self.inputFieldNode = PasscodeInputFieldNode(color: .white, accentColor: .white, fieldType: passcodeType, keyboardAppearance: .dark, useCustomNumpad: true) @@ -86,7 +92,12 @@ final class PasscodeEntryControllerNode: ASDisplayNode { self.iconNode.unlockedColor = theme.rootController.navigationBar.primaryTextColor 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 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.titleNode) self.addSubnode(self.inputFieldNode) @@ -146,7 +158,10 @@ final class PasscodeEntryControllerNode: ASDisplayNode { @objc private func deletePressed() { 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() { @@ -158,6 +173,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode { } func updatePresentationData(_ presentationData: PresentationData) { + self.presentationData = presentationData self.theme = presentationData.theme self.strings = presentationData.strings self.wallpaper = presentationData.chatWallpaper @@ -173,26 +189,42 @@ final class PasscodeEntryControllerNode: ASDisplayNode { return } - var size = validLayout.size + let size = validLayout.size if let background = self.background, background.size == size { return } switch self.wallpaper { + case let .gradient(_, colors, _): + self.background = CustomPasscodeBackground(size: size, colors: colors.compactMap { UIColor(rgb: $0) }) case .image, .file: 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) } else { - self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor) + 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 { + self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor) + } } default: self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor) } if let background = self.background { - self.backgroundNode.image = background.backgroundImage - self.keyboardNode.updateBackground(background) - self.inputFieldNode.updateBackground(background.foregroundImage, size: background.size) + self.backgroundCustomNode?.removeFromSupernode() + self.backgroundCustomNode = nil + + 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.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) } @@ -277,15 +314,17 @@ final class PasscodeEntryControllerNode: ASDisplayNode { 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 { 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.statusBar.layer.removeAnimation(forKey: "opacity") - self.statusBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.subtitleNode.isHidden = true self.inputFieldNode.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) + if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode { + gradientNode.animateEvent(transition: .animated(duration: 1.0, curve: .spring)) + } self.inputFieldNode.animateIn() self.keyboardNode.animateIn() var biometricDelay = 0.3 @@ -323,7 +365,6 @@ final class PasscodeEntryControllerNode: ASDisplayNode { } 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 completion() }) @@ -340,6 +381,10 @@ final class PasscodeEntryControllerNode: ASDisplayNode { self.iconNode.layer.addShakeAnimation(amplitude: -8.0, duration: 0.5, count: 6, decay: true) 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) { @@ -348,7 +393,14 @@ final class PasscodeEntryControllerNode: ASDisplayNode { self.updateBackground() 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) switch self.passcodeType { diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryKeyboardNode.swift b/submodules/PasscodeUI/Sources/PasscodeEntryKeyboardNode.swift index 0154e44451..2cd4b4f8fc 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryKeyboardNode.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryKeyboardNode.swift @@ -3,6 +3,7 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit +import TelegramPresentationData private let regularTitleFont = Font.regular(36.0) private let regularSubtitleFont: UIFont = { @@ -35,8 +36,9 @@ private func generateButtonImage(background: PasscodeBackground, frame: CGRect, context.clip() context.setAlpha(0.8) - context.draw(background.foregroundImage.cgImage!, in: relativeFrame) - + if let foregroundImage = background.foregroundImage { + context.draw(foregroundImage.cgImage!, in: relativeFrame) + } if highlighted { context.setFillColor(UIColor(white: 1.0, alpha: 0.65).cgColor) context.fillEllipse(in: bounds) @@ -98,6 +100,7 @@ private func generateButtonImage(background: PasscodeBackground, frame: CGRect, } final class PasscodeEntryButtonNode: HighlightTrackingButtonNode { + private var presentationData: PresentationData private var background: PasscodeBackground let title: String private let subtitle: String @@ -106,15 +109,24 @@ final class PasscodeEntryButtonNode: HighlightTrackingButtonNode { private var regularImage: UIImage? private var highlightedImage: UIImage? + private var blurredBackgroundNode: NavigationBackgroundNode? private let backgroundNode: ASImageNode 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.title = title 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.displaysAsynchronously = false self.backgroundNode.displayWithoutProcessing = true @@ -122,6 +134,9 @@ final class PasscodeEntryButtonNode: HighlightTrackingButtonNode { super.init() + if let blurredBackgroundNode = self.blurredBackgroundNode { + self.addSubnode(blurredBackgroundNode) + } self.addSubnode(self.backgroundNode) 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.updateGraphics() } @@ -175,6 +191,10 @@ final class PasscodeEntryButtonNode: HighlightTrackingButtonNode { override func 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 } @@ -199,22 +219,23 @@ private let buttonsData = [ ] final class PasscodeEntryKeyboardNode: ASDisplayNode { + private var presentationData: PresentationData? private var background: PasscodeBackground? var charactedEntered: ((String) -> Void)? private func updateButtons() { - guard let background = self.background else { + guard let presentationData = self.presentationData, let background = self.background else { return } if let subnodes = self.subnodes, !subnodes.isEmpty { for case let button as PasscodeEntryButtonNode in subnodes { - button.updateBackground(background) + button.updateBackground(presentationData, background) } } else { 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 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.updateButtons() } diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryLabelNode.swift b/submodules/PasscodeUI/Sources/PasscodeEntryLabelNode.swift index 51db01bf6e..d5b1783263 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryLabelNode.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryLabelNode.swift @@ -22,6 +22,7 @@ final class PasscodeEntryLabelNode: ASDisplayNode { self.textNode = ASTextNode() self.textNode.isLayerBacked = false self.textNode.textAlignment = .center + self.textNode.displaysAsynchronously = false super.init() diff --git a/submodules/PasscodeUI/Sources/PasscodeInputFieldNode.swift b/submodules/PasscodeUI/Sources/PasscodeInputFieldNode.swift index dd2ce400ac..f6ea4d47e5 100644 --- a/submodules/PasscodeUI/Sources/PasscodeInputFieldNode.swift +++ b/submodules/PasscodeUI/Sources/PasscodeInputFieldNode.swift @@ -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 let bounds = CGRect(origin: CGPoint(), size: size) 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) context.addPath(path.cgPath) context.clip() - context.draw(backgroundImage.cgImage!, in: relativeFrame) - + 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) + } else { + context.setFillColor(UIColor(rgb: 0xffffff, alpha: 1.0).cgColor) + context.fill(bounds) + } context.setBlendMode(.clear) context.setFillColor(UIColor.clear.cgColor) @@ -129,7 +132,7 @@ private class PasscodeEntryDotNode: ASImageNode { } public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate { - private var background: (UIImage, CGSize)? + private var background: PasscodeBackground? private var color: UIColor private var accentColor: UIColor private var fieldType: PasscodeEntryFieldType @@ -207,8 +210,8 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate { } } - func updateBackground(_ image: UIImage, size: CGSize) { - self.background = (image, size) + func updateBackground(_ background: PasscodeBackground) { + self.background = background if let (size, topOffset) = self.validLayout { 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 ?? "" guard !text.isEmpty else { - return + return false } text = String(text[text.startIndex ..< text.index(text.endIndex, offsetBy: -1)]) self.textFieldNode.textField.text = text self.updateDots(count: text.count, animated: true) + return true } 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) transition.updateFrame(node: self.borderNode, frame: fieldFrame) 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 } diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index 74ab715d1f..2c3c31104b 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -927,14 +927,14 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi return current.withUpdatedUpdating(true) } if peerId.namespace == Namespaces.Peer.CloudGroup { - updateRightsDisposable.set((removeGroupAdmin(account: context.account, peerId: peerId, adminId: adminId) + updateRightsDisposable.set((context.engine.peers.removeGroupAdmin(peerId: peerId, adminId: adminId) |> deliverOnMainQueue).start(error: { _ in }, completed: { updated(nil) dismissImpl?() })) } else { - updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: nil, rank: nil) |> deliverOnMainQueue).start(error: { _ in + updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: nil, rank: nil) |> deliverOnMainQueue).start(error: { _ in }, completed: { updated(nil) @@ -1041,7 +1041,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi updateState { current in return current.withUpdatedUpdating(true) } - updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags ?? []), rank: effectiveRank) |> deliverOnMainQueue).start(error: { error in + updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags ?? []), rank: effectiveRank) |> deliverOnMainQueue).start(error: { error in updateState { current in return current.withUpdatedUpdating(false) } @@ -1089,7 +1089,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi updateState { current in return current.withUpdatedUpdating(true) } - updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: currentFlags), rank: updateRank) |> deliverOnMainQueue).start(error: { _ in + updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: currentFlags), rank: updateRank) |> deliverOnMainQueue).start(error: { _ in }, completed: { updated(TelegramChatAdminRights(rights: currentFlags)) @@ -1134,7 +1134,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi updateState { current in return current.withUpdatedUpdating(true) } - updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags), rank: updateRank) |> deliverOnMainQueue).start(error: { error in + updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags), rank: updateRank) |> deliverOnMainQueue).start(error: { error in if case let .addMemberError(addMemberError) = error, let admin = adminView.peers[adminView.peerId] { var text = presentationData.strings.Login_UnknownError switch addMemberError { @@ -1201,7 +1201,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi updateState { current in return current.withUpdatedUpdating(true) } - updateRightsDisposable.set((addGroupAdmin(account: context.account, peerId: peerId, adminId: adminId) + updateRightsDisposable.set((context.engine.peers.addGroupAdmin(peerId: peerId, adminId: adminId) |> deliverOnMainQueue).start(error: { error in if case let .addMemberError(error) = error, case .privacy = error, let admin = adminView.peers[adminView.peerId] { presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(admin.compactDisplayTitle, admin.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) @@ -1234,7 +1234,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi guard let upgradedPeerId = upgradedPeerId else { return .fail(.conversionFailed) } - return context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: upgradedPeerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags), rank: updateRank) + return context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: upgradedPeerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags), rank: updateRank) |> mapError { error -> WrappedUpdateChannelAdminRightsError in return .direct(error) } diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index 57354a8240..d0b16d3986 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -583,14 +583,14 @@ public func channelAdminsController(context: AccountContext, peerId initialPeerI return $0.withUpdatedRemovingPeerId(adminId) } if peerId.namespace == Namespaces.Peer.CloudGroup { - removeAdminDisposable.set((removeGroupAdmin(account: context.account, peerId: peerId, adminId: adminId) + removeAdminDisposable.set((context.engine.peers.removeGroupAdmin(peerId: peerId, adminId: adminId) |> deliverOnMainQueue).start(completed: { updateState { return $0.withUpdatedRemovingPeerId(nil) } })) } else { - removeAdminDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: nil, rank: nil) + removeAdminDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: nil, rank: nil) |> deliverOnMainQueue).start(completed: { updateState { return $0.withUpdatedRemovingPeerId(nil) diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift index 352be4ea7c..37ce4689be 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift @@ -219,7 +219,7 @@ final class ChannelDiscussionGroupSearchContainerNode: SearchDisplayControllerCo } }) - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift index 06b5870298..fe3b052e30 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift @@ -437,7 +437,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon if peerId.namespace == Namespaces.Peer.CloudChannel { if case .searchAdmins = mode { - return context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: memberId, adminRights: nil, rank: nil) + return context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: memberId, adminRights: nil, rank: nil) |> `catch` { _ -> Signal in return .complete() } @@ -465,7 +465,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon } if case .searchAdmins = mode { - return removeGroupAdmin(account: context.account, peerId: peerId, adminId: memberId) + return context.engine.peers.removeGroupAdmin(peerId: peerId, adminId: memberId) |> `catch` { _ -> Signal in return .complete() } @@ -1279,10 +1279,10 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon } }) - self.emptyQueryListNode.beganInteractiveDragging = { [weak self] in + self.emptyQueryListNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index e4f1176aa7..0ae15a0ed8 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -581,7 +581,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } } - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in self?.view.endEditing(true) } } diff --git a/submodules/PeerInfoUI/Sources/OldChannelsController.swift b/submodules/PeerInfoUI/Sources/OldChannelsController.swift index 4da1e82f73..3a4edeb23e 100644 --- a/submodules/PeerInfoUI/Sources/OldChannelsController.swift +++ b/submodules/PeerInfoUI/Sources/OldChannelsController.swift @@ -455,21 +455,21 @@ public func oldChannelsController(context: AccountContext, intent: OldChannelsCo let state = stateValue.with { $0 } let _ = (peersPromise.get() |> take(1) - |> mapToSignal { peers in + |> mapToSignal { peers -> Signal in + let peers = peers ?? [] return context.account.postbox.transaction { transaction -> Void in - if let peers = peers { - for peer in peers { - if state.selectedPeers.contains(peer.peer.id) { - if transaction.getPeer(peer.peer.id) == nil { - updatePeers(transaction: transaction, peers: [peer.peer], update: { _, updated in - return updated - }) - } - removePeerChat(account: context.account, transaction: transaction, mediaBox: context.account.postbox.mediaBox, peerId: peer.peer.id, reportChatSpam: false, deleteGloballyIfPossible: false) + for peer in peers { + if state.selectedPeers.contains(peer.peer.id) { + if transaction.getPeer(peer.peer.id) == nil { + updatePeers(transaction: transaction, peers: [peer.peer], update: { _, updated in + return updated + }) } } } } + |> ignoreValues + |> then(context.engine.peers.removePeerChats(peerIds: Array(peers.map(\.peer.id)))) } |> deliverOnMainQueue).start(completed: { completed(true) diff --git a/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift b/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift index 4e27823a2f..685ca8cfc0 100644 --- a/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift +++ b/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift @@ -263,7 +263,7 @@ private final class OldChannelsSearchContainerNode: SearchDisplayControllerConte } }) - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } } diff --git a/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift b/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift index 9beb049164..0328661f80 100644 --- a/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift +++ b/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift @@ -174,7 +174,7 @@ private final class LocalizationListSearchContainerNode: SearchDisplayController } }) - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } } diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift index 8d98a8c01e..7689a657cd 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift @@ -1267,7 +1267,7 @@ private final class NotificationExceptionsSearchContainerNode: SearchDisplayCont } }) - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift index 750686026c..7b4c6980d7 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift @@ -469,7 +469,7 @@ public func passcodeEntryController(context: AccountContext, animateIn: Bool = t biometrics = .none } #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) }, modalPresentation: modalPresentation)) controller.presentationCompleted = { [weak controller] in diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift index 862cb1755b..4c1d7bb5b8 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift @@ -535,11 +535,11 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo } }) - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } - self.recentListNode.beganInteractiveDragging = { [weak self] in + self.recentListNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift index 39a9bd43b0..1f7ef63018 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift @@ -654,7 +654,7 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { } }) - self.recentListNode.beganInteractiveDragging = { [weak self] in + self.recentListNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } @@ -848,7 +848,7 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { } private func clearRecentSearch() { - let _ = (clearRecentlySearchedPeers(postbox: self.context.account.postbox) |> deliverOnMainQueue).start() + let _ = (self.context.engine.peers.clearRecentlySearchedPeers() |> deliverOnMainQueue).start() } override func scrollToTop() { diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 0aa3efa75b..9608e6275e 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -743,7 +743,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate }, extendedInitialReveal: self.presetText != nil, segmentedValues: self.segmentedValues) self.peersContentNode = peersContentNode peersContentNode.openSearch = { [weak self] in - let _ = (recentlySearchedPeers(postbox: context.account.postbox) + let _ = (context.engine.peers.recentlySearchedPeers() |> take(1) |> deliverOnMainQueue).start(next: { peers in if let strongSelf = self { diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 2bae41250a..649297977a 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -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 c.dismiss(completion: { 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))) } }) }))) diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index 7370caed78..d2fc365197 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -901,7 +901,7 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached } promotePeerImpl = { [weak controller] participantPeerId in if let navigationController = controller?.navigationController as? NavigationController { - let _ = (fetchChannelParticipant(account: context.account, peerId: peerId, participantId: participantPeerId) + let _ = (context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: participantPeerId) |> take(1) |> deliverOnMainQueue).start(next: { participant in if let participant = participant, let controller = context.sharedContext.makeChannelAdminController(context: context, peerId: peerId, adminId: participantPeerId, initialParticipant: participant) { diff --git a/submodules/StatisticsUI/Sources/MessageStatsController.swift b/submodules/StatisticsUI/Sources/MessageStatsController.swift index cd36e64526..3eebe518d1 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsController.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsController.swift @@ -264,7 +264,7 @@ public func messageStatsController(context: AccountContext, messageId: MessageId } navigateToMessageImpl = { [weak controller] messageId in 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 diff --git a/submodules/StatisticsUI/Sources/MessageStatsOverviewItem.swift b/submodules/StatisticsUI/Sources/MessageStatsOverviewItem.swift index d2bbfdfd00..339fc1a3f7 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsOverviewItem.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsOverviewItem.swift @@ -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())) - 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())) diff --git a/submodules/TelegramCore/Sources/AddPeerMember.swift b/submodules/TelegramCore/Sources/AddPeerMember.swift index 7ee9f8ec58..bde62c68df 100644 --- a/submodules/TelegramCore/Sources/AddPeerMember.swift +++ b/submodules/TelegramCore/Sources/AddPeerMember.swift @@ -80,7 +80,7 @@ public enum AddChannelMemberError { } public func addChannelMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal<(ChannelParticipant?, RenderedChannelParticipant), AddChannelMemberError> { - return fetchChannelParticipant(account: account, peerId: peerId, participantId: memberId) + return _internal_fetchChannelParticipant(account: account, peerId: peerId, participantId: memberId) |> mapError { error -> AddChannelMemberError in } |> mapToSignal { currentParticipant -> Signal<(ChannelParticipant?, RenderedChannelParticipant), AddChannelMemberError> in diff --git a/submodules/TelegramCore/Sources/MediaResourceApiUtils.swift b/submodules/TelegramCore/Sources/ApiUtils/MediaResourceApiUtils.swift similarity index 100% rename from submodules/TelegramCore/Sources/MediaResourceApiUtils.swift rename to submodules/TelegramCore/Sources/ApiUtils/MediaResourceApiUtils.swift diff --git a/submodules/TelegramCore/Sources/RegisterNotificationToken.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/RegisterNotificationToken.swift similarity index 79% rename from submodules/TelegramCore/Sources/RegisterNotificationToken.swift rename to submodules/TelegramCore/Sources/TelegramEngine/AccountData/RegisterNotificationToken.swift index 401e4706ee..9285316f4c 100644 --- a/submodules/TelegramCore/Sources/RegisterNotificationToken.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/RegisterNotificationToken.swift @@ -10,7 +10,7 @@ public enum NotificationTokenType { case voip } -public func unregisterNotificationToken(account: Account, token: Data, type: NotificationTokenType, otherAccountUserIds: [PeerId.Id]) -> Signal { +func _internal_unregisterNotificationToken(account: Account, token: Data, type: NotificationTokenType, otherAccountUserIds: [PeerId.Id]) -> Signal { let mappedType: Int32 switch type { case .aps: @@ -23,7 +23,7 @@ public func unregisterNotificationToken(account: Account, token: Data, type: Not |> ignoreValues } -public func registerNotificationToken(account: Account, token: Data, type: NotificationTokenType, sandbox: Bool, otherAccountUserIds: [PeerId.Id], excludeMutedChats: Bool) -> Signal { +func _internal_registerNotificationToken(account: Account, token: Data, type: NotificationTokenType, sandbox: Bool, otherAccountUserIds: [PeerId.Id], excludeMutedChats: Bool) -> Signal { return masterNotificationsKey(account: account, ignoreDisabled: false) |> mapToSignal { masterKey -> Signal in let mappedType: Int32 diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift index f9a025c8b2..8ea9a469ae 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift @@ -1,4 +1,7 @@ +import Foundation import SwiftSignalKit +import Postbox +import SyncCore public extension TelegramEngine { final class AccountData { @@ -35,5 +38,13 @@ public extension TelegramEngine { public func updateAbout(about: String?) -> Signal { return _internal_updateAbout(account: self.account, about: about) } + + public func unregisterNotificationToken(token: Data, type: NotificationTokenType, otherAccountUserIds: [PeerId.Id]) -> Signal { + return _internal_unregisterNotificationToken(account: self.account, token: token, type: type, otherAccountUserIds: otherAccountUserIds) + } + + public func registerNotificationToken(token: Data, type: NotificationTokenType, sandbox: Bool, otherAccountUserIds: [PeerId.Id], excludeMutedChats: Bool) -> Signal { + return _internal_registerNotificationToken(account: self.account, token: token, type: type, sandbox: sandbox, otherAccountUserIds: otherAccountUserIds, excludeMutedChats: excludeMutedChats) + } } } diff --git a/submodules/TelegramCore/Sources/EarliestUnseenPersonalMentionMessage.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift similarity index 95% rename from submodules/TelegramCore/Sources/EarliestUnseenPersonalMentionMessage.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift index 54c5a0b320..d3aa62b2bf 100644 --- a/submodules/TelegramCore/Sources/EarliestUnseenPersonalMentionMessage.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift @@ -10,7 +10,7 @@ public enum EarliestUnseenPersonalMentionMessageResult: Equatable { case result(MessageId?) } -public func earliestUnseenPersonalMentionMessage(account: Account, peerId: PeerId) -> Signal { +func _internal_earliestUnseenPersonalMentionMessage(account: Account, peerId: PeerId) -> Signal { return account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .lowerBound, anchorIndex: .lowerBound, count: 4, fixedCombinedReadStates: nil, tagMask: .unseenPersonalMessage, additionalData: [.peerChatState(peerId)]) |> mapToSignal { view -> Signal in if view.0.isLoading { diff --git a/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift similarity index 95% rename from submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift index fd5a79dc92..4b853a0b0f 100644 --- a/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift @@ -5,7 +5,7 @@ import SwiftSignalKit import SyncCore -public func installInteractiveReadMessagesAction(postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId) -> Disposable { +func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId) -> Disposable { return postbox.installStoreMessageAction(peerId: peerId, { messages, transaction in var consumeMessageIds: [MessageId] = [] diff --git a/submodules/TelegramCore/Sources/MarkMessageContentAsConsumedInteractively.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift similarity index 99% rename from submodules/TelegramCore/Sources/MarkMessageContentAsConsumedInteractively.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift index 53a1202972..7a39d5bb17 100644 --- a/submodules/TelegramCore/Sources/MarkMessageContentAsConsumedInteractively.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift @@ -5,7 +5,7 @@ import SwiftSignalKit import SyncCore -public func markMessageContentAsConsumedInteractively(postbox: Postbox, messageId: MessageId) -> Signal { +func _internal_markMessageContentAsConsumedInteractively(postbox: Postbox, messageId: MessageId) -> Signal { return postbox.transaction { transaction -> Void in if let message = transaction.getMessage(messageId), message.flags.contains(.Incoming) { var updateMessage = false diff --git a/submodules/TelegramCore/Sources/Polls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift similarity index 97% rename from submodules/TelegramCore/Sources/Polls.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift index f01fa7ac56..64f7d19967 100644 --- a/submodules/TelegramCore/Sources/Polls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift @@ -10,7 +10,7 @@ public enum RequestMessageSelectPollOptionError { case generic } -public func requestMessageSelectPollOption(account: Account, messageId: MessageId, opaqueIdentifiers: [Data]) -> Signal { +func _internal_requestMessageSelectPollOption(account: Account, messageId: MessageId, opaqueIdentifiers: [Data]) -> Signal { return account.postbox.loadedPeerWithId(messageId.peerId) |> take(1) |> castError(RequestMessageSelectPollOptionError.self) @@ -80,7 +80,7 @@ public func requestMessageSelectPollOption(account: Account, messageId: MessageI } } -public func requestClosePoll(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal { +func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal { return postbox.transaction { transaction -> (TelegramMediaPoll, Api.InputPeer)? in guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else { return nil @@ -419,7 +419,7 @@ public final class PollResultsContext { } } - public init(account: Account, messageId: MessageId, poll: TelegramMediaPoll) { + init(account: Account, messageId: MessageId, poll: TelegramMediaPoll) { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { return PollResultsContextImpl(queue: queue, account: account, messageId: messageId, poll: poll) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift index d0081e14f2..9b46a05b71 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift @@ -55,7 +55,7 @@ func _internal_requestStartBotInGroup(account: Account, botPeerId: PeerId, group |> mapToSignal { result -> Signal in account.stateManager.addUpdates(result) if groupPeerId.namespace == Namespaces.Peer.CloudChannel { - return fetchChannelParticipant(account: account, peerId: groupPeerId, participantId: botPeerId) + return _internal_fetchChannelParticipant(account: account, peerId: groupPeerId, participantId: botPeerId) |> mapError { _ -> RequestStartBotInGroupError in return .generic } |> mapToSignal { participant -> Signal in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index c29e9cfed1..144bf90c38 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1,3 +1,4 @@ +import Foundation import SwiftSignalKit import Postbox import SyncCore @@ -122,5 +123,29 @@ public extension TelegramEngine { public func getMessagesLoadIfNecessary(_ messageIds: [MessageId], strategy: GetMessagesStrategy = .cloud) -> Signal <[Message], NoError> { return _internal_getMessagesLoadIfNecessary(messageIds, postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId, strategy: strategy) } + + public func markMessageContentAsConsumedInteractively(messageId: MessageId) -> Signal { + return _internal_markMessageContentAsConsumedInteractively(postbox: self.account.postbox, messageId: messageId) + } + + public func installInteractiveReadMessagesAction(peerId: PeerId) -> Disposable { + return _internal_installInteractiveReadMessagesAction(postbox: self.account.postbox, stateManager: self.account.stateManager, peerId: peerId) + } + + public func requestMessageSelectPollOption(messageId: MessageId, opaqueIdentifiers: [Data]) -> Signal { + return _internal_requestMessageSelectPollOption(account: self.account, messageId: messageId, opaqueIdentifiers: opaqueIdentifiers) + } + + public func requestClosePoll(messageId: MessageId) -> Signal { + return _internal_requestClosePoll(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, messageId: messageId) + } + + public func pollResults(messageId: MessageId, poll: TelegramMediaPoll) -> PollResultsContext { + return PollResultsContext(account: self.account, messageId: messageId, poll: poll) + } + + public func earliestUnseenPersonalMentionMessage(peerId: PeerId) -> Signal { + return _internal_earliestUnseenPersonalMentionMessage(account: self.account, peerId: peerId) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift index 7cab29aba5..91bd5c9ef3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift @@ -6,7 +6,7 @@ import MtProtoKit import SyncCore func _internal_updateChannelMemberBannedRights(account: Account, peerId: PeerId, memberId: PeerId, rights: TelegramChatBannedRights?) -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> { - return fetchChannelParticipant(account: account, peerId: peerId, participantId: memberId) + return _internal_fetchChannelParticipant(account: account, peerId: peerId, participantId: memberId) |> mapToSignal { currentParticipant -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> in return account.postbox.transaction { transaction -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> in if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer), let _ = transaction.getPeer(account.peerId), let memberPeer = transaction.getPeer(memberId), let inputPeer = apiInputPeer(memberPeer) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift index 2abce2834d..8d57c103a8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift @@ -77,7 +77,7 @@ func _internal_updateChannelOwnership(account: Account, accountStateManager: Acc return .fail(.invalidPassword) } - return combineLatest(fetchChannelParticipant(account: account, peerId: channelId, participantId: account.peerId), fetchChannelParticipant(account: account, peerId: channelId, participantId: memberId)) + return combineLatest(_internal_fetchChannelParticipant(account: account, peerId: channelId, participantId: account.peerId), _internal_fetchChannelParticipant(account: account, peerId: channelId, participantId: memberId)) |> mapError { error -> ChannelOwnershipTransferError in return .generic } diff --git a/submodules/TelegramCore/Sources/PeerAdmins.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift similarity index 95% rename from submodules/TelegramCore/Sources/PeerAdmins.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift index db12ab124d..55ed9a46a1 100644 --- a/submodules/TelegramCore/Sources/PeerAdmins.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift @@ -10,7 +10,7 @@ public enum RemoveGroupAdminError { case generic } -public func removeGroupAdmin(account: Account, peerId: PeerId, adminId: PeerId) -> Signal { +func _internal_removeGroupAdmin(account: Account, peerId: PeerId, adminId: PeerId) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let adminPeer = transaction.getPeer(adminId), let inputUser = apiInputUser(adminPeer) { if let group = peer as? TelegramGroup { @@ -58,7 +58,7 @@ public enum AddGroupAdminError { case adminsTooMuch } -public func addGroupAdmin(account: Account, peerId: PeerId, adminId: PeerId) -> Signal { +func _internal_addGroupAdmin(account: Account, peerId: PeerId, adminId: PeerId) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let adminPeer = transaction.getPeer(adminId), let inputUser = apiInputUser(adminPeer) { if let group = peer as? TelegramGroup { @@ -127,7 +127,7 @@ public enum UpdateChannelAdminRightsError { case adminsTooMuch } -public func fetchChannelParticipant(account: Account, peerId: PeerId, participantId: PeerId) -> Signal { +func _internal_fetchChannelParticipant(account: Account, peerId: PeerId, participantId: PeerId) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let adminPeer = transaction.getPeer(participantId), let inputPeer = apiInputPeer(adminPeer) { if let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) { @@ -150,8 +150,8 @@ public func fetchChannelParticipant(account: Account, peerId: PeerId, participan } |> switchToLatest } -public func updateChannelAdminRights(account: Account, peerId: PeerId, adminId: PeerId, rights: TelegramChatAdminRights?, rank: String?) -> Signal<(ChannelParticipant?, RenderedChannelParticipant), UpdateChannelAdminRightsError> { - return fetchChannelParticipant(account: account, peerId: peerId, participantId: adminId) +func _internal_updateChannelAdminRights(account: Account, peerId: PeerId, adminId: PeerId, rights: TelegramChatAdminRights?, rank: String?) -> Signal<(ChannelParticipant?, RenderedChannelParticipant), UpdateChannelAdminRightsError> { + return _internal_fetchChannelParticipant(account: account, peerId: peerId, participantId: adminId) |> mapError { error -> UpdateChannelAdminRightsError in } |> mapToSignal { currentParticipant -> Signal<(ChannelParticipant?, RenderedChannelParticipant), UpdateChannelAdminRightsError> in diff --git a/submodules/TelegramCore/Sources/PeerSpecificStickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift similarity index 93% rename from submodules/TelegramCore/Sources/PeerSpecificStickerPack.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift index acd61c4511..c50de3ef03 100644 --- a/submodules/TelegramCore/Sources/PeerSpecificStickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift @@ -17,7 +17,7 @@ public struct PeerSpecificStickerPackData { public let canSetup: Bool } -public func peerSpecificStickerPack(postbox: Postbox, network: Network, peerId: PeerId) -> Signal { +func _internal_peerSpecificStickerPack(postbox: Postbox, network: Network, peerId: PeerId) -> Signal { if peerId.namespace == Namespaces.Peer.CloudChannel { let signal: Signal<(WrappedStickerPackCollectionInfo, Bool), NoError> = postbox.combinedView(keys: [.cachedPeerData(peerId: peerId)]) |> map { view -> (WrappedStickerPackCollectionInfo, Bool) in diff --git a/submodules/TelegramCore/Sources/RecentlySearchedPeerIds.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentlySearchedPeerIds.swift similarity index 92% rename from submodules/TelegramCore/Sources/RecentlySearchedPeerIds.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentlySearchedPeerIds.swift index ad9544f4dc..f54fc35884 100644 --- a/submodules/TelegramCore/Sources/RecentlySearchedPeerIds.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentlySearchedPeerIds.swift @@ -4,19 +4,19 @@ import SwiftSignalKit import SyncCore -public func addRecentlySearchedPeer(postbox: Postbox, peerId: PeerId) -> Signal { +func _internal_addRecentlySearchedPeer(postbox: Postbox, peerId: PeerId) -> Signal { return postbox.transaction { transaction -> Void in transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, item: OrderedItemListEntry(id: RecentPeerItemId(peerId).rawValue, contents: RecentPeerItem(rating: 0.0)), removeTailIfCountExceeds: 20) } } -public func removeRecentlySearchedPeer(postbox: Postbox, peerId: PeerId) -> Signal { +func _internal_removeRecentlySearchedPeer(postbox: Postbox, peerId: PeerId) -> Signal { return postbox.transaction { transaction -> Void in transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, itemId: RecentPeerItemId(peerId).rawValue) } } -public func clearRecentlySearchedPeers(postbox: Postbox) -> Signal { +func _internal_clearRecentlySearchedPeers(postbox: Postbox) -> Signal { return postbox.transaction { transaction -> Void in transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, items: []) } @@ -34,7 +34,7 @@ public struct RecentlySearchedPeer: Equatable { public let subpeerSummary: RecentlySearchedPeerSubpeerSummary? } -public func recentlySearchedPeers(postbox: Postbox) -> Signal<[RecentlySearchedPeer], NoError> { +func _internal_recentlySearchedPeers(postbox: Postbox) -> Signal<[RecentlySearchedPeer], NoError> { return postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.RecentlySearchedPeerIds)]) |> mapToSignal { view -> Signal<[RecentlySearchedPeer], NoError> in var peerIds: [PeerId] = [] diff --git a/submodules/TelegramCore/Sources/RemovePeerChat.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/RemovePeerChat.swift similarity index 87% rename from submodules/TelegramCore/Sources/RemovePeerChat.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/RemovePeerChat.swift index 1b72cd2cdd..f5c31ba638 100644 --- a/submodules/TelegramCore/Sources/RemovePeerChat.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/RemovePeerChat.swift @@ -4,13 +4,13 @@ import SwiftSignalKit import SyncCore -public func removePeerChat(account: Account, peerId: PeerId, reportChatSpam: Bool, deleteGloballyIfPossible: Bool = false) -> Signal { +func _internal_removePeerChat(account: Account, peerId: PeerId, reportChatSpam: Bool, deleteGloballyIfPossible: Bool = false) -> Signal { return account.postbox.transaction { transaction -> Void in - removePeerChat(account: account, transaction: transaction, mediaBox: account.postbox.mediaBox, peerId: peerId, reportChatSpam: reportChatSpam, deleteGloballyIfPossible: deleteGloballyIfPossible) + _internal_removePeerChat(account: account, transaction: transaction, mediaBox: account.postbox.mediaBox, peerId: peerId, reportChatSpam: reportChatSpam, deleteGloballyIfPossible: deleteGloballyIfPossible) } } -public func terminateSecretChat(transaction: Transaction, peerId: PeerId, requestRemoteHistoryRemoval: Bool) { +func _internal_terminateSecretChat(transaction: Transaction, peerId: PeerId, requestRemoteHistoryRemoval: Bool) { if let state = transaction.getPeerChatState(peerId) as? SecretChatState, state.embeddedState != .terminated { let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: SecretChatOutgoingOperationContents.terminate(reportSpam: false, requestRemoteHistoryRemoval: requestRemoteHistoryRemoval), state: state).withUpdatedEmbeddedState(.terminated) if updatedState != state { @@ -24,7 +24,7 @@ public func terminateSecretChat(transaction: Transaction, peerId: PeerId, reques } } -public func removePeerChat(account: Account, transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, reportChatSpam: Bool, deleteGloballyIfPossible: Bool) { +func _internal_removePeerChat(account: Account, transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, reportChatSpam: Bool, deleteGloballyIfPossible: Bool) { if let _ = transaction.getPeerChatInterfaceState(peerId) { transaction.updatePeerChatInterfaceState(peerId, update: { current in if let current = current { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 7250b414bd..dc78be2755 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -231,5 +231,61 @@ public extension TelegramEngine { public func peerCommands(id: PeerId) -> Signal { return _internal_peerCommands(account: self.account, id: id) } + + public func addGroupAdmin(peerId: PeerId, adminId: PeerId) -> Signal { + return _internal_addGroupAdmin(account: self.account, peerId: peerId, adminId: adminId) + } + + public func removeGroupAdmin(peerId: PeerId, adminId: PeerId) -> Signal { + return _internal_removeGroupAdmin(account: self.account, peerId: peerId, adminId: adminId) + } + + public func fetchChannelParticipant(peerId: PeerId, participantId: PeerId) -> Signal { + return _internal_fetchChannelParticipant(account: self.account, peerId: peerId, participantId: participantId) + } + + public func updateChannelAdminRights(peerId: PeerId, adminId: PeerId, rights: TelegramChatAdminRights?, rank: String?) -> Signal<(ChannelParticipant?, RenderedChannelParticipant), UpdateChannelAdminRightsError> { + return _internal_updateChannelAdminRights(account: self.account, peerId: peerId, adminId: adminId, rights: rights, rank: rank) + } + + public func peerSpecificStickerPack(peerId: PeerId) -> Signal { + return _internal_peerSpecificStickerPack(postbox: self.account.postbox, network: self.account.network, peerId: peerId) + } + + public func addRecentlySearchedPeer(peerId: PeerId) -> Signal { + return _internal_addRecentlySearchedPeer(postbox: self.account.postbox, peerId: peerId) + } + + public func removeRecentlySearchedPeer(peerId: PeerId) -> Signal { + return _internal_removeRecentlySearchedPeer(postbox: self.account.postbox, peerId: peerId) + } + + public func clearRecentlySearchedPeers() -> Signal { + return _internal_clearRecentlySearchedPeers(postbox: self.account.postbox) + } + + public func recentlySearchedPeers() -> Signal<[RecentlySearchedPeer], NoError> { + return _internal_recentlySearchedPeers(postbox: self.account.postbox) + } + + public func removePeerChat(peerId: PeerId, reportChatSpam: Bool, deleteGloballyIfPossible: Bool = false) -> Signal { + return _internal_removePeerChat(account: self.account, peerId: peerId, reportChatSpam: reportChatSpam, deleteGloballyIfPossible: deleteGloballyIfPossible) + } + + public func removePeerChats(peerIds: [PeerId]) -> Signal { + return self.account.postbox.transaction { transaction -> Void in + for peerId in peerIds { + _internal_removePeerChat(account: self.account, transaction: transaction, mediaBox: self.account.postbox.mediaBox, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: peerId.namespace == Namespaces.Peer.SecretChat) + } + } + |> ignoreValues + } + + public func terminateSecretChat(peerId: PeerId, requestRemoteHistoryRemoval: Bool) -> Signal { + return self.account.postbox.transaction { transaction -> Void in + _internal_terminateSecretChat(transaction: transaction, peerId: peerId, requestRemoteHistoryRemoval: requestRemoteHistoryRemoval) + } + |> ignoreValues + } } } diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index ec59cf61f4..2d12daaa85 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -45,24 +45,35 @@ private let handleVoipNotifications = false private var testIsLaunched = false -private func encodeText(_ string: String, _ key: Int) -> String { - var result = "" - for c in string.unicodeScalars { - result.append(Character(UnicodeScalar(UInt32(Int(c.value) + key))!)) +private func isKeyboardWindow(window: NSObject) -> Bool { + let typeName = NSStringFromClass(type(of: window)) + if #available(iOS 9.0, *) { + if typeName.hasPrefix("UI") && typeName.hasSuffix("RemoteKeyboardWindow") { + return true + } + } else { + if typeName.hasPrefix("UI") && typeName.hasSuffix("TextEffectsWindow") { + return true + } } - return result + return false } -private let keyboardViewClass: AnyClass? = NSClassFromString(encodeText("VJJoqvuTfuIptuWjfx", -1))! -private let keyboardViewContainerClass: AnyClass? = NSClassFromString(encodeText("VJJoqvuTfuDpoubjofsWjfx", -1))! - -private let keyboardWindowClass: AnyClass? = { - if #available(iOS 9.0, *) { - return NSClassFromString(encodeText("VJSfnpufLfzcpbseXjoepx", -1)) - } else { - return NSClassFromString(encodeText("VJUfyuFggfdutXjoepx", -1)) +private func isKeyboardView(view: NSObject) -> Bool { + let typeName = NSStringFromClass(type(of: view)) + if typeName.hasPrefix("UI") && typeName.hasSuffix("InputSetHostView") { + return true } -}() + return false +} + +private func isKeyboardViewContainer(view: NSObject) -> Bool { + let typeName = NSStringFromClass(type(of: view)) + if typeName.hasPrefix("UI") && typeName.hasSuffix("InputSetContainerView") { + return true + } + return false +} private class ApplicationStatusBarHost: StatusBarHost { private let application = UIApplication.shared @@ -96,12 +107,8 @@ private class ApplicationStatusBarHost: StatusBarHost { } var keyboardWindow: UIWindow? { - guard let keyboardWindowClass = keyboardWindowClass else { - return nil - } - for window in UIApplication.shared.windows { - if window.isKind(of: keyboardWindowClass) { + if isKeyboardWindow(window: window) { return window } } @@ -109,14 +116,14 @@ private class ApplicationStatusBarHost: StatusBarHost { } var keyboardView: UIView? { - guard let keyboardWindow = self.keyboardWindow, let keyboardViewContainerClass = keyboardViewContainerClass, let keyboardViewClass = keyboardViewClass else { + guard let keyboardWindow = self.keyboardWindow else { return nil } for view in keyboardWindow.subviews { - if view.isKind(of: keyboardViewContainerClass) { + if isKeyboardViewContainer(view: view) { for subview in view.subviews { - if subview.isKind(of: keyboardViewClass) { + if isKeyboardView(view: subview) { return subview } } diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index 3b5106cb76..20adc4ffc6 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -759,7 +759,7 @@ final class AuthorizedApplicationContext { } 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 { @@ -838,7 +838,7 @@ final class AuthorizedApplicationContext { if visiblePeerId != peerId || messageId != 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 { self.scheduledOpenChatWithPeerId = (peerId, messageId, activateInput) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 0a0e87ffe1..f14b19780d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -106,13 +106,13 @@ private enum ChatRecordingActivity { } public enum NavigateToMessageLocation { - case id(MessageId) + case id(MessageId, Double?) case index(MessageIndex) case upperBound(PeerId) var messageId: MessageId? { switch self { - case let .id(id): + case let .id(id, _): return id case let .index(index): return index.id @@ -123,7 +123,7 @@ public enum NavigateToMessageLocation { var peerId: PeerId { switch self { - case let .id(id): + case let .id(id, _): return id.peerId case let .index(index): return index.id.peerId @@ -571,7 +571,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .pinnedMessageUpdated: for attribute in message.attributes { 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 } } @@ -580,7 +580,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .gameScore: for attribute in message.attributes { 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 } } @@ -996,9 +996,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, openMessageContextActions: { message, node, rect, gesture in gesture?.cancel() }, 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 - 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 self?.chatDisplayNode.dismissInput() }, toggleMessagesSelection: { [weak self] ids, value in @@ -1875,7 +1875,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let message = message else { return } + let context = strongSelf.context + let chatPresentationInterfaceState = strongSelf.presentationInterfaceState 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: [ ActionSheetTextItem(title: text), 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) } }), - 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() - 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) + 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 + + 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) + } + }) + } 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: [ ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in @@ -2231,7 +2281,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G disposables = DisposableDict() strongSelf.selectMessagePollOptionDisposables = disposables } - let signal = requestMessageSelectPollOption(account: strongSelf.context.account, messageId: id, opaqueIdentifiers: opaqueIdentifiers) + let signal = strongSelf.context.engine.messages.requestMessageSelectPollOption(messageId: id, opaqueIdentifiers: opaqueIdentifiers) disposables.set((signal |> deliverOnMainQueue).start(next: { resultPoll in guard let strongSelf = self, let resultPoll = resultPoll else { @@ -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) } } else { - strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId)) + strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId, nil)) } break } @@ -4535,7 +4585,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatDisplayNode.historyNode.scrolledToIndex = { [weak self] toIndex, initial in 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) } else if let controllerInteraction = strongSelf.controllerInteraction { 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 if let strongSelf = self, strongSelf.isNodeLoaded { 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 { if case .known = strongSelf.chatDisplayNode.historyNode.visibleContentOffset() { strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() @@ -4853,13 +4909,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatDisplayNode.navigateButtons.mentionsPressed = { [weak self] in if let strongSelf = self, strongSelf.isNodeLoaded, case let .peer(peerId) = strongSelf.chatLocation { - let signal = earliestUnseenPersonalMentionMessage(account: strongSelf.context.account, peerId: peerId) + let signal = strongSelf.context.engine.messages.earliestUnseenPersonalMentionMessage(peerId: peerId) strongSelf.navigationActionDisposable.set((signal |> deliverOnMainQueue).start(next: { result in if let strongSelf = self { switch result { case let .result(messageId): if let messageId = messageId { - strongSelf.navigateToMessage(from: nil, to: .id(messageId)) + strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil)) } case .loading: break @@ -5422,7 +5478,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { strongSelf.loadingMessage.set(.single(nil)) 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() } }, 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 guard let strongSelf = self else { return @@ -6329,7 +6385,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let controller = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil)) strongSelf.present(controller, in: .window(.root)) - let signal = requestMessageSelectPollOption(account: strongSelf.context.account, messageId: id, opaqueIdentifiers: []) + let signal = strongSelf.context.engine.messages.requestMessageSelectPollOption(messageId: id, opaqueIdentifiers: []) |> afterDisposed { [weak controller] in Queue.mainQueue().async { controller?.dismiss() @@ -6391,7 +6447,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let controller = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil)) strongSelf.present(controller, in: .window(.root)) - let signal = requestClosePoll(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, stateManager: strongSelf.context.account.stateManager, messageId: id) + let signal = strongSelf.context.engine.messages.requestClosePoll(messageId: id) |> afterDisposed { [weak controller] in Queue.mainQueue().async { controller?.dismiss() @@ -6598,7 +6654,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } 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)) } }, 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 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? if let atMessageId = atMessageId { - subject = .message(id: atMessageId, highlight: true) + subject = .message(id: atMessageId, highlight: true, timecode: nil) } else if let index = result.scrollToLowerBoundMessage { - subject = .message(id: index.id, highlight: false) + subject = .message(id: index.id, highlight: false, timecode: nil) } else { subject = nil } @@ -10603,11 +10659,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if isPinnedMessages, let messageId = messageLocation.messageId { if let navigationController = self.effectiveNavigationController { 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)) { 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 { if let _ = fromId, let fromIndex = fromIndex, rememberInStack { @@ -10629,13 +10685,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.messageIndexDisposable.set(nil) self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition) 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 { self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition) } else { self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue())) let searchLocation: ChatHistoryInitialSearchLocation switch messageLocation { - case let .id(id): + case let .id(id, _): searchLocation = .id(id) case let .index(index): searchLocation = .index(index) @@ -10728,7 +10788,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let fromIndex = fromIndex { let searchLocation: ChatHistoryInitialSearchLocation switch messageLocation { - case let .id(id): + case let .id(id, _): searchLocation = .id(id) case let .index(index): searchLocation = .index(index) @@ -10762,7 +10822,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G completion?() } else { 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?() } @@ -10774,7 +10834,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } else { 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?() } @@ -11361,12 +11421,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let _ = strongSelf.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.id, isBlocked: true).start() if let _ = chatPeer as? TelegramSecretChat { - let _ = (strongSelf.context.account.postbox.transaction { transaction in - terminateSecretChat(transaction: transaction, peerId: chatPeer.id, requestRemoteHistoryRemoval: true) - }).start() + let _ = strongSelf.context.engine.peers.terminateSecretChat(peerId: chatPeer.id, requestRemoteHistoryRemoval: true).start() } if deleteChat { - let _ = removePeerChat(account: strongSelf.context.account, peerId: chatPeer.id, reportChatSpam: reportSpam).start() + let _ = strongSelf.context.engine.peers.removePeerChat(peerId: chatPeer.id, reportChatSpam: reportSpam).start() strongSelf.effectiveNavigationController?.filterController(strongSelf, animated: true) } else if reportSpam { let _ = strongSelf.context.engine.peers.reportPeer(peerId: peer.id, reason: .spam, message: "").start() @@ -11502,7 +11560,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.commitPurposefulAction() self.chatDisplayNode.historyNode.disconnect() - let _ = removePeerChat(account: self.context.account, peerId: peerId, reportChatSpam: reportChatSpam).start() + let _ = self.context.engine.peers.removePeerChat(peerId: peerId, reportChatSpam: reportChatSpam).start() self.effectiveNavigationController?.popToRoot(animated: true) let _ = self.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peerId, isBlocked: true).start() @@ -11532,8 +11590,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch navigation { case let .chat(_, subject, peekData): if case .peer(peerId) = strongSelf.chatLocation { - if let subject = subject, case let .message(messageId, _) = subject { - strongSelf.navigateToMessage(from: nil, to: .id(messageId)) + if let subject = subject, case let .message(messageId, _, timecode) = subject { + strongSelf.navigateToMessage(from: nil, to: .id(messageId, timecode)) } } 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)) @@ -11748,7 +11806,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private func presentBanMessageOptions(accountPeerId: PeerId, author: Peer, messageIds: Set, options: ChatAvailableMessageActionOptions) { let peerId = self.chatLocation.peerId do { - self.navigationActionDisposable.set((fetchChannelParticipant(account: self.context.account, peerId: peerId, participantId: author.id) + self.navigationActionDisposable.set((self.context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: author.id) |> deliverOnMainQueue).start(next: { [weak self] participant in if let strongSelf = self { let canBan = participant?.canBeBannedBy(peerId: accountPeerId) ?? true diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 53558487a3..19193334b4 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -1248,7 +1248,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let previousInputPanelBackgroundFrame = self.inputPanelBackgroundNode.frame 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.navigateButtons, frame: apparentNavigateButtonsFrame) @@ -2276,8 +2276,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { func sendCurrentMessage(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, completion: @escaping () -> Void = {}) { if let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { - if textInputPanelNode.textInputNode?.isFirstResponder() ?? false { - Keyboard.applyAutocorrection() + if let textInputNode = textInputPanelNode.textInputNode, textInputNode.isFirstResponder() { + Keyboard.applyAutocorrection(textView: textInputNode.textView) } var effectivePresentationInterfaceState = self.chatPresentationInterfaceState diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 4f3f2fcf65..383e50988d 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -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) } 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) } 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) @@ -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) } 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) @@ -1193,7 +1193,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } }).start() - self.beganInteractiveDragging = { [weak self] in + self.beganInteractiveDragging = { [weak self] _ in self?.isInteractivelyScrollingValue = true self?.isInteractivelyScrollingPromise.set(true) self?.beganDragging?() @@ -2001,7 +2001,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } else if self.interactiveReadActionDisposable == nil { if case let .peer(peerId) = self.chatLocation { if !self.context.sharedContext.immediateExperimentalUISettings.skipReadHistory { - self.interactiveReadActionDisposable = installInteractiveReadMessagesAction(postbox: self.context.account.postbox, stateManager: self.context.account.stateManager, peerId: peerId) + self.interactiveReadActionDisposable = self.context.engine.messages.installInteractiveReadMessagesAction(peerId: peerId) } } } diff --git a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift index bfdfe79955..50ef7caa52 100644 --- a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift @@ -231,7 +231,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { } })) - self.listNode.beganInteractiveDragging = { [weak self] in + self.listNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() } diff --git a/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift index 4df8a95697..3490c85396 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift @@ -21,17 +21,19 @@ final class ChatMediaInputMetaSectionItem: ListViewItem { let inputNodeInteraction: ChatMediaInputNodeInteraction let type: ChatMediaInputMetaSectionItemType let theme: PresentationTheme + let expanded: Bool let selectedItem: () -> Void var selectable: Bool { 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.type = type self.selectedItem = selected 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?, (ListViewItemApply) -> Void)) -> Void) { @@ -40,11 +42,11 @@ final class ChatMediaInputMetaSectionItem: ListViewItem { Queue.mainQueue().async { node.inputNodeInteraction = self.inputNodeInteraction node.setItem(item: self) - node.updateTheme(theme: self.theme) + node.updateTheme(theme: self.theme, expanded: self.expanded) node.updateIsHighlighted() 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) 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) { 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)?.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 boundingImageSize = CGSize(width: 30.0, height: 30.0) -private let highlightSize = CGSize(width: 35.0, height: 35.0) +private let boundingSize = CGSize(width: 72.0, height: 41.0) +private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) +private let boundingImageScale: CGFloat = 0.625 +private let highlightSize = CGSize(width: 56.0, height: 56.0) private let verticalOffset: CGFloat = 3.0 + UIScreenPixel final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { + private let containerNode: ASDisplayNode + private let scalingNode: ASDisplayNode private let imageNode: ASImageNode private let textNodeContainer: ASDisplayNode private let textNode: ImmediateTextNode private let highlightNode: ASImageNode + private let titleNode: ImmediateTextNode + + private var currentExpanded = false var item: ChatMediaInputMetaSectionItem? var currentCollectionId: ItemCollectionId? @@ -88,6 +96,11 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { var theme: PresentationTheme? 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.isLayerBacked = true self.highlightNode.isHidden = true @@ -105,22 +118,17 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { self.textNodeContainer.addSubnode(self.textNode) 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.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) + self.titleNode = ImmediateTextNode() super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.highlightNode) - self.addSubnode(self.imageNode) - self.addSubnode(self.textNodeContainer) + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.scalingNode) - 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) - - 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.highlightNode) + self.scalingNode.addSubnode(self.titleNode) + self.scalingNode.addSubnode(self.imageNode) + self.scalingNode.addSubnode(self.textNodeContainer) } 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 { self.theme = theme self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme) + var title = "" if let item = self.item { switch item.type { case .savedStickers: self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelSavedStickersIcon(theme) + title = "Favorites" case .recentStickers: self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentStickersIcon(theme) + title = "Recent" case .stickersMode: self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelStickersModeIcon(theme) + title = "Stickers" case .savedGifs: self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentStickersIcon(theme) + title = "GIFs" case .trendingGifs: self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingGifsIcon(theme) + title = "Trending" case let .gifEmoji(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 #available(iOSApplicationExtension 12.1, iOS 12.1, *) { } else { @@ -165,12 +208,34 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { } } 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)) 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() { diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index a3d3f084ad..c514206dbb 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -34,6 +34,7 @@ struct ChatMediaInputPanelTransition { let deletions: [ListViewDeleteItem] let insertions: [ListViewInsertItem] let updates: [ListViewUpdateItem] + let scrollToItem: ListViewScrollToItem? } struct ChatMediaInputGridTransition { @@ -47,14 +48,14 @@ struct ChatMediaInputGridTransition { 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 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 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 { @@ -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) } -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] = [] if hasGifs { - entries.append(.recentGifs(theme)) + entries.append(.recentGifs(theme, expanded)) } if let hasUnreadTrending = hasUnreadTrending { - entries.append(.trending(hasUnreadTrending, theme)) + entries.append(.trending(hasUnreadTrending, theme, expanded)) } if let savedStickers = savedStickers, !savedStickers.items.isEmpty { - entries.append(.savedStickers(theme)) + entries.append(.savedStickers(theme, expanded)) } var savedStickerIds = Set() if let savedStickers = savedStickers, !savedStickers.items.isEmpty { @@ -182,40 +183,40 @@ func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: Ordere } } if found { - entries.append(.recentPacks(theme)) + entries.append(.recentPacks(theme, expanded)) } } 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 { - entries.append(.peerSpecific(theme: theme, peer: peer)) + entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded)) } var index = 0 for (_, info, item) in view.collectionInfos { 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 } } 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 { - entries.append(.settings(theme)) + entries.append(.settings(theme, expanded)) } return entries } -func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, reactions: [String]) -> [ChatMediaInputPanelEntry] { +func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, reactions: [String], expanded: Bool) -> [ChatMediaInputPanelEntry] { var entries: [ChatMediaInputPanelEntry] = [] - entries.append(.stickersMode(theme)) - entries.append(.savedGifs(theme)) - entries.append(.trendingGifs(theme)) + entries.append(.stickersMode(theme, expanded)) + entries.append(.savedGifs(theme, expanded)) + entries.append(.trendingGifs(theme, expanded)) for reaction in reactions { - entries.append(.gifEmotion(entries.count, theme, reaction)) + entries.append(.gifEmotion(entries.count, theme, reaction, expanded)) } return entries @@ -451,6 +452,15 @@ final class ChatMediaInputNode: ChatInputNode { private var currentView: ItemCollectionsView? private let dismissedPeerSpecificStickerPack = Promise() + private var panelCollapseScrollToIndex: Int? + private let panelExpandedPromise = ValuePromise(false) + private var panelExpanded: Bool = false { + didSet { + self.panelExpandedPromise.set(self.panelExpanded) + } + } + private var panelCollapseTimer: SwiftSignalKit.Timer? + var requestDisableStickerAnimations: ((Bool) -> Void)? 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.listView = ListView() +// self.listView.clipsToBounds = false self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0) self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false self.listView.accessibilityPageScrolledString = { row, count in @@ -502,6 +513,7 @@ final class ChatMediaInputNode: ChatInputNode { } self.gifListView = ListView() +// self.gifListView.clipsToBounds = false self.gifListView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0) self.gifListView.scroller.panGestureRecognizer.cancelsTouchesInView = false self.gifListView.accessibilityPageScrolledString = { row, count in @@ -527,7 +539,7 @@ final class ChatMediaInputNode: ChatInputNode { 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() @@ -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 { strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace) strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) @@ -761,7 +772,7 @@ final class ChatMediaInputNode: ChatInputNode { return false }) - peerSpecificPack = combineLatest(peerSpecificStickerPack(postbox: context.account.postbox, network: context.account.network, peerId: peerId), context.account.postbox.multiplePeersView([peerId]), self.dismissedPeerSpecificStickerPack.get()) + peerSpecificPack = combineLatest(context.engine.peers.peerSpecificStickerPack(peerId: peerId), context.account.postbox.multiplePeersView([peerId]), self.dismissedPeerSpecificStickerPack.get()) |> map { packData, peersView, dismissedPeerSpecificPack -> (PeerSpecificPackData?, CanInstallPeerSpecificPack) in if let peer = peersView.peers[peerId] { var canInstall: CanInstallPeerSpecificPack = .none @@ -781,7 +792,7 @@ final class ChatMediaInputNode: ChatInputNode { } 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 } 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(value: nil) let transitionQueue = Queue() - let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions) - |> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in + let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions, self.panelExpandedPromise.get()) + |> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions, panelExpanded -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in let (view, viewUpdate) = viewAndUpdate let previous = previousView.swap(view) 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 gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, reactions: reactions) + 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, expanded: panelExpanded) var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, strings: strings, theme: theme) if view.higher == nil { @@ -901,9 +912,9 @@ final class ChatMediaInputNode: ChatInputNode { } } } - + 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 @@ -970,7 +981,6 @@ final class ChatMediaInputNode: ChatInputNode { self.stickerPane.inputNodeInteraction = self.inputNodeInteraction self.gifPane.inputNodeInteraction = self.inputNodeInteraction - //self.trendingPane.inputNodeInteraction = self.inputNodeInteraction paneDidScrollImpl = { [weak self] pane, state, transition in 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 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 { self.disposable.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) { @@ -1215,7 +1273,7 @@ final class ChatMediaInputNode: ChatInputNode { } } } else { - panes = [strongSelf.gifPane, strongSelf.stickerPane/*, strongSelf.trendingPane*/] + panes = [strongSelf.gifPane, strongSelf.stickerPane] } let panelPoint = strongSelf.view.convert(point, to: strongSelf.collectionListPanel.view) if panelPoint.y < strongSelf.collectionListPanel.frame.maxY { @@ -1367,19 +1425,11 @@ final class ChatMediaInputNode: ChatInputNode { } 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 { 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) 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 { 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) self.updateAppearanceTransition(transition: transition) @@ -1396,23 +1446,7 @@ final class ChatMediaInputNode: ChatInputNode { } else if let collectionIdHint = collectionIdHint { 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 { 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) @@ -1425,10 +1459,6 @@ final class ChatMediaInputNode: ChatInputNode { if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs { self.inputNodeInteraction.highlightedItemCollectionId = collectionId } - } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue { - /*if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending { - self.inputNodeInteraction.highlightedItemCollectionId = collectionId - }*/ } else { self.inputNodeInteraction.highlightedStickerItemCollectionId = collectionId if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .stickers { @@ -1469,31 +1499,56 @@ final class ChatMediaInputNode: ChatInputNode { } itemNode.updateIsHighlighted() if itemNode.currentCollectionId == collectionId { - self.listView.ensureItemNodeVisible(itemNode) + if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) { + self.panelCollapseScrollToIndex = targetIndex + self.updateIsExpanded(false) + } else { + self.listView.ensureItemNodeVisible(itemNode) + } ensuredNodeVisible = true } } else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode { itemNode.updateIsHighlighted() if itemNode.currentCollectionId == collectionId { - self.listView.ensureItemNodeVisible(itemNode) + if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) { + self.panelCollapseScrollToIndex = targetIndex + self.updateIsExpanded(false) + } else { + self.listView.ensureItemNodeVisible(itemNode) + } ensuredNodeVisible = true } } else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode { itemNode.updateIsHighlighted() if itemNode.currentCollectionId == collectionId { - self.listView.ensureItemNodeVisible(itemNode) + if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) { + self.panelCollapseScrollToIndex = targetIndex + self.updateIsExpanded(false) + } else { + self.listView.ensureItemNodeVisible(itemNode) + } ensuredNodeVisible = true } } else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode { itemNode.updateIsHighlighted() if itemNode.currentCollectionId == collectionId { - self.listView.ensureItemNodeVisible(itemNode) + if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) { + self.panelCollapseScrollToIndex = targetIndex + self.updateIsExpanded(false) + } else { + self.listView.ensureItemNodeVisible(itemNode) + } ensuredNodeVisible = true } } else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode { itemNode.updateIsHighlighted() if itemNode.currentCollectionId == collectionId { - self.listView.ensureItemNodeVisible(itemNode) + if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) { + self.panelCollapseScrollToIndex = targetIndex + self.updateIsExpanded(false) + } else { + self.listView.ensureItemNodeVisible(itemNode) + } ensuredNodeVisible = true } } @@ -1504,7 +1559,12 @@ final class ChatMediaInputNode: ChatInputNode { let firstVisibleIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == firstVisibleCollectionId }) if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex { let toRight = targetIndex > firstVisibleIndex - 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) + 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) + } } } } @@ -1516,8 +1576,6 @@ final class ChatMediaInputNode: ChatInputNode { return self.stickerPane.collectionListPanelOffset case .gifs: return self.gifPane.collectionListPanelOffset - /*case .trending: - return self.trendingPane.collectionListPanelOffset*/ } } @@ -1611,7 +1669,6 @@ final class ChatMediaInputNode: ChatInputNode { } self.stickerPane.collectionListPanelOffset = 0.0 self.gifPane.collectionListPanelOffset = 0.0 - //self.trendingPane.collectionListPanelOffset = 0.0 self.updateAppearanceTransition(transition: transition) } else { panelHeight = standardInputHeight @@ -1642,11 +1699,6 @@ final class ChatMediaInputNode: ChatInputNode { } } case .trending: - /*self.trendingPane.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? PaneSearchBarPlaceholderNode { - placeholderNode = itemNode - } - }*/ 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.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)) - 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)) 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 }) @@ -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.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.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 !visiblePanes.contains(where: { $0.0 == .gifs }) { @@ -1773,31 +1824,6 @@ final class ChatMediaInputNode: ChatInputNode { 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 { self.searchContainerNode = nil self.searchContainerNodeLoadedDisposable.set(nil) @@ -1820,12 +1846,6 @@ final class ChatMediaInputNode: ChatInputNode { } } case .trending: - /*self.trendingPane.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? PaneSearchBarPlaceholderNode { - placeholderNode = itemNode - } - } - paneIsEmpty = true*/ break } } @@ -1866,7 +1886,20 @@ final class ChatMediaInputNode: ChatInputNode { } else { 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 { strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime) if !strongSelf.didSetReady { @@ -1904,7 +1937,6 @@ final class ChatMediaInputNode: ChatInputNode { } self.searchContainerNode?.contentNode.updatePreviewing(animated: animated) - //self.trendingPane.updatePreviewing(animated: animated) } } @@ -1921,11 +1953,6 @@ final class ChatMediaInputNode: ChatInputNode { self.animatingStickerPaneOut = false self.stickerPane.removeFromSupernode() } - /*self.trendingPane.layer.removeAllAnimations() - if self.animatingTrendingPaneOut { - self.animatingTrendingPaneOut = false - self.trendingPane.removeFromSupernode() - }*/ case .changed: if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { 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) - 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.gifListView, position: CGPoint(x: self.gifListView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0)) + transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: listPanelOffset), size: self.collectionListPanel.bounds.size)) + transition.updatePosition(node: self.listView, position: CGPoint(x: self.listView.position.x, y: (41.0 - listPanelOffset) / 2.0)) + transition.updatePosition(node: self.gifListView, position: CGPoint(x: self.gifListView.position.x, y: (41.0 - listPanelOffset) / 2.0)) self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: collectionListPanelOffset, transition: transition) } 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)) - transition.updateSublayerTransformOffset(layer: self.paneClippingContainer.layer, offset: CGPoint(x: 0.0, y: -offset - 41.0)) + var offset = offset + 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) { @@ -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) 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.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.gifListView, position: CGPoint(x: self.gifListView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0)) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift b/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift index 25336491a5..bb8b80ea07 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift @@ -32,18 +32,18 @@ enum ChatMediaInputPanelEntryStableId: Hashable { } enum ChatMediaInputPanelEntry: Comparable, Identifiable { - case recentGifs(PresentationTheme) - case savedStickers(PresentationTheme) - case recentPacks(PresentationTheme) - case trending(Bool, PresentationTheme) - case settings(PresentationTheme) - case peerSpecific(theme: PresentationTheme, peer: Peer) - case stickerPack(index: Int, info: StickerPackCollectionInfo, topItem: StickerPackItem?, theme: PresentationTheme) + case recentGifs(PresentationTheme, Bool) + case savedStickers(PresentationTheme, Bool) + case recentPacks(PresentationTheme, Bool) + case trending(Bool, PresentationTheme, Bool) + case settings(PresentationTheme, Bool) + case peerSpecific(theme: PresentationTheme, peer: Peer, expanded: Bool) + case stickerPack(index: Int, info: StickerPackCollectionInfo, topItem: StickerPackItem?, theme: PresentationTheme, expanded: Bool) - case stickersMode(PresentationTheme) - case savedGifs(PresentationTheme) - case trendingGifs(PresentationTheme) - case gifEmotion(Int, PresentationTheme, String) + case stickersMode(PresentationTheme, Bool) + case savedGifs(PresentationTheme, Bool) + case trendingGifs(PresentationTheme, Bool) + case gifEmotion(Int, PresentationTheme, String, Bool) var stableId: ChatMediaInputPanelEntryStableId { switch self { @@ -59,7 +59,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { return .settings case .peerSpecific: return .peerSpecific - case let .stickerPack(_, info, _, _): + case let .stickerPack(_, info, _, _, _): return .stickerPack(info.id.id) case .stickersMode: return .stickersMode @@ -67,75 +67,75 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { return .savedGifs case .trendingGifs: return .trendingGifs - case let .gifEmotion(_, _, emoji): + case let .gifEmotion(_, _, emoji, _): return .gifEmotion(emoji) } } static func ==(lhs: ChatMediaInputPanelEntry, rhs: ChatMediaInputPanelEntry) -> Bool { switch lhs { - case let .recentGifs(lhsTheme): - if case let .recentGifs(rhsTheme) = rhs, lhsTheme === rhsTheme { + case let .recentGifs(lhsTheme, lhsExpanded): + if case let .recentGifs(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded { return true } else { return false } - case let .savedStickers(lhsTheme): - if case let .savedStickers(rhsTheme) = rhs, lhsTheme === rhsTheme { + case let .savedStickers(lhsTheme, lhsExpanded): + if case let .savedStickers(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded { return true } else { return false } - case let .recentPacks(lhsTheme): - if case let .recentPacks(rhsTheme) = rhs, lhsTheme === rhsTheme { + case let .recentPacks(lhsTheme, lhsExpanded): + if case let .recentPacks(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded { return true } else { return false } - case let .trending(lhsElevated, lhsTheme): - if case let .trending(rhsElevated, rhsTheme) = rhs, lhsTheme === rhsTheme, lhsElevated == rhsElevated { + case let .trending(lhsElevated, lhsTheme, lhsExpanded): + if case let .trending(rhsElevated, rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsElevated == rhsElevated, lhsExpanded == rhsExpanded { return true } else { return false } - case let .settings(lhsTheme): - if case let .settings(rhsTheme) = rhs, lhsTheme === rhsTheme { + case let .settings(lhsTheme, lhsExpanded): + if case let .settings(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded { return true } else { return false } - case let .peerSpecific(lhsTheme, lhsPeer): - if case let .peerSpecific(rhsTheme, rhsPeer) = rhs, lhsTheme === rhsTheme, lhsPeer.isEqual(rhsPeer) { + case let .peerSpecific(lhsTheme, lhsPeer, lhsExpanded): + if case let .peerSpecific(rhsTheme, rhsPeer, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsPeer.isEqual(rhsPeer), lhsExpanded == rhsExpanded { return true } else { return false } - case let .stickerPack(index, info, topItem, lhsTheme): - if case let .stickerPack(rhsIndex, rhsInfo, rhsTopItem, rhsTheme) = rhs, index == rhsIndex, info == rhsInfo, topItem == rhsTopItem, lhsTheme === rhsTheme { + case let .stickerPack(index, info, topItem, lhsTheme, lhsExpanded): + if case let .stickerPack(rhsIndex, rhsInfo, rhsTopItem, rhsTheme, rhsExpanded) = rhs, index == rhsIndex, info == rhsInfo, topItem == rhsTopItem, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded { return true } else { return false } - case let .stickersMode(lhsTheme): - if case let .stickersMode(rhsTheme) = rhs, lhsTheme === rhsTheme { + case let .stickersMode(lhsTheme, lhsExpanded): + if case let .stickersMode(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded { return true } else { return false } - case let .savedGifs(lhsTheme): - if case let .savedGifs(rhsTheme) = rhs, lhsTheme === rhsTheme { + case let .savedGifs(lhsTheme, lhsExpanded): + if case let .savedGifs(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded { return true } else { return false } - case let .trendingGifs(lhsTheme): - if case let .trendingGifs(rhsTheme) = rhs, lhsTheme === rhsTheme { + case let .trendingGifs(lhsTheme, lhsExpanded): + if case let .trendingGifs(rhsTheme, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded { return true } else { return false } - case let .gifEmotion(lhsIndex, lhsTheme, lhsEmoji): - if case let .gifEmotion(rhsIndex, rhsTheme, rhsEmoji) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsEmoji == rhsEmoji { + case let .gifEmotion(lhsIndex, lhsTheme, lhsEmoji, lhsExpanded): + if case let .gifEmotion(rhsIndex, rhsTheme, rhsEmoji, rhsExpanded) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsEmoji == rhsEmoji, lhsExpanded == rhsExpanded { return true } else { return false @@ -156,7 +156,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { switch rhs { case .recentGifs, savedStickers: return false - case let .trending(elevated, _) where elevated: + case let .trending(elevated, _, _) where elevated: return false default: return true @@ -165,7 +165,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { switch rhs { case .recentGifs, .savedStickers, recentPacks: return false - case let .trending(elevated, _) where elevated: + case let .trending(elevated, _, _) where elevated: return false default: return true @@ -174,16 +174,16 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { switch rhs { case .recentGifs, .savedStickers, recentPacks, .peerSpecific: return false - case let .trending(elevated, _) where elevated: + case let .trending(elevated, _, _) where elevated: return false default: return true } - case let .stickerPack(lhsIndex, lhsInfo, _, _): + case let .stickerPack(lhsIndex, lhsInfo, _, _, _): switch rhs { case .recentGifs, .savedStickers, .recentPacks, .peerSpecific: return false - case let .trending(elevated, _): + case let .trending(elevated, _, _): if elevated { return false } else { @@ -191,7 +191,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { } case .settings: return true - case let .stickerPack(rhsIndex, rhsInfo, _, _): + case let .stickerPack(rhsIndex, rhsInfo, _, _, _): if lhsIndex == rhsIndex { return lhsInfo.id.id < rhsInfo.id.id } else { @@ -200,7 +200,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { default: return true } - case let .trending(elevated, _): + case let .trending(elevated, _, _): if elevated { switch rhs { case .recentGifs, .trending: @@ -231,11 +231,11 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { default: return true } - case let .gifEmotion(lhsIndex, _, _): + case let .gifEmotion(lhsIndex, _, _, _): switch rhs { case .stickersMode, .savedGifs, .trendingGifs: return false - case let .gifEmotion(rhsIndex, _, _): + case let .gifEmotion(rhsIndex, _, _, _): return lhsIndex < rhsIndex default: return true @@ -251,53 +251,53 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { func item(context: AccountContext, inputNodeInteraction: ChatMediaInputNodeInteraction) -> ListViewItem { switch self { - case let .recentGifs(theme): - return ChatMediaInputRecentGifsItem(inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { + case let .recentGifs(theme, expanded): + return ChatMediaInputRecentGifsItem(inputNodeInteraction: inputNodeInteraction, theme: theme, expanded: expanded, selected: { let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue, id: 0) inputNodeInteraction.navigateToCollectionId(collectionId) }) - case let .savedStickers(theme): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedStickers, theme: theme, selected: { + case let .savedStickers(theme, expanded): + return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedStickers, theme: theme, expanded: expanded, selected: { let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0) inputNodeInteraction.navigateToCollectionId(collectionId) }) - case let .recentPacks(theme): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .recentStickers, theme: theme, selected: { + case let .recentPacks(theme, expanded): + return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .recentStickers, theme: theme, expanded: expanded, selected: { let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0) inputNodeInteraction.navigateToCollectionId(collectionId) }) - case let .trending(elevated, theme): - return ChatMediaInputTrendingItem(inputNodeInteraction: inputNodeInteraction, elevated: elevated, theme: theme, selected: { + case let .trending(elevated, theme, expanded): + return ChatMediaInputTrendingItem(inputNodeInteraction: inputNodeInteraction, elevated: elevated, theme: theme, expanded: expanded, selected: { let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0) inputNodeInteraction.navigateToCollectionId(collectionId) }) - case let .settings(theme): - return ChatMediaInputSettingsItem(inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { + case let .settings(theme, expanded): + return ChatMediaInputSettingsItem(inputNodeInteraction: inputNodeInteraction, theme: theme, expanded: expanded, selected: { inputNodeInteraction.openSettings() }) - case let .peerSpecific(theme, peer): + case let .peerSpecific(theme, peer, expanded): 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) }) - case let .stickerPack(index, info, topItem, theme): - return ChatMediaInputStickerPackItem(account: context.account, inputNodeInteraction: inputNodeInteraction, collectionId: info.id, collectionInfo: info, stickerPackItem: topItem, index: index, theme: theme, selected: { + 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, expanded: expanded, selected: { inputNodeInteraction.navigateToCollectionId(info.id) }) - case let .stickersMode(theme): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .stickersMode, theme: theme, selected: { + case let .stickersMode(theme, expanded): + return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .stickersMode, theme: theme, expanded: expanded, selected: { inputNodeInteraction.navigateBackToStickers() }) - case let .savedGifs(theme): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedGifs, theme: theme, selected: { + case let .savedGifs(theme, expanded): + return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedGifs, theme: theme, expanded: expanded, selected: { inputNodeInteraction.setGifMode(.recent) }) - case let .trendingGifs(theme): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .trendingGifs, theme: theme, selected: { + case let .trendingGifs(theme, expanded): + return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .trendingGifs, theme: theme, expanded: expanded, selected: { inputNodeInteraction.setGifMode(.trending) }) - case let .gifEmotion(_, theme, emoji): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji), theme: theme, selected: { + case let .gifEmotion(_, theme, emoji, expanded): + return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji), theme: theme, expanded: expanded, selected: { inputNodeInteraction.setGifMode(.emojiSearch(emoji)) }) } diff --git a/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift index 834fcb7b06..8d4d0181fc 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift @@ -15,6 +15,7 @@ final class ChatMediaInputPeerSpecificItem: ListViewItem { let inputNodeInteraction: ChatMediaInputNodeInteraction let collectionId: ItemCollectionId let peer: Peer + let expanded: Bool let selectedItem: () -> Void let theme: PresentationTheme @@ -22,12 +23,13 @@ final class ChatMediaInputPeerSpecificItem: ListViewItem { 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.inputNodeInteraction = inputNodeInteraction self.collectionId = collectionId self.peer = peer self.selectedItem = selected + self.expanded = expanded self.theme = theme } diff --git a/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift index d0926be1a2..76ae81d613 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift @@ -11,28 +11,30 @@ import TelegramPresentationData final class ChatMediaInputRecentGifsItem: ListViewItem { let inputNodeInteraction: ChatMediaInputNodeInteraction let selectedItem: () -> Void + let expanded: Bool let theme: PresentationTheme var selectable: Bool { return true } - init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, selected: @escaping () -> Void) { + init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) { self.inputNodeInteraction = inputNodeInteraction self.selectedItem = selected 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?, (ListViewItemApply) -> Void)) -> Void) { async { 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.inputNodeInteraction = self.inputNodeInteraction - node.updateTheme(theme: self.theme) node.updateIsHighlighted() node.updateAppearanceTransition(transition: .immediate) Queue.mainQueue().async { + node.updateTheme(theme: self.theme, expanded: self.expanded) completion(node, { 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) { Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in - (node() as? ChatMediaInputRecentGifsItemNode)?.updateTheme(theme: self.theme) + completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in + (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 boundingImageSize = CGSize(width: 30.0, height: 30.0) -private let highlightSize = CGSize(width: 35.0, height: 35.0) +private let boundingSize = CGSize(width: 72.0, height: 41.0) +private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) +private let boundingImageScale: CGFloat = 0.625 +private let highlightSize = CGSize(width: 56.0, height: 56.0) private let verticalOffset: CGFloat = 3.0 + UIScreenPixel final class ChatMediaInputRecentGifsItemNode: ListViewItemNode { + private let containerNode: ASDisplayNode + private let scalingNode: ASDisplayNode private let imageNode: ASImageNode private let highlightNode: ASImageNode + private let titleNode: ImmediateTextNode + + private var currentExpanded = false var currentCollectionId: ItemCollectionId? var inputNodeInteraction: ChatMediaInputNodeInteraction? @@ -68,40 +76,68 @@ final class ChatMediaInputRecentGifsItemNode: ListViewItemNode { var theme: PresentationTheme? 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.isLayerBacked = true self.highlightNode.isHidden = true self.imageNode = ASImageNode() 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.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + self.titleNode = ImmediateTextNode() super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.highlightNode) - self.addSubnode(self.imageNode) + self.addSubnode(self.containerNode) + 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) - - 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 { } - func updateTheme(theme: PresentationTheme) { + func updateTheme(theme: PresentationTheme, expanded: Bool) { if self.theme !== theme { self.theme = theme self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(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() { diff --git a/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift index 15a69c1d51..a1e60e847a 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift @@ -11,27 +11,29 @@ import TelegramPresentationData final class ChatMediaInputSettingsItem: ListViewItem { let inputNodeInteraction: ChatMediaInputNodeInteraction let selectedItem: () -> Void + let expanded: Bool let theme: PresentationTheme var selectable: Bool { return true } - init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, selected: @escaping () -> Void) { + init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) { self.inputNodeInteraction = inputNodeInteraction self.selectedItem = selected 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?, (ListViewItemApply) -> Void)) -> Void) { async { 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.inputNodeInteraction = self.inputNodeInteraction - node.updateTheme(theme: self.theme) node.updateAppearanceTransition(transition: .immediate) Queue.mainQueue().async { + node.updateTheme(theme: self.theme, expanded: self.expanded) completion(node, { 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) { Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in - (node() as? ChatMediaInputSettingsItemNode)?.updateTheme(theme: self.theme) + completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in + (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 boundingImageSize = CGSize(width: 30.0, height: 30.0) -private let highlightSize = CGSize(width: 35.0, height: 35.0) +private let boundingSize = CGSize(width: 72.0, height: 41.0) +private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) +private let boundingImageScale: CGFloat = 0.625 private let verticalOffset: CGFloat = 3.0 + UIScreenPixel final class ChatMediaInputSettingsItemNode: ListViewItemNode { + private let containerNode: ASDisplayNode + private let scalingNode: ASDisplayNode private let buttonNode: HighlightableButtonNode private let imageNode: ASImageNode + private let titleNode: ImmediateTextNode + + private var currentExpanded = false var currentCollectionId: ItemCollectionId? var inputNodeInteraction: ChatMediaInputNodeInteraction? @@ -67,37 +74,59 @@ final class ChatMediaInputSettingsItemNode: ListViewItemNode { var theme: PresentationTheme? 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.imageNode = ASImageNode() self.imageNode.isLayerBacked = true - self.imageNode.contentMode = .center - self.imageNode.contentsScale = UIScreenScale - - self.buttonNode.frame = CGRect(origin: CGPoint(), size: boundingSize) - - self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - self.imageNode.contentMode = .center - self.imageNode.contentsScale = UIScreenScale + self.titleNode = ImmediateTextNode() + super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.buttonNode) - self.buttonNode.addSubnode(self.imageNode) + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.scalingNode) - 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) + self.scalingNode.addSubnode(self.buttonNode) + self.scalingNode.addSubnode(self.imageNode) } - - deinit { - } - - 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) + if self.theme !== theme { self.theme = 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) { diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift index dafba8f3f1..49aea9ed90 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift @@ -22,12 +22,13 @@ final class ChatMediaInputStickerPackItem: ListViewItem { let selectedItem: () -> Void let index: Int let theme: PresentationTheme + let expanded: Bool var selectable: Bool { 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.inputNodeInteraction = inputNodeInteraction self.collectionId = collectionId @@ -36,18 +37,19 @@ final class ChatMediaInputStickerPackItem: ListViewItem { self.selectedItem = selected self.index = index 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?, (ListViewItemApply) -> Void)) -> Void) { async { let node = ChatMediaInputStickerPackItemNode() - node.contentSize = boundingSize + node.contentSize = self.expanded ? expandedBoundingSize : boundingSize node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem) node.inputNodeInteraction = self.inputNodeInteraction Queue.mainQueue().async { completion(node, { 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) }) }) @@ -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) { Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: node().contentSize, 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) + 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, expanded: self.expanded) }) } } @@ -68,20 +70,26 @@ final class ChatMediaInputStickerPackItem: ListViewItem { } } -private let boundingSize = CGSize(width: 41.0, height: 41.0) -private let boundingImageSize = CGSize(width: 28.0, height: 28.0) -private let highlightSize = CGSize(width: 35.0, height: 35.0) -private let verticalOffset: CGFloat = 3.0 +private let boundingSize = CGSize(width: 72.0, height: 41.0) +private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) +private let boundingImageSize = CGSize(width: 45.0, height: 45.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 { + private let containerNode: ASDisplayNode + private let scalingNode: ASDisplayNode private let imageNode: TransformImageNode private var animatedStickerNode: AnimatedStickerNode? private var placeholderNode: StickerShimmerEffectNode? private let highlightNode: ASImageNode + private let titleNode: ImmediateTextNode var inputNodeInteraction: ChatMediaInputNodeInteraction? var currentCollectionId: ItemCollectionId? private var currentThumbnailItem: StickerPackThumbnailItem? + private var currentExpanded = false private var theme: PresentationTheme? private let stickerFetchedDisposable = MetaDisposable() @@ -102,6 +110,11 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { } 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.isLayerBacked = true self.highlightNode.isHidden = true @@ -110,18 +123,19 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { self.imageNode.isLayerBacked = !smartInvertColorsEnabled() 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.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - + + self.titleNode = ImmediateTextNode() + super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.highlightNode) - self.addSubnode(self.imageNode) + self.addSubnode(self.containerNode) + 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 { - self.addSubnode(placeholderNode) + self.scalingNode.addSubnode(placeholderNode) } 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 + var themeUpdated = false if self.theme !== theme { self.theme = theme + themeUpdated = true 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 { self.currentThumbnailItem = thumbnailItem if let thumbnailItem = thumbnailItem { switch thumbnailItem { 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())) imageApply() 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): - let imageSize = boundingImageSize let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) imageApply() 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 self.imageNode.isHidden = loopAnimatedStickers @@ -212,18 +232,14 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { } else { animatedStickerNode = AnimatedStickerNode() self.animatedStickerNode = animatedStickerNode - animatedStickerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) if let placeholderNode = self.placeholderNode { - self.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode) + self.scalingNode.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode) } else { - self.addSubnode(animatedStickerNode) + self.scalingNode.addSubnode(animatedStickerNode) } animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 80, height: 80, mode: .cached) } 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 { self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: resourceReference).start()) @@ -232,16 +248,43 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { if let placeholderNode = self.placeholderNode { 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)) } 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) { if let placeholderNode = self.placeholderNode { placeholderNode.updateAbsoluteRect(rect, within: containerSize) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift index 4b25c0ab63..1c25f037f8 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift @@ -12,29 +12,31 @@ final class ChatMediaInputTrendingItem: ListViewItem { let inputNodeInteraction: ChatMediaInputNodeInteraction let selectedItem: () -> Void let elevated: Bool + let expanded: Bool let theme: PresentationTheme var selectable: Bool { 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.elevated = elevated self.selectedItem = selected + self.expanded = expanded self.theme = theme } func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { async { 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.inputNodeInteraction = self.inputNodeInteraction - node.updateTheme(elevated: self.elevated, theme: self.theme) node.updateIsHighlighted() node.updateAppearanceTransition(transition: .immediate) Queue.mainQueue().async { + node.updateTheme(elevated: self.elevated, theme: self.theme, expanded: self.expanded) completion(node, { 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) { Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in - (node() as? ChatMediaInputTrendingItemNode)?.updateTheme(elevated: self.elevated, theme: self.theme) + 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, expanded: self.expanded) }) } } @@ -55,14 +57,20 @@ final class ChatMediaInputTrendingItem: ListViewItem { } } -private let boundingSize = CGSize(width: 41.0, height: 41.0) -private let boundingImageSize = CGSize(width: 30.0, height: 30.0) -private let highlightSize = CGSize(width: 35.0, height: 35.0) +private let boundingSize = CGSize(width: 72.0, height: 41.0) +private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) +private let boundingImageScale: CGFloat = 0.625 +private let highlightSize = CGSize(width: 56.0, height: 56.0) private let verticalOffset: CGFloat = 3.0 + UIScreenPixel final class ChatMediaInputTrendingItemNode: ListViewItemNode { + private let containerNode: ASDisplayNode + private let scalingNode: ASDisplayNode private let imageNode: ASImageNode private let highlightNode: ASImageNode + private let titleNode: ImmediateTextNode + + private var currentExpanded = false var currentCollectionId: ItemCollectionId? var inputNodeInteraction: ChatMediaInputNodeInteraction? @@ -73,37 +81,43 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode { let badgeBackground: ASImageNode 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.isLayerBacked = true self.highlightNode.isHidden = true self.imageNode = ASImageNode() self.imageNode.isLayerBacked = true - self.imageNode.contentMode = .center - self.imageNode.contentsScale = UIScreenScale self.badgeBackground = ASImageNode() self.badgeBackground.displaysAsynchronously = false self.badgeBackground.displayWithoutProcessing = 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.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + self.titleNode = ImmediateTextNode() super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.highlightNode) - self.addSubnode(self.imageNode) - self.addSubnode(self.badgeBackground) + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.scalingNode) + + 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) } - deinit { - } - - func updateTheme(elevated: Bool, theme: PresentationTheme) { + 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 + if self.theme !== theme { self.theme = theme @@ -111,19 +125,37 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode { self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingIconImage(theme) 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 { - 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 { self.elevated = 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() { diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 7ac4decd62..dcc9ab4721 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1278,7 +1278,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { 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) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index f89744e8d6..034af0b2fe 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -3012,7 +3012,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { 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) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 8fc9243c32..473d47fe08 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -731,7 +731,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { 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) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index e7e794379c..43b041813b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -884,7 +884,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { 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) } } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 22f27d32ce..5c0076a5bb 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -839,7 +839,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if canBan { actions.append(ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuBan, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuBan), action: { [weak self] in if let strongSelf = self { - strongSelf.banDisposables.set((fetchChannelParticipant(account: strongSelf.context.account, peerId: strongSelf.peer.id, participantId: author.id) + strongSelf.banDisposables.set((strongSelf.context.engine.peers.fetchChannelParticipant(peerId: strongSelf.peer.id, participantId: author.id) |> deliverOnMainQueue).start(next: { participant in if let strongSelf = self { strongSelf.presentController(channelBannedMemberController(context: strongSelf.context, peerId: strongSelf.peer.id, memberId: author.id, initialParticipant: participant, updated: { _ in }, upgradedToSupergroup: { _, f in f() }), .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) @@ -905,13 +905,13 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { break case .groupBotStart: break - case let .channelMessage(peerId, messageId): + case let .channelMessage(peerId, messageId, timecode): 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): 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): let packReference: StickerPackReference = .name(name) diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index a5aafc5f90..cbe30e00d4 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -205,7 +205,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe switch item.content { case let .peer(peer): 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) let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single([]), reactionItems: [], gesture: gesture) presentInGlobalOverlay(contextController) diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index 0c1a300b7f..9aebe8dbb9 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -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 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)) - 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 @@ -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 (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 diff --git a/submodules/TelegramUI/Sources/MediaManager.swift b/submodules/TelegramUI/Sources/MediaManager.swift index 717f765e27..3a1d3e1b1f 100644 --- a/submodules/TelegramUI/Sources/MediaManager.swift +++ b/submodules/TelegramUI/Sources/MediaManager.swift @@ -417,7 +417,7 @@ public final class MediaManagerImpl: NSObject, MediaManager { if let (account, stateOrLoading, type) = accountStateAndType { switch type { case .music: - minimumStoreDuration = 15.0 * 60.0 + minimumStoreDuration = 10.0 * 60.0 case .voice: minimumStoreDuration = 5.0 * 60.0 case .file: diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index fe5d204ab5..f464fc7811 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -20,10 +20,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if let updateTextInputState = params.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 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 { let _ = navigationController.popToViewController(controller, animated: animated) } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 132f9be876..888d39f43a 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -97,8 +97,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur } dismissInput() navigationController?.pushViewController(controller) - case let .channelMessage(peerId, messageId): - openPeer(peerId, .chat(textInputState: nil, subject: .message(id: messageId, highlight: true), peekData: nil)) + case let .channelMessage(peerId, messageId, timecode): + openPeer(peerId, .chat(textInputState: nil, subject: .message(id: messageId, highlight: true, timecode: timecode), peekData: nil)) case let .replyThreadMessage(replyThreadMessage, messageId): if let navigationController = navigationController { let _ = ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 6d5e5b4a98..748d69ccf2 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -187,7 +187,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu 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() @@ -263,7 +263,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu self.contentNode.addSubnode(self.historyNode) self.contentNode.addSubnode(self.controlsNode) - self.historyNode.beganInteractiveDragging = { [weak self] in + self.historyNode.beganInteractiveDragging = { [weak self] _ in self?.controlsNode.collapse() } @@ -528,7 +528,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu } let chatLocationContextHolder = Atomic(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.stackFromBottom = true 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() } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 6f6d4fcd61..d5f10e6eec 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1738,7 +1738,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD c.dismiss(completion: { if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { 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 indexesToRemove = Set() var keptCurrentChatController = false @@ -1884,7 +1884,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD c.dismiss(completion: { if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { 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 indexesToRemove = Set() var keptCurrentChatController = false @@ -2775,7 +2775,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD 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 - UIPasteboard.general.string = username + UIPasteboard.general.string = "@\(username)" if let strongSelf = self { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } @@ -4294,7 +4294,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD strongSelf.activeActionDisposable.set(strongSelf.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.id, isBlocked: true).start()) if deleteChat { - let _ = removePeerChat(account: strongSelf.context.account, peerId: strongSelf.peerId, reportChatSpam: reportSpam).start() + let _ = strongSelf.context.engine.peers.removePeerChat(peerId: strongSelf.peerId, reportChatSpam: reportSpam).start() (strongSelf.controller?.navigationController as? NavigationController)?.popToRoot(animated: true) } else if reportSpam { let _ = strongSelf.context.engine.peers.reportPeer(peerId: strongSelf.peerId, reason: .spam, message: "").start() @@ -6239,8 +6239,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD private var canOpenAvatarByDragging = false - private let velocityKey: String = encodeText("`wfsujdbmWfmpdjuz", -1) - func scrollViewDidScroll(_ scrollView: UIScrollView) { if self.ignoreScrolling { return @@ -6249,7 +6247,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD if !self.state.isEditing { if self.canAddVelocity { self.previousVelocityM1 = self.previousVelocity - if let value = (scrollView.value(forKey: self.velocityKey) as? NSNumber)?.doubleValue { + if let value = (scrollView.value(forKey: (["_", "verticalVelocity"] as [String]).joined()) as? NSNumber)?.doubleValue { self.previousVelocity = CGFloat(value) } } @@ -7063,14 +7061,6 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig } } -private func encodeText(_ string: String, _ key: Int) -> String { - var result = "" - for c in string.unicodeScalars { - result.append(Character(UnicodeScalar(UInt32(Int(c.value) + key))!)) - } - return result -} - private final class ContextControllerContentSourceImpl: ContextControllerContentSource { let controller: ViewController weak var sourceNode: ASDisplayNode? diff --git a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift index 28f65a474c..f6aade2b53 100644 --- a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift @@ -808,7 +808,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { default: break } - let _ = markMessageContentAsConsumedInteractively(postbox: self.context.account.postbox, messageId: item.message.id).start() + let _ = self.context.engine.messages.markMessageContentAsConsumedInteractively(messageId: item.message.id).start() } } } diff --git a/submodules/TelegramUI/Sources/PollResultsController.swift b/submodules/TelegramUI/Sources/PollResultsController.swift index 0763e1f166..4dea982934 100644 --- a/submodules/TelegramUI/Sources/PollResultsController.swift +++ b/submodules/TelegramUI/Sources/PollResultsController.swift @@ -323,7 +323,7 @@ public func pollResultsController(context: AccountContext, messageId: MessageId, let actionsDisposable = DisposableSet() - let resultsContext = PollResultsContext(account: context.account, messageId: messageId, poll: poll) + let resultsContext = context.engine.messages.pollResults(messageId: messageId, poll: poll) let arguments = PollResultsControllerArguments(context: context, collapseOption: { optionId in diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 83170b3a6b..90679827a6 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -872,7 +872,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let token = token else { return .complete() } - return unregisterNotificationToken(account: account, token: token, type: .aps(encrypt: false), otherAccountUserIds: (account.testingEnvironment ? allTestingUserIds : allProductionUserIds).filter({ $0 != account.peerId.id })) + return TelegramEngine(account: account).accountData.unregisterNotificationToken(token: token, type: .aps(encrypt: false), otherAccountUserIds: (account.testingEnvironment ? allTestingUserIds : allProductionUserIds).filter({ $0 != account.peerId.id })) } appliedVoip = self.voipNotificationToken |> distinctUntilChanged(isEqual: { $0 == $1 }) @@ -880,7 +880,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let token = token else { return .complete() } - return unregisterNotificationToken(account: account, token: token, type: .voip, otherAccountUserIds: (account.testingEnvironment ? allTestingUserIds : allProductionUserIds).filter({ $0 != account.peerId.id })) + return TelegramEngine(account: account).accountData.unregisterNotificationToken(token: token, type: .voip, otherAccountUserIds: (account.testingEnvironment ? allTestingUserIds : allProductionUserIds).filter({ $0 != account.peerId.id })) } } else { appliedAps = self.apsNotificationToken @@ -895,7 +895,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } else { encrypt = false } - return registerNotificationToken(account: account, token: token, type: .aps(encrypt: encrypt), sandbox: sandbox, otherAccountUserIds: (account.testingEnvironment ? activeTestingUserIds : activeProductionUserIds).filter({ $0 != account.peerId.id }), excludeMutedChats: !settings.includeMuted) + return TelegramEngine(account: account).accountData.registerNotificationToken(token: token, type: .aps(encrypt: encrypt), sandbox: sandbox, otherAccountUserIds: (account.testingEnvironment ? activeTestingUserIds : activeProductionUserIds).filter({ $0 != account.peerId.id }), excludeMutedChats: !settings.includeMuted) } appliedVoip = self.voipNotificationToken |> distinctUntilChanged(isEqual: { $0 == $1 }) @@ -903,7 +903,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let token = token else { return .complete() } - return registerNotificationToken(account: account, token: token, type: .voip, sandbox: sandbox, otherAccountUserIds: (account.testingEnvironment ? activeTestingUserIds : activeProductionUserIds).filter({ $0 != account.peerId.id }), excludeMutedChats: !settings.includeMuted) + return TelegramEngine(account: account).accountData.registerNotificationToken(token: token, type: .voip, sandbox: sandbox, otherAccountUserIds: (account.testingEnvironment ? activeTestingUserIds : activeProductionUserIds).filter({ $0 != account.peerId.id }), excludeMutedChats: !settings.includeMuted) } } diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index f9bc83d904..3ae0393bb1 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -58,9 +58,9 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate context.sharedContext.applicationBindings.openUrl(url) case let .peer(peerId, navigation): openResolvedPeerImpl(peerId, navigation) - case let .channelMessage(peerId, messageId): + case let .channelMessage(peerId, messageId, timecode): 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): if let navigationController = controller.navigationController as? NavigationController { diff --git a/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift b/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift index 7ec4920062..4a3c25ab61 100644 --- a/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift +++ b/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift @@ -378,8 +378,8 @@ public final class PeerChannelMemberCategoriesContextsManager { } } - public func updateMemberAdminRights(account: Account, peerId: PeerId, memberId: PeerId, adminRights: TelegramChatAdminRights?, rank: String?) -> Signal { - return updateChannelAdminRights(account: account, peerId: peerId, adminId: memberId, rights: adminRights, rank: rank) + public func updateMemberAdminRights(engine: TelegramEngine, peerId: PeerId, memberId: PeerId, adminRights: TelegramChatAdminRights?, rank: String?) -> Signal { + return engine.peers.updateChannelAdminRights(peerId: peerId, adminId: memberId, rights: adminRights, rank: rank) |> map(Optional.init) |> deliverOnMainQueue |> beforeNext { [weak self] result in diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m index 76eac05f37..0b33273d6b 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m @@ -84,38 +84,10 @@ CGFloat springAnimationValueAtImpl(CABasicAnimation * _Nonnull animation, CGFloa @interface CustomBlurEffect : UIBlurEffect -/*@property (nonatomic) double blurRadius; -@property (nonatomic) double colorBurnTintAlpha; -@property (nonatomic) double colorBurnTintLevel; -@property (nonatomic, retain) UIColor *colorTint; -@property (nonatomic) double colorTintAlpha; -@property (nonatomic) bool darkenWithSourceOver; -@property (nonatomic) double darkeningTintAlpha; -@property (nonatomic) double darkeningTintHue; -@property (nonatomic) double darkeningTintSaturation; -@property (nonatomic) double grayscaleTintAlpha; -@property (nonatomic) double grayscaleTintLevel; -@property (nonatomic) bool lightenGrayscaleWithSourceOver; -@property (nonatomic) double saturationDeltaFactor; -@property (nonatomic) double scale; -@property (nonatomic) double zoom;*/ - + (id)effectWithStyle:(long long)arg1; @end -static NSString *encodeText(NSString *string, int key) { - NSMutableString *result = [[NSMutableString alloc] init]; - - for (int i = 0; i < (int)[string length]; i++) { - unichar c = [string characterAtIndex:i]; - c += key; - [result appendString:[NSString stringWithCharacters:&c length:1]]; - } - - return result; -} - static void setField(CustomBlurEffect *object, NSString *name, double value) { SEL selector = NSSelectorFromString(name); NSMethodSignature *signature = [[object class] instanceMethodSignatureForSelector:selector]; @@ -145,7 +117,7 @@ static void setNilField(CustomBlurEffect *object, NSString *name) { [inv invoke]; } -static void setBoolField(CustomBlurEffect *object, NSString *name, BOOL value) { +static void setBoolField(NSObject *object, NSString *name, BOOL value) { SEL selector = NSSelectorFromString(name); NSMethodSignature *signature = [[object class] instanceMethodSignatureForSelector:selector]; if (signature == nil) { @@ -170,18 +142,17 @@ UIBlurEffect *makeCustomZoomBlurEffectImpl(bool isLight) { NSString *string = [@[@"_", @"UI", @"Custom", @"BlurEffect"] componentsJoinedByString:@""]; CustomBlurEffect *result = (CustomBlurEffect *)[NSClassFromString(string) effectWithStyle:0]; - setField(result, encodeText(@"tfuCmvsSbejvt;", -1), 10.0); - //setField(result, encodeText(@"tfu[ppn;", -1), 0.015); - setNilField(result, encodeText(@"tfuDpmpsUjou;", -1)); - setField(result, encodeText(@"tfuDpmpsUjouBmqib;", -1), 0.0); - setField(result, encodeText(@"tfuEbslfojohUjouBmqib;", -1), 0.0); - setField(result, encodeText(@"tfuHsbztdbmfUjouBmqib;", -1), 0.0); - setField(result, encodeText(@"tfuTbuvsbujpoEfmubGbdups;", -1), 1.0); + setField(result, [@[@"set", @"BlurRadius", @":"] componentsJoinedByString:@""], 10.0); + setNilField(result, [@[@"set", @"Color", @"Tint", @":"] componentsJoinedByString:@""]); + setField(result, [@[@"set", @"Color", @"Tint", @"Alpha", @":"] componentsJoinedByString:@""], 0.0); + setField(result, [@[@"set", @"Darkening", @"Tint", @"Alpha", @":"] componentsJoinedByString:@""], 0.0); + setField(result, [@[@"set", @"Grayscale", @"Tint", @"Alpha", @":"] componentsJoinedByString:@""], 0.0); + setField(result, [@[@"set", @"Saturation", @"Delta", @"Factor", @":"] componentsJoinedByString:@""], 1.0); if ([UIScreen mainScreen].scale > 2.5f) { - setField(result, encodeText(@"setScale:", 0), 0.3); + setField(result, @"setScale:", 0.3); } else { - setField(result, encodeText(@"setScale:", 0), 0.5); + setField(result, @"setScale:", 0.5); } return result; @@ -191,7 +162,9 @@ UIBlurEffect *makeCustomZoomBlurEffectImpl(bool isLight) { } void applySmoothRoundedCornersImpl(CALayer * _Nonnull layer) { - if (@available(iOS 11.0, *)) { - setBoolField(layer, encodeText(@"tfuDpoujovpvtDpsofst;", -1), true); + if (@available(iOS 13.0, *)) { + layer.cornerCurve = kCACornerCurveContinuous; + } else { + setBoolField(layer, [@[@"set", @"Continuous", @"Corners", @":"] componentsJoinedByString:@""], true); } } diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.h b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.h index 733fba2371..e34181f313 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.h +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.h @@ -34,7 +34,7 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) { @end -void applyKeyboardAutocorrection(); +void applyKeyboardAutocorrection(UITextView * _Nonnull textView); @interface AboveStatusBarWindow : UIWindow diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m index a330e45585..4000be50b1 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m @@ -316,28 +316,15 @@ static NSString *TGEncodeText(NSString *string, int key) return result; } -void applyKeyboardAutocorrection() { - static Class keyboardClass = NULL; - static SEL currentInstanceSelector = NULL; - static SEL applyVariantSelector = NULL; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - keyboardClass = NSClassFromString(TGEncodeText(@"VJLfzcpbse", -1)); - - currentInstanceSelector = NSSelectorFromString(TGEncodeText(@"bdujwfLfzcpbse", -1)); - applyVariantSelector = NSSelectorFromString(TGEncodeText(@"bddfquBvupdpssfdujpo", -1)); - }); - - if ([keyboardClass respondsToSelector:currentInstanceSelector]) - { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - id currentInstance = [keyboardClass performSelector:currentInstanceSelector]; - if ([currentInstance respondsToSelector:applyVariantSelector]) - [currentInstance performSelector:applyVariantSelector]; -#pragma clang diagnostic pop +void applyKeyboardAutocorrection(UITextView * _Nonnull textView) { + NSRange rangeCopy = textView.selectedRange; + NSRange fakeRange = rangeCopy; + if (fakeRange.location != 0) { + fakeRange.location--; } + [textView unmarkText]; + [textView setSelectedRange:fakeRange]; + [textView setSelectedRange:rangeCopy]; } @interface AboveStatusBarWindowController : UIViewController diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 835c7530f7..bf80c2ecab 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -16,7 +16,7 @@ private let baseTelegraPhPaths = ["telegra.ph/", "te.legra.ph/", "graph.org/", " public enum ParsedInternalPeerUrlParameter { case botStart(String) case groupBotStart(String) - case channelMessage(Int32) + case channelMessage(Int32, Double?) case replyThread(Int32, Int32) case voiceChat(String?) } @@ -24,7 +24,7 @@ public enum ParsedInternalPeerUrlParameter { public enum ParsedInternalUrl { case peerName(String, ParsedInternalPeerUrlParameter?) case peerId(PeerId) - case privateMessage(messageId: MessageId, threadId: Int32?) + case privateMessage(messageId: MessageId, threadId: Int32?, timecode: Double?) case stickerPack(String) case join(String) case localization(String) @@ -281,37 +281,44 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { } else if pathComponents.count == 3 && pathComponents[0] == "c" { if let channelId = Int64(pathComponents[1]), let messageId = Int32(pathComponents[2]) { var threadId: Int32? + var timecode: Double? if let queryItems = components.queryItems { for queryItem in queryItems { if let value = queryItem.value { if queryItem.name == "thread" { if let intValue = Int32(value) { 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._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId) + return .privateMessage(messageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId, timecode: timecode) } else { return nil } } else if let value = Int32(pathComponents[1]) { var threadId: Int32? var commentId: Int32? + var timecode: Double? if let queryItems = components.queryItems { for queryItem in queryItems { if let value = queryItem.value { if queryItem.name == "thread" { if let intValue = Int32(value) { threadId = intValue - break } } else if queryItem.name == "comment" { if let intValue = Int32(value) { 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 { return .peerName(peerName, .replyThread(value, commentId)) } else { - return .peerName(peerName, .channelMessage(value)) + return .peerName(peerName, .channelMessage(value, timecode)) } } else { return nil @@ -357,8 +364,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) return .single(.botStart(peerId: peer.id, payload: payload)) case let .groupBotStart(payload): return .single(.groupBotStart(peerId: peer.id, payload: payload)) - case let .channelMessage(id): - return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id))) + case let .channelMessage(id, timecode): + return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode)) case let .replyThread(id, replyId): let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id) return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) @@ -368,7 +375,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } |> map { result -> ResolvedUrl? in 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)) } @@ -397,7 +404,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) return .single(.inaccessiblePeer) } } - case let .privateMessage(messageId, threadId): + case let .privateMessage(messageId, threadId, timecode): return context.account.postbox.transaction { transaction -> Peer? in return transaction.getPeer(messageId.peerId) } @@ -420,12 +427,12 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } |> map { result -> ResolvedUrl? in 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) } } 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 { return .single(.inaccessiblePeer) diff --git a/submodules/UrlWhitelist/Sources/UrlWhitelist.swift b/submodules/UrlWhitelist/Sources/UrlWhitelist.swift index fac3604964..7f7c8ef7f3 100644 --- a/submodules/UrlWhitelist/Sources/UrlWhitelist.swift +++ b/submodules/UrlWhitelist/Sources/UrlWhitelist.swift @@ -31,7 +31,7 @@ public func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concea latin.insert(charactersIn: "a"..."z") latin.insert(charactersIn: "0"..."9") var punctuation = CharacterSet() - punctuation.insert(charactersIn: ".-/+_") + punctuation.insert(charactersIn: ".-/+_?=") var hasLatin = false var hasNonLatin = false for c in rawHost { diff --git a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift index 80bc1ec2bf..b2b279f61d 100644 --- a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift +++ b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift @@ -284,7 +284,7 @@ class WebSearchControllerNode: ASDisplayNode { } }) - self.recentQueriesNode.beganInteractiveDragging = { [weak self] in + self.recentQueriesNode.beganInteractiveDragging = { [weak self] _ in self?.dismissInput?() }