diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index be59d055fc..7e51e86deb 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -39,6 +39,9 @@ 09619B9521A4ABF600493558 /* InstantPageReferenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */; }; 09619B9621A4ABF600493558 /* InstantPageReferenceControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B9421A4ABF600493558 /* InstantPageReferenceControllerNode.swift */; }; 0962E65D21B1486D00245FD9 /* CallDebugNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E65C21B1486D00245FD9 /* CallDebugNode.swift */; }; + 0962E66121B3512500245FD9 /* WebSearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66021B3512500245FD9 /* WebSearchController.swift */; }; + 0962E66321B3513100245FD9 /* WebSearchControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66221B3513100245FD9 /* WebSearchControllerNode.swift */; }; + 0962E66521B3631100245FD9 /* WebSearchNavigationContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66421B3631100245FD9 /* WebSearchNavigationContentNode.swift */; }; 096C98BA21787A5C00C211FF /* LegacyBridgeAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */; }; 096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; }; 096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; }; @@ -1112,6 +1115,9 @@ 09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageReferenceController.swift; sourceTree = ""; }; 09619B9421A4ABF600493558 /* InstantPageReferenceControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageReferenceControllerNode.swift; sourceTree = ""; }; 0962E65C21B1486D00245FD9 /* CallDebugNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallDebugNode.swift; sourceTree = ""; }; + 0962E66021B3512500245FD9 /* WebSearchController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchController.swift; sourceTree = ""; }; + 0962E66221B3513100245FD9 /* WebSearchControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchControllerNode.swift; sourceTree = ""; }; + 0962E66421B3631100245FD9 /* WebSearchNavigationContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchNavigationContentNode.swift; sourceTree = ""; }; 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyBridgeAudio.swift; sourceTree = ""; }; 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGBridgeAudioEncoder.m; sourceTree = ""; }; 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioEncoder.h; sourceTree = ""; }; @@ -2325,6 +2331,16 @@ name = "Open In"; sourceTree = ""; }; + 0962E65F21B3510800245FD9 /* Web Search */ = { + isa = PBXGroup; + children = ( + 0962E66021B3512500245FD9 /* WebSearchController.swift */, + 0962E66221B3513100245FD9 /* WebSearchControllerNode.swift */, + 0962E66421B3631100245FD9 /* WebSearchNavigationContentNode.swift */, + ); + name = "Web Search"; + sourceTree = ""; + }; 0965C7152178738A007C94D0 /* Bridge Audio */ = { isa = PBXGroup; children = ( @@ -4393,6 +4409,7 @@ D020A9D81FEAE611008C66F7 /* Player */, D01C06AD1FBB45ED001561AB /* Join Link Preview */, D081E102217F57B2003CD921 /* Language Link Preview */, + 0962E65F21B3510800245FD9 /* Web Search */, ); name = Media; sourceTree = ""; @@ -5090,6 +5107,7 @@ D0ACCB1A1EC5E0C20079D8BF /* CallControllerKeyPreviewNode.swift in Sources */, D0E9BA611F055A4300F079A4 /* STPDelegateProxy.m in Sources */, D0EC6CF91EB9F58800EBF1C3 /* MediaManager.swift in Sources */, + 0962E66521B3631100245FD9 /* WebSearchNavigationContentNode.swift in Sources */, 096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */, D01776B81F1D6FB30044446D /* RadialProgressContentNode.swift in Sources */, D05D8B762195CD930064586F /* SetupTwoStepVerificationControllerNode.swift in Sources */, @@ -5146,6 +5164,7 @@ 09C9EA33219F79F600E90146 /* ID3Artwork.m in Sources */, D0FA08CA2049BEAC00DD23FC /* ChatEmptyNode.swift in Sources */, D053DADC201AAAB100993D32 /* ChatTextInputMenu.swift in Sources */, + 0962E66321B3513100245FD9 /* WebSearchControllerNode.swift in Sources */, D0EC6D1A1EB9F58800EBF1C3 /* FFMpegMediaFrameSourceContextHelpers.swift in Sources */, D0EC6D1B1EB9F58800EBF1C3 /* FFMpegMediaVideoFrameDecoder.swift in Sources */, D01C06AF1FBB461E001561AB /* JoinLinkPreviewController.swift in Sources */, @@ -5495,6 +5514,7 @@ D0C0B59F1EE082F5000F4D2C /* ChatSearchInputPanelNode.swift in Sources */, D079FCD91F05A5550038FADE /* BotCheckoutPasswordEntryController.swift in Sources */, D0EC6DC51EB9F58900EBF1C3 /* SoftwareVideoSource.swift in Sources */, + 0962E66121B3512500245FD9 /* WebSearchController.swift in Sources */, D0EC6DC61EB9F58900EBF1C3 /* MultiplexedSoftwareVideoSourceManager.swift in Sources */, D0EC6DC71EB9F58900EBF1C3 /* SampleBufferPool.swift in Sources */, D0EC6DC81EB9F58900EBF1C3 /* MultiplexedVideoNode.swift in Sources */, diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 57f7d3732c..8bd7528e94 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -1888,8 +1888,8 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID } } - self.chatDisplayNode.requestUpdateChatInterfaceState = { [weak self] animated, f in - self?.updateChatPresentationInterfaceState(animated: animated, interactive: true, { $0.updatedInterfaceState(f) }) + self.chatDisplayNode.requestUpdateChatInterfaceState = { [weak self] animated, saveInterfaceState, f in + self?.updateChatPresentationInterfaceState(animated: animated, interactive: true, saveInterfaceState: saveInterfaceState, { $0.updatedInterfaceState(f) }) } self.chatDisplayNode.requestUpdateInterfaceState = { [weak self] transition, interactive, f in @@ -3077,11 +3077,14 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID self.mediaRecordingModeTooltipController?.dismiss() } - private func saveInterfaceState() { + private func saveInterfaceState(includeScrollState: Bool = true) { if case let .peer(peerId) = self.chatLocation { let timestamp = Int32(Date().timeIntervalSince1970) - let scrollState = self.chatDisplayNode.historyNode.immediateScrollState() - let interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp).withUpdatedHistoryScrollState(scrollState) + var interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp) + if includeScrollState { + let scrollState = self.chatDisplayNode.historyNode.immediateScrollState() + interfaceState = interfaceState.withUpdatedHistoryScrollState(scrollState) + } let _ = updatePeerChatInterfaceState(account: account, peerId: peerId, state: interfaceState).start() } } @@ -3121,11 +3124,11 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID }) } - func updateChatPresentationInterfaceState(animated: Bool = true, interactive: Bool, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) { - self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, interactive: interactive, f) + func updateChatPresentationInterfaceState(animated: Bool = true, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) { + self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, interactive: interactive, saveInterfaceState: saveInterfaceState, f) } - func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, interactive: Bool, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) { + func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) { var temporaryChatPresentationInterfaceState = f(self.presentationInterfaceState) if self.presentationInterfaceState.keyboardButtonsMessage?.visibleButtonKeyboardMarkup != temporaryChatPresentationInterfaceState.keyboardButtonsMessage?.visibleButtonKeyboardMarkup { @@ -3396,6 +3399,10 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID case .inline: self.statusBar.statusBarStyle = .Ignore } + + if saveInterfaceState { + self.saveInterfaceState(includeScrollState: false) + } } private func updateItemNodesSelectionStates(animated: Bool) { @@ -5495,9 +5502,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID var inputShortcuts: [KeyShortcut] if self.chatDisplayNode.isInputViewFocused { - inputShortcuts = [KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", action: { - - })] + inputShortcuts = [KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", action: {})] } else { inputShortcuts = [KeyShortcut(title: strings.KeyCommand_FocusOnInputField, input: "\r", action: { [weak self] in if let strongSelf = self { diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index 55ac2739d0..2ff557ef72 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -121,7 +121,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - var requestUpdateChatInterfaceState: (Bool, (ChatInterfaceState) -> ChatInterfaceState) -> Void = { _, _ in } + var requestUpdateChatInterfaceState: (Bool, Bool, (ChatInterfaceState) -> ChatInterfaceState) -> Void = { _, _, _ in } var requestUpdateInterfaceState: (ContainedViewLayoutTransition, Bool, (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void = { _, _, _ in } var sendMessages: ([EnqueueMessage]) -> Void = { _ in } var displayAttachmentMenu: () -> Void = { } @@ -300,12 +300,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let strongSelf = strongSelf, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode { strongSelf.ignoreUpdateHeight = true textInputPanelNode.text = "" - strongSelf.requestUpdateChatInterfaceState(false, { $0.withUpdatedReplyMessageId(nil).withUpdatedForwardMessageIds(nil).withUpdatedComposeDisableUrlPreview(nil) }) + strongSelf.requestUpdateChatInterfaceState(false, true, { $0.withUpdatedReplyMessageId(nil).withUpdatedForwardMessageIds(nil).withUpdatedComposeDisableUrlPreview(nil) }) strongSelf.ignoreUpdateHeight = false } }) - if let forwardMessageIds = strongSelf.chatPresentationInterfaceState.interfaceState.forwardMessageIds { for id in forwardMessageIds { messages.append(.forward(source: id, grouping: .auto)) @@ -742,9 +741,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { accessoryPanelNode.dismiss = { [weak self, weak accessoryPanelNode] in if let strongSelf = self, let accessoryPanelNode = accessoryPanelNode, strongSelf.accessoryPanelNode === accessoryPanelNode { if let _ = accessoryPanelNode as? ReplyAccessoryPanelNode { - strongSelf.requestUpdateChatInterfaceState(true, { $0.withUpdatedReplyMessageId(nil) }) + strongSelf.requestUpdateChatInterfaceState(true, false, { $0.withUpdatedReplyMessageId(nil) }) } else if let _ = accessoryPanelNode as? ForwardAccessoryPanelNode { - strongSelf.requestUpdateChatInterfaceState(true, { $0.withUpdatedForwardMessageIds(nil) }) + strongSelf.requestUpdateChatInterfaceState(true, false, { $0.withUpdatedForwardMessageIds(nil) }) } else if let _ = accessoryPanelNode as? EditAccessoryPanelNode { strongSelf.interfaceInteraction?.setupEditMessage(nil) } else if let _ = accessoryPanelNode as? WebpagePreviewAccessoryPanelNode { diff --git a/TelegramUI/ChatHistoryGridNode.swift b/TelegramUI/ChatHistoryGridNode.swift index 366854f176..36a48ef6e0 100644 --- a/TelegramUI/ChatHistoryGridNode.swift +++ b/TelegramUI/ChatHistoryGridNode.swift @@ -195,9 +195,11 @@ private func mappedChatHistoryViewListTransition(account: Account, peerId: PeerI return ChatHistoryGridViewTransition(historyView: transition.historyView, topOffsetWithinMonth: topOffsetWithinMonth, deleteItems: transition.deleteItems.map { $0.index }, insertItems: mappedInsertEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.insertEntries, theme: presentationData.theme.theme, strings: presentationData.strings), updateItems: mappedUpdateEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.updateEntries, theme: presentationData.theme.theme, strings: presentationData.strings), scrollToItem: mappedScrollToItem, stationaryItems: stationaryItems) } -private func itemSizeForContainerLayout(size: CGSize) -> CGSize { - let side = floor(size.width / 4.0) - return CGSize(width: side, height: side) +private func gridNodeLayoutForContainerLayout(size: CGSize) -> GridNodeLayoutType { + let side = floorToScreenPixels((size.width - 3.0) / 4.0) + //let side = floor(size.width / 4.0) + //return CGSize(width: side, height: side) + return .fixed(itemSize: CGSize(width: side, height: side), fillWidth: true, lineSpacing: 1.0, itemSpacing: 1.0) } public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { @@ -493,7 +495,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { var updateLayout: GridNodeUpdateLayout? if let updateSizeAndInsets = updateSizeAndInsets { - updateLayout = GridNodeUpdateLayout(layout: GridNodeLayout(size: updateSizeAndInsets.size, insets: updateSizeAndInsets.insets, preloadSize: 400.0, type: .fixed(itemSize: CGSize(width: 200.0, height: 200.0), lineSpacing: 0.0)), transition: .immediate) + updateLayout = GridNodeUpdateLayout(layout: GridNodeLayout(size: updateSizeAndInsets.size, insets: updateSizeAndInsets.insets, preloadSize: 400.0, type: .fixed(itemSize: CGSize(width: 200.0, height: 200.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: .immediate) } self.transaction(GridNodeTransaction(deleteItems: mappedTransition.deleteItems, insertItems: mappedTransition.insertItems, updateItems: mappedTransition.updateItems, scrollToItem: mappedTransition.scrollToItem, updateLayout: updateLayout, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: mappedTransition.topOffsetWithinMonth), completion: completion) @@ -504,7 +506,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { } public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { - self.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: updateSizeAndInsets.size, insets: updateSizeAndInsets.insets, preloadSize: 400.0, type: .fixed(itemSize: itemSizeForContainerLayout(size: updateSizeAndInsets.size), lineSpacing: 0.0)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none,updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: updateSizeAndInsets.size, insets: updateSizeAndInsets.insets, preloadSize: 400.0, type: gridNodeLayoutForContainerLayout(size: updateSizeAndInsets.size)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none,updateFirstIndexInSectionOffset: nil), completion: { _ in }) if !self.dequeuedInitialTransitionOnLayout { self.dequeuedInitialTransitionOnLayout = true diff --git a/TelegramUI/ChatInterfaceStateContextMenus.swift b/TelegramUI/ChatInterfaceStateContextMenus.swift index 618d77f44a..42f63807cc 100644 --- a/TelegramUI/ChatInterfaceStateContextMenus.swift +++ b/TelegramUI/ChatInterfaceStateContextMenus.swift @@ -287,7 +287,6 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: var hasUneditableAttributes = false - if let peer = message.peers[message.id.peerId] as? TelegramChannel { if peer.hasBannedRights(.banSendMessages) { hasUneditableAttributes = true @@ -324,7 +323,6 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } if !hasUneditableAttributes { - let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) if canPerformEditingActions(limits: limitsConfiguration, accountPeerId: account.peerId, message: message) { canEdit = true } @@ -510,7 +508,6 @@ private func canPerformEditingActions(limits: LimitsConfiguration, accountPeerId } let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - if message.timestamp + limits.maxMessageEditingInterval > timestamp { return true } else { diff --git a/TelegramUI/ChatMediaInputStickerPane.swift b/TelegramUI/ChatMediaInputStickerPane.swift index 309bfb75a8..20f32f952c 100644 --- a/TelegramUI/ChatMediaInputStickerPane.swift +++ b/TelegramUI/ChatMediaInputStickerPane.swift @@ -98,7 +98,7 @@ final class ChatMediaInputStickerPane: ChatMediaInputPane { } } } - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), preloadSize: 300.0, type: .fixed(itemSize: itemSize, lineSpacing: 0.0)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), preloadSize: 300.0, type: .fixed(itemSize: itemSize, fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) if false, let scrollToItem = scrollToItem { self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index 91feaaf21c..e9ae3cc2a5 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -668,7 +668,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } } } - } else if let webpage = webpage, let automaticDownload = self.automaticDownload, automaticDownload, case let .Loaded(content) = webpage.content { + } else if let webpage = webpage, let automaticDownload = self.automaticDownload, automaticDownload, case .Loaded = webpage.content { state = .play(bubbleTheme.mediaOverlayControlForegroundColor) } case .Local: @@ -698,28 +698,23 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { state = .play(bubbleTheme.mediaOverlayControlForegroundColor) } } - if case .constrained = sizeCalculation { - if let file = media as? TelegramMediaFile, let duration = file.duration, !file.isAnimated { - let durationString = stringForDuration(duration) - badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) - } + if let file = media as? TelegramMediaFile, let duration = file.duration, !file.isAnimated { + let durationString = stringForDuration(duration) + badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) } case .Remote: state = .download(bubbleTheme.mediaOverlayControlForegroundColor) if let file = self.media as? TelegramMediaFile, let duration = file.duration, !file.isAnimated { + let durationString = stringForDuration(duration) if case .constrained = sizeCalculation { if isMediaStreamable(message: message, media: file) { state = .play(bubbleTheme.mediaOverlayControlForegroundColor) - - let durationString = stringForDuration(duration) badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: dataSizeString(file.size ?? 0)) mediaDownloadState = .remote } else { - let durationString = stringForDuration(duration) badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) } } else { - let durationString = stringForDuration(duration) if isMediaStreamable(message: message, media: file) { state = .play(bubbleTheme.mediaOverlayControlForegroundColor) badgeContent = .text(inset: 16.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) @@ -728,7 +723,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) } } - } else if let webpage = webpage, let automaticDownload = self.automaticDownload, automaticDownload, case let .Loaded(content) = webpage.content { + } else if let webpage = webpage, let automaticDownload = self.automaticDownload, automaticDownload, case .Loaded = webpage.content { state = .play(bubbleTheme.mediaOverlayControlForegroundColor) } } diff --git a/TelegramUI/ChatMessageNotificationItem.swift b/TelegramUI/ChatMessageNotificationItem.swift index 9900f2ce90..149c197c12 100644 --- a/TelegramUI/ChatMessageNotificationItem.swift +++ b/TelegramUI/ChatMessageNotificationItem.swift @@ -24,9 +24,9 @@ public final class ChatMessageNotificationItem: NotificationItem { self.expandAction = expandAction } - public func node() -> NotificationItemNode { + public func node(compact: Bool) -> NotificationItemNode { let node = ChatMessageNotificationItemNode() - node.setupItem(self) + node.setupItem(self, compact: compact) return node } @@ -45,6 +45,7 @@ public final class ChatMessageNotificationItem: NotificationItem { } } +private let compactAvatarFont: UIFont = UIFont(name: ".SFCompactRounded-Semibold", size: 20.0)! private let avatarFont: UIFont = UIFont(name: ".SFCompactRounded-Semibold", size: 24.0)! final class ChatMessageNotificationItemNode: NotificationItemNode { @@ -59,6 +60,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { private var titleAttributedText: NSAttributedString? private var textAttributedText: NSAttributedString? + private var compact: Bool? private var validLayout: CGFloat? override init() { @@ -86,8 +88,12 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { self.addSubnode(self.imageNode) } - func setupItem(_ item: ChatMessageNotificationItem) { + func setupItem(_ item: ChatMessageNotificationItem, compact: Bool) { self.item = item + self.compact = compact + if compact { + self.avatarNode.font = compactAvatarFont + } let presentationData = item.account.telegramApplicationContext.currentPresentationData.with { $0 } var title: String? @@ -204,7 +210,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { messageText = "" } - self.titleAttributedText = NSAttributedString(string: title ?? "", font: Font.semibold(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor) + self.titleAttributedText = NSAttributedString(string: title ?? "", font: compact ? Font.semibold(15.0) : Font.semibold(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor) let imageNodeLayout = self.imageNode.asyncLayout() var applyImage: (() -> Void)? @@ -241,25 +247,28 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { self.imageNode.setSignal(updateImageSignal) } - self.textAttributedText = NSAttributedString(string: messageText, font: Font.regular(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor) + self.textAttributedText = NSAttributedString(string: messageText, font: compact ? Font.regular(15.0) : Font.regular(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor) - if let validLayout = self.validLayout { - let _ = self.updateLayout(width: validLayout, transition: .immediate) + if let width = self.validLayout { + let _ = self.updateLayout(width: width, transition: .immediate) } } override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { self.validLayout = width + let compact = self.compact ?? false - let panelHeight: CGFloat = 74.0 - let leftInset: CGFloat = 77.0 + let panelHeight: CGFloat = compact ? 64.0 : 74.0 + let imageSize: CGSize = compact ? CGSize(width: 44.0, height: 44.0) : CGSize(width: 54.0, height: 54.0) + let imageSpacing: CGFloat = compact ? 19.0 : 23.0 + let leftInset: CGFloat = imageSize.width + imageSpacing var rightInset: CGFloat = 8.0 if !self.imageNode.isHidden { - rightInset += 55.0 + 8.0 + rightInset += imageSize.width + 8.0 } - transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: CGSize(width: 54.0, height: 54.0))) + transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: 10.0, y: (panelHeight - imageSize.height) / 2.0), size: imageSize)) var titleInset: CGFloat = 0.0 if let image = self.titleIconNode.image { @@ -286,8 +295,8 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + textSpacing), size: textLayout.size)) - transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: width - 9.0 - 55.0, y: 9.0), size: CGSize(width: 55.0, height: 55.0))) + transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: width - 10.0 - imageSize.width, y: (panelHeight - imageSize.height) / 2.0), size: imageSize)) - return 74.0 + return panelHeight } } diff --git a/TelegramUI/CheckDeviceAccess.swift b/TelegramUI/CheckDeviceAccess.swift index 070fc6ba16..610b234bf8 100644 --- a/TelegramUI/CheckDeviceAccess.swift +++ b/TelegramUI/CheckDeviceAccess.swift @@ -158,7 +158,7 @@ public final class DeviceAccess { } } - public static func authorizeAccess(to subject: DeviceAccessSubject, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void, openSettings: @escaping () -> Void, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void) { + public static func authorizeAccess(to subject: DeviceAccessSubject, presentationData: PresentationData? = nil, present: @escaping (ViewController, Any?) -> Void = { _, _ in }, openSettings: @escaping () -> Void = { }, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void = { _ in }) { switch subject { case .camera: let status = PGCamera.cameraAuthorizationStatus() @@ -166,7 +166,7 @@ public final class DeviceAccess { AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in Queue.mainQueue().async { completion(response) - if !response { + if !response, let presentationData = presentationData { let text = presentationData.strings.AccessDenied_Camera present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() @@ -174,7 +174,7 @@ public final class DeviceAccess { } } } - } else if status == PGCameraAuthorizationStatusRestricted || status == PGCameraAuthorizationStatusDenied { + } else if status == PGCameraAuthorizationStatusRestricted || status == PGCameraAuthorizationStatusDenied, let presentationData = presentationData { let text: String if status == PGCameraAuthorizationStatusRestricted { text = presentationData.strings.AccessDenied_CameraRestricted @@ -198,7 +198,7 @@ public final class DeviceAccess { AVAudioSession.sharedInstance().requestRecordPermission({ granted in if granted { completion(true) - } else { + } else if let presentationData = presentationData { completion(false) let text: String switch microphoneSubject { @@ -223,7 +223,7 @@ public final class DeviceAccess { Queue.mainQueue().async { if value { completion(true) - } else { + } else if let presentationData = presentationData { completion(false) let text: String switch mediaLibrarySubject { @@ -264,27 +264,31 @@ public final class DeviceAccess { completion(true) case .live: completion(false) - let text = presentationData.strings.AccessDenied_LocationAlwaysDenied - present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { - openSettings() - })]), nil) + if let presentationData = presentationData { + let text = presentationData.strings.AccessDenied_LocationAlwaysDenied + present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + openSettings() + })]), nil) + } } case .denied, .restricted: completion(false) - let text: String - if status == .denied { - switch locationSubject { - case .send, .live: - text = presentationData.strings.AccessDenied_LocationDenied - case .tracking: - text = presentationData.strings.AccessDenied_LocationTracking + if let presentationData = presentationData { + let text: String + if status == .denied { + switch locationSubject { + case .send, .live: + text = presentationData.strings.AccessDenied_LocationDenied + case .tracking: + text = presentationData.strings.AccessDenied_LocationTracking + } + } else { + text = presentationData.strings.AccessDenied_LocationDisabled } - } else { - text = presentationData.strings.AccessDenied_LocationDisabled + present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + openSettings() + })]), nil) } - present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { - openSettings() - })]), nil) case .notDetermined: completion(true) } diff --git a/TelegramUI/ContactListNode.swift b/TelegramUI/ContactListNode.swift index f841a4bb9a..81a360cc25 100644 --- a/TelegramUI/ContactListNode.swift +++ b/TelegramUI/ContactListNode.swift @@ -923,7 +923,7 @@ final class ContactListNode: ASDisplayNode { |> deliverOnMainQueue).start(next: { status in switch status { case .notDetermined: - DeviceAccess.authorizeAccess(to: .contacts, presentationData: strongSelf.presentationData, present: { _, _ in }, openSettings: {}, { _ in }) + DeviceAccess.authorizeAccess(to: .contacts) case .denied, .restricted: account.telegramApplicationContext.applicationBindings.openSettings() default: diff --git a/TelegramUI/GridMessageItem.swift b/TelegramUI/GridMessageItem.swift index 3a4525350e..f78e000c5d 100644 --- a/TelegramUI/GridMessageItem.swift +++ b/TelegramUI/GridMessageItem.swift @@ -280,7 +280,7 @@ final class GridMessageItemNode: GridItemNode { self.imageNode.addSubnode(self.statusNode) } } else { - videoAccessoryNode.isHidden = true + self.videoAccessoryNode.isHidden = true } if let mediaDimensions = mediaDimensions { @@ -300,7 +300,7 @@ final class GridMessageItemNode: GridItemNode { override func layout() { super.layout() - let imageFrame = self.bounds.insetBy(dx: 1.0, dy: 1.0) + let imageFrame = self.bounds self.imageNode.frame = imageFrame if let item = self.item, let (_, _, mediaDimensions) = self.currentState { @@ -312,7 +312,7 @@ final class GridMessageItemNode: GridItemNode { let progressDiameter: CGFloat = 40.0 self.statusNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - progressDiameter) / 2.0), y: floor((imageFrame.size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter)) - videoAccessoryNode.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - videoAccessoryNode.contentSize.width - 5, y: imageFrame.maxY - videoAccessoryNode.contentSize.height - 5), size: videoAccessoryNode.contentSize) + self.videoAccessoryNode.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - self.videoAccessoryNode.contentSize.width - 5, y: imageFrame.maxY - self.videoAccessoryNode.contentSize.height - 5), size: self.videoAccessoryNode.contentSize) } func updateSelectionState(animated: Bool) { diff --git a/TelegramUI/HorizontalStickersChatContextPanelNode.swift b/TelegramUI/HorizontalStickersChatContextPanelNode.swift index cd51df6c4b..61bb097854 100644 --- a/TelegramUI/HorizontalStickersChatContextPanelNode.swift +++ b/TelegramUI/HorizontalStickersChatContextPanelNode.swift @@ -284,7 +284,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { self.gridNode.bounds = CGRect(x: gridBounds.minX, y: gridBounds.minY, width: gridFrame.size.height, height: gridFrame.size.width) self.gridNode.position = CGPoint(x: gridFrame.size.width / 2.0, y: gridFrame.size.height / 2.0) - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: CGSize(width: gridFrame.size.height, height: gridFrame.size.width), insets: UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0), preloadSize: 100.0, type: .fixed(itemSize: CGSize(width: 66.0, height: 66.0), lineSpacing: 0.0)), transition: .immediate), itemTransition: .immediate, stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: CGSize(width: gridFrame.size.height, height: gridFrame.size.width), insets: UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0), preloadSize: 100.0, type: .fixed(itemSize: CGSize(width: 66.0, height: 66.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: .immediate), itemTransition: .immediate, stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in }) let dequeue = self.validLayout == nil self.validLayout = (size, leftInset, rightInset, interfaceState) diff --git a/TelegramUI/InstantPageAnchorItem.swift b/TelegramUI/InstantPageAnchorItem.swift index 3549ec0fa2..2b6e2fd9f3 100644 --- a/TelegramUI/InstantPageAnchorItem.swift +++ b/TelegramUI/InstantPageAnchorItem.swift @@ -23,7 +23,7 @@ final class InstantPageAnchorItem: InstantPageItem { func drawInTile(context: CGContext) { } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { return nil } diff --git a/TelegramUI/InstantPageArticleItem.swift b/TelegramUI/InstantPageArticleItem.swift index cf0f8f1940..2bf8d0ef0a 100644 --- a/TelegramUI/InstantPageArticleItem.swift +++ b/TelegramUI/InstantPageArticleItem.swift @@ -28,7 +28,7 @@ final class InstantPageArticleItem: InstantPageItem { self.rtl = rtl } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { return InstantPageArticleNode(account: account, item: self, webPage: self.webPage, strings: strings, theme: theme, contentItems: self.contentItems, contentSize: self.contentSize, cover: self.cover, url: self.url, webpageId: self.webpageId, rtl: self.rtl, openUrl: openUrl) } diff --git a/TelegramUI/InstantPageAudioItem.swift b/TelegramUI/InstantPageAudioItem.swift index 919ae848ad..6d822fa3f0 100644 --- a/TelegramUI/InstantPageAudioItem.swift +++ b/TelegramUI/InstantPageAudioItem.swift @@ -19,7 +19,7 @@ final class InstantPageAudioItem: InstantPageItem { self.medias = [media] } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { return InstantPageAudioNode(account: account, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia) } diff --git a/TelegramUI/InstantPageContentNode.swift b/TelegramUI/InstantPageContentNode.swift index 8a0add9176..2ab8ce4472 100644 --- a/TelegramUI/InstantPageContentNode.swift +++ b/TelegramUI/InstantPageContentNode.swift @@ -11,6 +11,7 @@ final class InstantPageContentNode : ASDisplayNode { private let theme: InstantPageTheme private let openMedia: (InstantPageMedia) -> Void + private let longPressMedia: (InstantPageMedia) -> Void private let openPeer: (PeerId) -> Void private let openUrl: (InstantPageUrlItem) -> Void @@ -33,12 +34,13 @@ final class InstantPageContentNode : ASDisplayNode { private var previousVisibleBounds: CGRect? - init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) { + init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) { self.account = account self.strings = strings self.theme = theme self.openMedia = openMedia + self.longPressMedia = longPressMedia self.openPeer = openPeer self.openUrl = openUrl @@ -180,6 +182,8 @@ final class InstantPageContentNode : ASDisplayNode { let detailsIndex = detailsIndex if let newNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in self?.openMedia(media) + }, longPressMedia: { [weak self] media in + self?.longPressMedia(media) }, openPeer: { [weak self] peerId in self?.openPeer(peerId) }, openUrl: { [weak self] url in diff --git a/TelegramUI/InstantPageControllerNode.swift b/TelegramUI/InstantPageControllerNode.swift index fb242ac032..26fd64548d 100644 --- a/TelegramUI/InstantPageControllerNode.swift +++ b/TelegramUI/InstantPageControllerNode.swift @@ -501,6 +501,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { let detailsIndex = detailsIndex if let newNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in self?.openMedia(media) + }, longPressMedia: { [weak self] media in + self?.longPressMedia(media) }, openPeer: { [weak self] peerId in self?.openPeer(peerId) }, openUrl: { [weak self] url in @@ -860,6 +862,34 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { return nil } + private func longPressMedia(_ media: InstantPageMedia) { + let controller = ContextMenuController(actions: [ContextMenuAction(content: .text(self.strings.Conversation_ContextMenuCopy), action: { [weak self] in + if let strongSelf = self, let image = media.media as? TelegramMediaImage { + let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, reference: nil, partialReference: nil) + let _ = copyToPasteboard(applicationContext: strongSelf.account.telegramApplicationContext, postbox: strongSelf.account.postbox, mediaReference: .standalone(media: media)).start() + } + }), ContextMenuAction(content: .text(self.strings.Conversation_LinkDialogSave), action: { [weak self] in + if let strongSelf = self, let image = media.media as? TelegramMediaImage { + let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, reference: nil, partialReference: nil) + let _ = saveToCameraRoll(applicationContext: strongSelf.account.telegramApplicationContext, postbox: strongSelf.account.postbox, mediaReference: .standalone(media: media)).start() + } + }), ContextMenuAction(content: .text(self.strings.Conversation_ContextMenuShare), action: { [weak self] in + if let strongSelf = self, let image = media.media as? TelegramMediaImage { + strongSelf.present(ShareController(account: strongSelf.account, subject: .image(image.representations)), nil) + } + })], catchTapsOutside: true) + self.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in + if let strongSelf = self { + for (_, itemNode) in strongSelf.visibleItemsWithNodes { + if let (node, _) = itemNode.transitionNode(media: media) { + return (strongSelf.scrollNode, node.convert(node.bounds, to: strongSelf.scrollNode), strongSelf, strongSelf.bounds) + } + } + } + return nil + })) + } + @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: diff --git a/TelegramUI/InstantPageDetailsItem.swift b/TelegramUI/InstantPageDetailsItem.swift index c0a97bd9a3..6d5ea686b8 100644 --- a/TelegramUI/InstantPageDetailsItem.swift +++ b/TelegramUI/InstantPageDetailsItem.swift @@ -29,12 +29,12 @@ final class InstantPageDetailsItem: InstantPageItem { self.index = index } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { var expanded: Bool? if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] { expanded = currentlyExpanded } - return InstantPageDetailsNode(account: account, strings: strings, theme: theme, item: self, openMedia: openMedia, openPeer: openPeer, openUrl: openUrl, currentlyExpanded: expanded, updateDetailsExpanded: updateDetailsExpanded) + return InstantPageDetailsNode(account: account, strings: strings, theme: theme, item: self, openMedia: openMedia, longPressMedia: longPressMedia, openPeer: openPeer, openUrl: openUrl, currentlyExpanded: expanded, updateDetailsExpanded: updateDetailsExpanded) } func matchesAnchor(_ anchor: String) -> Bool { diff --git a/TelegramUI/InstantPageDetailsNode.swift b/TelegramUI/InstantPageDetailsNode.swift index 992c0973dd..c82af30566 100644 --- a/TelegramUI/InstantPageDetailsNode.swift +++ b/TelegramUI/InstantPageDetailsNode.swift @@ -30,7 +30,7 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode { var requestLayoutUpdate: ((Bool) -> Void)? - init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) { + init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) { self.account = account self.strings = strings self.theme = theme @@ -59,7 +59,7 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode { self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: self.expanded) self.separatorNode = ASDisplayNode() - self.contentNode = InstantPageContentNode(account: account, strings: strings, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight), openMedia: openMedia, openPeer: openPeer, openUrl: openUrl) + self.contentNode = InstantPageContentNode(account: account, strings: strings, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight), openMedia: openMedia, longPressMedia: longPressMedia, openPeer: openPeer, openUrl: openUrl) super.init() diff --git a/TelegramUI/InstantPageFeedbackItem.swift b/TelegramUI/InstantPageFeedbackItem.swift index fbbb85e299..ddcd79869b 100644 --- a/TelegramUI/InstantPageFeedbackItem.swift +++ b/TelegramUI/InstantPageFeedbackItem.swift @@ -16,7 +16,7 @@ final class InstantPageFeedbackItem: InstantPageItem { self.webPage = webPage } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { return InstantPageFeedbackNode(account: account, strings: strings, theme: theme, webPage: self.webPage, openUrl: openUrl) } diff --git a/TelegramUI/InstantPageGalleryFooterContentNode.swift b/TelegramUI/InstantPageGalleryFooterContentNode.swift index b044648326..b904ac9cf0 100644 --- a/TelegramUI/InstantPageGalleryFooterContentNode.swift +++ b/TelegramUI/InstantPageGalleryFooterContentNode.swift @@ -105,9 +105,15 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode { let sideInset: CGFloat = leftInset + 8.0 let topInset: CGFloat = 0.0 let bottomInset: CGFloat = 0.0 - let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)) + var textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)) + + var x = sideInset + if let hasRTL = self.textNode.cachedLayout?.hasRTL, hasRTL { + x = width - rightInset - 8.0 - textSize.width + } + panelHeight += textSize.height + topInset + bottomInset - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: sideInset, y: topInset), size: textSize)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: x, y: topInset), size: textSize)) } self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0)) diff --git a/TelegramUI/InstantPageImageItem.swift b/TelegramUI/InstantPageImageItem.swift index a69e28c1e1..19a21cc428 100644 --- a/TelegramUI/InstantPageImageItem.swift +++ b/TelegramUI/InstantPageImageItem.swift @@ -40,8 +40,8 @@ final class InstantPageImageItem: InstantPageItem { self.fit = fit } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { - return InstantPageImageNode(account: account, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia) + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + return InstantPageImageNode(account: account, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, longPressMedia: longPressMedia) } func matchesAnchor(_ anchor: String) -> Bool { diff --git a/TelegramUI/InstantPageImageNode.swift b/TelegramUI/InstantPageImageNode.swift index 17022638f5..628dbcce69 100644 --- a/TelegramUI/InstantPageImageNode.swift +++ b/TelegramUI/InstantPageImageNode.swift @@ -15,6 +15,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private let roundCorners: Bool private let fit: Bool private let openMedia: (InstantPageMedia) -> Void + private let longPressMedia: (InstantPageMedia) -> Void private let imageNode: TransformImageNode private let statusNode: RadialStatusNode @@ -29,7 +30,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private var themeUpdated: Bool = false - init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void) { + init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void) { self.account = account self.theme = theme self.webPage = webPage @@ -39,6 +40,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.roundCorners = roundCorners self.fit = fit self.openMedia = openMedia + self.longPressMedia = longPressMedia self.imageNode = TransformImageNode() self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6)) @@ -54,21 +56,23 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.imageNode.setSignal(chatMessagePhoto(postbox: account.postbox, photoReference: imageReference)) self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: imageReference, storeToDownloadsPeerType: nil).start()) - self.statusDisposable.set((account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in - displayLinkDispatcher.dispatch { - if let strongSelf = self { - strongSelf.fetchStatus = status - strongSelf.updateFetchStatus() + if interactive { + self.statusDisposable.set((account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in + displayLinkDispatcher.dispatch { + if let strongSelf = self { + strongSelf.fetchStatus = status + strongSelf.updateFetchStatus() + } } + })) + + if media.url != nil { + self.linkIconNode.image = UIImage(bundleImageName: "Instant View/ImageLink") + self.addSubnode(self.linkIconNode) } - })) - - if media.url != nil { - self.linkIconNode.image = UIImage(bundleImageName: "Instant View/ImageLink") - self.addSubnode(self.linkIconNode) + + self.addSubnode(self.statusNode) } - - self.addSubnode(self.statusNode) } else if let file = media.media as? TelegramMediaFile { let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file) if file.mimeType.hasPrefix("image/") { @@ -109,7 +113,9 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { super.didLoad() if self.interactive { - self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + recognizer.delaysTouchesBegan = false + self.view.addGestureRecognizer(recognizer) } else { self.view.isUserInteractionEnabled = false } @@ -226,9 +232,24 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.statusNode.isHidden = self.imageNode.isHidden } - @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.openMedia(self.media) + @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { + switch gesture { + case .tap: + if self.media.media is TelegramMediaImage && self.media.index == -1 { + return + } + self.openMedia(self.media) + case .longTap: + self.longPressMedia(self.media) + default: + break + } + } + default: + break } } } diff --git a/TelegramUI/InstantPageItem.swift b/TelegramUI/InstantPageItem.swift index ec22d3e3e1..feb88bad24 100644 --- a/TelegramUI/InstantPageItem.swift +++ b/TelegramUI/InstantPageItem.swift @@ -11,7 +11,7 @@ protocol InstantPageItem { func matchesAnchor(_ anchor: String) -> Bool func drawInTile(context: CGContext) - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? func matchesNode(_ node: InstantPageNode) -> Bool func linkSelectionRects(at point: CGPoint) -> [CGRect] diff --git a/TelegramUI/InstantPagePeerReferenceItem.swift b/TelegramUI/InstantPagePeerReferenceItem.swift index 1e86077ef8..7cb3f48b9f 100644 --- a/TelegramUI/InstantPagePeerReferenceItem.swift +++ b/TelegramUI/InstantPagePeerReferenceItem.swift @@ -22,7 +22,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem { self.rtl = rtl } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { return InstantPagePeerReferenceNode(account: account, strings: strings, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, transparent: self.transparent, rtl: self.rtl, openPeer: openPeer) } diff --git a/TelegramUI/InstantPagePlayableVideoItem.swift b/TelegramUI/InstantPagePlayableVideoItem.swift index 6c91434fbf..863feba41a 100644 --- a/TelegramUI/InstantPagePlayableVideoItem.swift +++ b/TelegramUI/InstantPagePlayableVideoItem.swift @@ -24,7 +24,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem { self.interactive = interactive } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { return InstantPagePlayableVideoNode(account: account, webPage: self.webPage, theme: theme, media: self.media, interactive: self.interactive, openMedia: openMedia) } diff --git a/TelegramUI/InstantPageReferenceControllerNode.swift b/TelegramUI/InstantPageReferenceControllerNode.swift index 86d7d51ccd..2e4187611f 100644 --- a/TelegramUI/InstantPageReferenceControllerNode.swift +++ b/TelegramUI/InstantPageReferenceControllerNode.swift @@ -190,7 +190,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie let sideInset: CGFloat = 16.0 let (_, items, contentSize) = layoutTextItemWithString(self.item.attributedString, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset)) - let contentNode = InstantPageContentNode(account: self.account, strings: self.presentationData.strings, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in }) + let contentNode = InstantPageContentNode(account: self.account, strings: self.presentationData.strings, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, longPressMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in }) transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: width, height: contentSize.height))) self.contentContainerNode.insertSubnode(contentNode, at: 0) self.contentNode = contentNode diff --git a/TelegramUI/InstantPageShapeItem.swift b/TelegramUI/InstantPageShapeItem.swift index 40a9fc720a..de91ce4a69 100644 --- a/TelegramUI/InstantPageShapeItem.swift +++ b/TelegramUI/InstantPageShapeItem.swift @@ -57,7 +57,7 @@ final class InstantPageShapeItem: InstantPageItem { return false } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { return nil } diff --git a/TelegramUI/InstantPageSlideshowItem.swift b/TelegramUI/InstantPageSlideshowItem.swift index 16f97cb50e..c619270a1e 100644 --- a/TelegramUI/InstantPageSlideshowItem.swift +++ b/TelegramUI/InstantPageSlideshowItem.swift @@ -16,8 +16,8 @@ final class InstantPageSlideshowItem: InstantPageItem { self.medias = medias } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { - return InstantPageSlideshowNode(account: account, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia) + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + return InstantPageSlideshowNode(account: account, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia, longPressMedia: longPressMedia) } func matchesAnchor(_ anchor: String) -> Bool { diff --git a/TelegramUI/InstantPageSlideshowItemNode.swift b/TelegramUI/InstantPageSlideshowItemNode.swift index 0cc44a2e8e..2b17bfef1e 100644 --- a/TelegramUI/InstantPageSlideshowItemNode.swift +++ b/TelegramUI/InstantPageSlideshowItemNode.swift @@ -63,6 +63,7 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, UIScrollViewDe private let theme: InstantPageTheme private let webPage: TelegramMediaWebpage private let openMedia: (InstantPageMedia) -> Void + private let longPressMedia: (InstantPageMedia) -> Void private let pageGap: CGFloat private let scrollView: UIScrollView @@ -92,11 +93,12 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, UIScrollViewDe } } - init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, openMedia: @escaping (InstantPageMedia) -> Void, pageGap: CGFloat = 0.0) { + init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, pageGap: CGFloat = 0.0) { self.account = account self.theme = theme self.webPage = webPage self.openMedia = openMedia + self.longPressMedia = longPressMedia self.pageGap = pageGap self.scrollView = UIScrollView() if #available(iOSApplicationExtension 11.0, *) { @@ -174,7 +176,7 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, UIScrollViewDe let media = self.items[index] let contentNode: ASDisplayNode if let _ = media.media as? TelegramMediaImage { - contentNode = InstantPageImageNode(account: self.account, theme: self.theme, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia) + contentNode = InstantPageImageNode(account: self.account, theme: self.theme, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia, longPressMedia: self.longPressMedia) } else if let file = media.media as? TelegramMediaFile { contentNode = ASDisplayNode() } else { @@ -373,10 +375,10 @@ final class InstantPageSlideshowNode: ASDisplayNode, InstantPageNode { private let pagerNode: InstantPageSlideshowPagerNode private let pageControlNode: PageControlNode - init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, medias: [InstantPageMedia], openMedia: @escaping (InstantPageMedia) -> Void) { + init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, medias: [InstantPageMedia], openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void) { self.medias = medias - self.pagerNode = InstantPageSlideshowPagerNode(account: account, theme: theme, webPage: webPage, openMedia: openMedia) + self.pagerNode = InstantPageSlideshowPagerNode(account: account, theme: theme, webPage: webPage, openMedia: openMedia, longPressMedia: longPressMedia) self.pagerNode.replaceItems(medias, centralItemIndex: nil) self.pageControlNode = PageControlNode(dotColor: .white) diff --git a/TelegramUI/InstantPageTableItem.swift b/TelegramUI/InstantPageTableItem.swift index dd1d0068ce..dfe8d1d631 100644 --- a/TelegramUI/InstantPageTableItem.swift +++ b/TelegramUI/InstantPageTableItem.swift @@ -194,12 +194,12 @@ final class InstantPageTableItem: InstantPageScrollableItem { return false } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { var additionalNodes: [InstantPageNode] = [] for cell in self.cells { for item in cell.additionalItems { if item.wantsNode { - if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) { + if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, longPressMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) { node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY) additionalNodes.append(node) } diff --git a/TelegramUI/InstantPageTextItem.swift b/TelegramUI/InstantPageTextItem.swift index a523bed7e6..48c8248249 100644 --- a/TelegramUI/InstantPageTextItem.swift +++ b/TelegramUI/InstantPageTextItem.swift @@ -165,7 +165,7 @@ final class InstantPageTextItem: InstantPageItem { let lineFrame = frameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment) if lineFrame.insetBy(dx: -5.0, dy: -5.0).contains(transformedPoint) { var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) - if index == attributedString.length { + if index == self.attributedString.length { index -= 1 } else if index != 0 { var glyphStart: CGFloat = 0.0 @@ -174,8 +174,8 @@ final class InstantPageTextItem: InstantPageItem { index -= 1 } } - if index >= 0 && index < attributedString.length { - return (index, attributedString.attributes(at: index, effectiveRange: nil)) + if index >= 0 && index < self.attributedString.length { + return (index, self.attributedString.attributes(at: index, effectiveRange: nil)) } break } @@ -194,15 +194,18 @@ final class InstantPageTextItem: InstantPageItem { let lineRange = NSIntersectionRange(range, line.range) if lineRange.length != 0 { var leftOffset: CGFloat = 0.0 - if lineRange.location != line.range.location { + if lineRange.location != line.range.location || line.isRTL { leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) } var rightOffset: CGFloat = line.frame.width - if lineRange.location + lineRange.length != line.range.length { + if lineRange.location + lineRange.length != line.range.length || line.isRTL { rightOffset = ceil(CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, nil)) } let lineFrame = frameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment) - rects.append(CGRect(origin: CGPoint(x: lineFrame.minX + leftOffset, y: lineFrame.minY), size: CGSize(width: rightOffset - leftOffset, height: lineFrame.size.height))) + let width = abs(rightOffset - leftOffset) + if width > 1.0 { + rects.append(CGRect(origin: CGPoint(x: lineFrame.minX + (leftOffset < rightOffset ? leftOffset : rightOffset), y: lineFrame.minY), size: CGSize(width: width, height: lineFrame.size.height))) + } } } if !rects.isEmpty { @@ -309,7 +312,7 @@ final class InstantPageTextItem: InstantPageItem { return false } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { return nil } @@ -358,11 +361,11 @@ final class InstantPageScrollableTextItem: InstantPageScrollableItem { context.restoreGState() } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (ASDisplayNode & InstantPageNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (ASDisplayNode & InstantPageNode)? { var additionalNodes: [InstantPageNode] = [] for item in additionalItems { if item.wantsNode { - if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) { + if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, longPressMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) { node.frame = item.frame additionalNodes.append(node) } @@ -662,7 +665,8 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo if let _ = attributes[NSAttributedStringKey.strikethroughStyle] { let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) - strikethroughItems.append(InstantPageTextStrikethroughItem(frame: CGRect(x: workingLineOrigin.x + lowerX, y: workingLineOrigin.y, width: upperX - lowerX, height: fontLineHeight))) + let x = lowerX < upperX ? lowerX : upperX + strikethroughItems.append(InstantPageTextStrikethroughItem(frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y, width: abs(upperX - lowerX), height: fontLineHeight))) } if let color = attributes[NSAttributedStringKey.init(rawValue: InstantPageMarkerColorAttribute)] as? UIColor { var lineHeight = fontLineHeight @@ -674,7 +678,8 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo } let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) - markedItems.append(InstantPageTextMarkedItem(frame: CGRect(x: workingLineOrigin.x + lowerX, y: workingLineOrigin.y + delta, width: upperX - lowerX, height: lineHeight), color: color)) + let x = lowerX < upperX ? lowerX : upperX + markedItems.append(InstantPageTextMarkedItem(frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y + delta, width: abs(upperX - lowerX), height: lineHeight), color: color)) } if let item = attributes[NSAttributedStringKey.init(rawValue: InstantPageAnchorAttribute)] as? Dictionary, let name = item["name"] as? String, let empty = item["empty"] as? Bool { anchorItems.append(InstantPageTextAnchorItem(name: name, empty: empty)) @@ -743,7 +748,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment) for imageItem in line.imageItems { if let image = media[imageItem.id] as? TelegramMediaFile { - let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: image, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false) + let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: image, url: nil, caption: nil, credit: nil), interactive: true, roundCorners: false, fit: false) additionalItems.append(item) if item.frame.minY < topInset { diff --git a/TelegramUI/InstantPageWebEmbedItem.swift b/TelegramUI/InstantPageWebEmbedItem.swift index 1e76f735b6..7fff8cebd6 100644 --- a/TelegramUI/InstantPageWebEmbedItem.swift +++ b/TelegramUI/InstantPageWebEmbedItem.swift @@ -20,7 +20,7 @@ final class InstantPageWebEmbedItem: InstantPageItem { self.enableScrolling = enableScrolling } - func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { + func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? { return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight) } diff --git a/TelegramUI/ItemListTextEmptyStateItem.swift b/TelegramUI/ItemListTextEmptyStateItem.swift index 5c9f8fce2c..307be37e2f 100644 --- a/TelegramUI/ItemListTextEmptyStateItem.swift +++ b/TelegramUI/ItemListTextEmptyStateItem.swift @@ -59,7 +59,7 @@ final class ItemListTextEmptyStateItemNode: ItemListControllerEmptyStateItemNode self.validLayout = (layout, navigationBarHeight) var insets = layout.insets(options: [.statusBar]) insets.top += navigationBarHeight - let textSize = self.textNode.measure(CGSize(width: layout.size.width - 40.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) - self.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: insets.top + floor((layout.size.height - insets.top - insets.bottom - textSize.height) / 2.0)), size: textSize) + let textSize = self.textNode.measure(CGSize(width: layout.size.width - 40.0 - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + self.textNode.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - textSize.width) / 2.0), y: insets.top + floor((layout.size.height - insets.top - insets.bottom - textSize.height) / 2.0)), size: textSize) } } diff --git a/TelegramUI/NotificationContainerControllerNode.swift b/TelegramUI/NotificationContainerControllerNode.swift index c71e52817c..2fe4f93906 100644 --- a/TelegramUI/NotificationContainerControllerNode.swift +++ b/TelegramUI/NotificationContainerControllerNode.swift @@ -91,7 +91,12 @@ final class NotificationContainerControllerNode: ASDisplayNode { updatedDisplayingItems = true } - let itemNode = item.node() + var useCompactLayout = false + if let validLayout = self.validLayout { + useCompactLayout = min(validLayout.size.width, validLayout.size.height) < 375.0 + } + + let itemNode = item.node(compact: useCompactLayout) let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme) containerNode.item = item containerNode.contentNode = itemNode diff --git a/TelegramUI/NotificationItem.swift b/TelegramUI/NotificationItem.swift index 4b6e007a36..0a1431d4f9 100644 --- a/TelegramUI/NotificationItem.swift +++ b/TelegramUI/NotificationItem.swift @@ -5,7 +5,7 @@ import Display public protocol NotificationItem { var groupingKey: AnyHashable? { get } - func node() -> NotificationItemNode + func node(compact: Bool) -> NotificationItemNode func tapped(_ take: @escaping () -> (ASDisplayNode?, () -> Void)) func canBeExpanded() -> Bool func expand(_ take: @escaping () -> (ASDisplayNode?, () -> Void)) diff --git a/TelegramUI/NotificationItemContainerNode.swift b/TelegramUI/NotificationItemContainerNode.swift index 497f87e53f..a8bd09ccec 100644 --- a/TelegramUI/NotificationItemContainerNode.swift +++ b/TelegramUI/NotificationItemContainerNode.swift @@ -84,14 +84,16 @@ final class NotificationItemContainerNode: ASDisplayNode { self.validLayout = layout if let contentNode = self.contentNode { - var contentInsets = UIEdgeInsets(top: 8.0, left: 8.0 + layout.safeInsets.left, bottom: 8.0, right: 8.0 + layout.safeInsets.right) + let inset: CGFloat = 8.0 + + var contentInsets = UIEdgeInsets(top: inset, left: inset + layout.safeInsets.left, bottom: inset, right: inset + layout.safeInsets.right) if let statusBarHeight = layout.statusBarHeight, CGFloat(44.0).isLessThanOrEqualTo(statusBarHeight) { contentInsets.top += 34.0 } let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left) - let contentWidth = containerWidth - 8.0 * 2.0 + let contentWidth = containerWidth - contentInsets.left - contentInsets.right let contentHeight = contentNode.updateLayout(width: contentWidth, transition: transition) transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - containerWidth - 8.0 * 2.0) / 2.0), y: contentInsets.top - 16.0), size: CGSize(width: containerWidth + 8.0 * 2.0, height: 8.0 + contentHeight + 20.0 + 8.0))) diff --git a/TelegramUI/NotificationsAndSounds.swift b/TelegramUI/NotificationsAndSounds.swift index 0366b2de81..9fed027877 100644 --- a/TelegramUI/NotificationsAndSounds.swift +++ b/TelegramUI/NotificationsAndSounds.swift @@ -658,6 +658,7 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N |> deliverOnMainQueue).start(next: { status in switch status { case .notDetermined: + DeviceAccess.authorizeAccess(to: .notifications) account.telegramApplicationContext.applicationBindings.registerForNotifications() case .denied, .restricted: account.telegramApplicationContext.applicationBindings.openSettings() diff --git a/TelegramUI/SaveToCameraRoll.swift b/TelegramUI/SaveToCameraRoll.swift index a23648ed6f..f193956498 100644 --- a/TelegramUI/SaveToCameraRoll.swift +++ b/TelegramUI/SaveToCameraRoll.swift @@ -5,8 +5,9 @@ import Postbox import TelegramCore import Photos import Display +import MobileCoreServices -func saveToCameraRoll(applicationContext: TelegramApplicationContext, postbox: Postbox, mediaReference: AnyMediaReference) -> Signal { +private func fetchMediaData(applicationContext: TelegramApplicationContext, postbox: Postbox, mediaReference: AnyMediaReference) -> Signal<(MediaResourceData, Bool), NoError> { var resource: MediaResource? var isImage = true var fileExtension: String? @@ -50,55 +51,82 @@ func saveToCameraRoll(applicationContext: TelegramApplicationContext, postbox: P } } return fetchedData - |> mapToSignal { data -> Signal in + |> mapToSignal { data -> Signal<(MediaResourceData, Bool), NoError> in if data.complete { - return Signal { subscriber in - DeviceAccess.authorizeAccess(to: .mediaLibrary(.save), presentationData: applicationContext.currentPresentationData.with { $0 }, present: { c, a in - applicationContext.presentGlobalController(c, a) - }, openSettings: applicationContext.applicationBindings.openSettings, { authorized in - if !authorized { - subscriber.putCompletion() - return - } - - let tempVideoPath = NSTemporaryDirectory() + "\(arc4random64()).mp4" - PHPhotoLibrary.shared().performChanges({ - if isImage { - if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { - if #available(iOSApplicationExtension 9.0, *) { - PHAssetCreationRequest.forAsset().addResource(with: .photo, data: fileData, options: nil) - } else { - if let image = UIImage(data: fileData) { - PHAssetChangeRequest.creationRequestForAsset(from: image) - } - } - } - } else { - if let _ = try? FileManager.default.copyItem(atPath: data.path, toPath: tempVideoPath) { - PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: tempVideoPath)) - } - } - }, completionHandler: { _, error in - if let error = error { - print("\(error)") - } - let _ = try? FileManager.default.removeItem(atPath: tempVideoPath) - subscriber.putNext(Void()) - subscriber.putCompletion() - }) - }) - - return ActionDisposable { - } - } + return .single((data, isImage)) } else { return .complete() } } - |> take(1) - |> mapToSignal { _ -> Signal in return .complete() - } } else { return .complete() } } + +func saveToCameraRoll(applicationContext: TelegramApplicationContext, postbox: Postbox, mediaReference: AnyMediaReference) -> Signal { + return fetchMediaData(applicationContext: applicationContext, postbox: postbox, mediaReference: mediaReference) + |> mapToSignal { data, isImage -> Signal in + return Signal { subscriber in + DeviceAccess.authorizeAccess(to: .mediaLibrary(.save), presentationData: applicationContext.currentPresentationData.with { $0 }, present: { c, a in + applicationContext.presentGlobalController(c, a) + }, openSettings: applicationContext.applicationBindings.openSettings, { authorized in + if !authorized { + subscriber.putCompletion() + return + } + + let tempVideoPath = NSTemporaryDirectory() + "\(arc4random64()).mp4" + PHPhotoLibrary.shared().performChanges({ + if isImage { + if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + if #available(iOSApplicationExtension 9.0, *) { + PHAssetCreationRequest.forAsset().addResource(with: .photo, data: fileData, options: nil) + } else { + if let image = UIImage(data: fileData) { + PHAssetChangeRequest.creationRequestForAsset(from: image) + } + } + } + } else { + if let _ = try? FileManager.default.copyItem(atPath: data.path, toPath: tempVideoPath) { + PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: tempVideoPath)) + } + } + }, completionHandler: { _, error in + if let error = error { + print("\(error)") + } + let _ = try? FileManager.default.removeItem(atPath: tempVideoPath) + subscriber.putNext(Void()) + subscriber.putCompletion() + }) + }) + + return EmptyDisposable + } + } + |> take(1) + |> mapToSignal { _ -> Signal in return .complete() } +} + +func copyToPasteboard(applicationContext: TelegramApplicationContext, postbox: Postbox, mediaReference: AnyMediaReference) -> Signal { + return fetchMediaData(applicationContext: applicationContext, postbox: postbox, mediaReference: mediaReference) + |> mapToSignal { data, isImage -> Signal in + return Signal { subscriber in + let pasteboard = UIPasteboard.general + pasteboard.setPersistent(true) + + if mediaReference.media is TelegramMediaImage { + if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: .mappedIfSafe) { + pasteboard.setData(fileData, forPasteboardType: kUTTypeJPEG as String) + } + } + subscriber.putNext(Void()) + subscriber.putCompletion() + + return EmptyDisposable + } + } + |> take(1) + |> mapToSignal { _ -> Signal in return .complete() } +} diff --git a/TelegramUI/SearchBarNode.swift b/TelegramUI/SearchBarNode.swift index d38a738e19..7faad911b9 100644 --- a/TelegramUI/SearchBarNode.swift +++ b/TelegramUI/SearchBarNode.swift @@ -127,8 +127,8 @@ final class SearchBarNodeTheme { self.keyboard = keyboard } - init(theme: PresentationTheme) { - self.background = theme.rootController.activeNavigationSearchBar.backgroundColor + init(theme: PresentationTheme, active: Bool = true) { + self.background = active ? theme.rootController.activeNavigationSearchBar.backgroundColor : theme.rootController.navigationBar.backgroundColor self.separator = theme.rootController.navigationBar.separatorColor self.inputFill = theme.rootController.activeNavigationSearchBar.inputFillColor self.placeholder = theme.rootController.activeNavigationSearchBar.inputPlaceholderTextColor diff --git a/TelegramUI/SharePeersContainerNode.swift b/TelegramUI/SharePeersContainerNode.swift index 8c084b4a7a..a94c4a97a8 100644 --- a/TelegramUI/SharePeersContainerNode.swift +++ b/TelegramUI/SharePeersContainerNode.swift @@ -255,7 +255,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { let gridSize = CGSize(width: size.width - 12.0, height: size.height) - self.contentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 0.0, bottom: bottomInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), lineSpacing: 0.0)), transition: gridLayoutTransition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.contentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 0.0, bottom: bottomInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: gridLayoutTransition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) gridLayoutTransition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((size.width - gridSize.width) / 2.0), y: 0.0), size: gridSize)) if firstLayout { diff --git a/TelegramUI/ShareSearchContainerNode.swift b/TelegramUI/ShareSearchContainerNode.swift index a7d0624017..58996e0576 100644 --- a/TelegramUI/ShareSearchContainerNode.swift +++ b/TelegramUI/ShareSearchContainerNode.swift @@ -432,10 +432,10 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { let gridSize = CGSize(width: size.width, height: size.height - 5.0) - self.recentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToRecentItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 6.0, bottom: bottomInset, right: 6.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), lineSpacing: 0.0)), transition: gridLayoutTransition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.recentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToRecentItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 6.0, bottom: bottomInset, right: 6.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: gridLayoutTransition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) gridLayoutTransition.updateFrame(node: self.recentGridNode, frame: CGRect(origin: CGPoint(x: floor((size.width - gridSize.width) / 2.0), y: 5.0), size: gridSize)) - self.contentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 6.0, bottom: bottomInset, right: 6.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), lineSpacing: 0.0)), transition: gridLayoutTransition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.contentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 6.0, bottom: bottomInset, right: 6.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: gridLayoutTransition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) gridLayoutTransition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((size.width - gridSize.width) / 2.0), y: 5.0), size: gridSize)) if firstLayout { @@ -458,7 +458,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { let offset = self.recentGridNode.scrollView.contentOffset.y - self.contentGridNode.scrollView.contentOffset.y let gridSize = CGSize(width: size.width, height: size.height - 5.0) - self.recentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 6.0, bottom: bottomInset, right: 6.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), lineSpacing: 0.0)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.recentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 6.0, bottom: bottomInset, right: 6.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) transition.animatePositionAdditive(node: self.recentGridNode, offset: CGPoint(x: 0.0, y: offset)) } @@ -471,7 +471,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { let offset = self.recentGridNode.scrollView.contentOffset.y - self.contentGridNode.scrollView.contentOffset.y let gridSize = CGSize(width: size.width, height: size.height - 5.0) - self.contentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 6.0, bottom: bottomInset, right: 6.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), lineSpacing: 0.0)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.contentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 6.0, bottom: bottomInset, right: 6.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) transition.animatePositionAdditive(node: self.contentGridNode, offset: CGPoint(x: 0.0, y: -offset)) } diff --git a/TelegramUI/StickerPackPreviewControllerNode.swift b/TelegramUI/StickerPackPreviewControllerNode.swift index f5681788f2..195edecdc8 100644 --- a/TelegramUI/StickerPackPreviewControllerNode.swift +++ b/TelegramUI/StickerPackPreviewControllerNode.swift @@ -371,7 +371,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol let gridSize = CGSize(width: contentFrame.size.width, height: max(32.0, contentFrame.size.height - titleAreaHeight)) - self.contentGridNode.transaction(GridNodeTransaction(deleteItems: transaction?.deletions ?? [], insertItems: transaction?.insertions ?? [], updateItems: transaction?.updates ?? [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomGridInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), lineSpacing: 0.0)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.contentGridNode.transaction(GridNodeTransaction(deleteItems: transaction?.deletions ?? [], insertItems: transaction?.insertions ?? [], updateItems: transaction?.updates ?? [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomGridInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) transition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize)) if animateIn { diff --git a/TelegramUI/StickerPaneSearchContainerNode.swift b/TelegramUI/StickerPaneSearchContainerNode.swift index 2f2d8cce58..c930656561 100644 --- a/TelegramUI/StickerPaneSearchContainerNode.swift +++ b/TelegramUI/StickerPaneSearchContainerNode.swift @@ -389,7 +389,7 @@ final class StickerPaneSearchContainerNode: ASDisplayNode { let contentFrame = CGRect(origin: CGPoint(x: leftInset, y: searchBarHeight), size: CGSize(width: size.width - leftInset - rightInset, height: size.height - searchBarHeight)) - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: contentFrame.size, insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0 + bottomInset, right: 0.0), preloadSize: 300.0, type: .fixed(itemSize: CGSize(width: 75.0, height: 75.0), lineSpacing: 0.0)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: contentFrame.size, insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0 + bottomInset, right: 0.0), preloadSize: 300.0, type: .fixed(itemSize: CGSize(width: 75.0, height: 75.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) transition.updateFrame(node: self.trendingPane, frame: contentFrame) self.trendingPane.updateLayout(size: contentFrame.size, topInset: 0.0, bottomInset: bottomInset, isExpanded: false, transition: transition) diff --git a/TelegramUI/ThemeGridControllerNode.swift b/TelegramUI/ThemeGridControllerNode.swift index 3c23ce19fb..5cb1f148e4 100644 --- a/TelegramUI/ThemeGridControllerNode.swift +++ b/TelegramUI/ThemeGridControllerNode.swift @@ -244,7 +244,7 @@ final class ThemeGridControllerNode: ASDisplayNode { insets.top += spacing + buttonInset - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 300.0, type: .fixed(itemSize: imageSize, lineSpacing: spacing)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: nil, lineSpacing: spacing, itemSpacing: nil)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) diff --git a/TelegramUI/WebSearchController.swift b/TelegramUI/WebSearchController.swift new file mode 100644 index 0000000000..dc1eb99ede --- /dev/null +++ b/TelegramUI/WebSearchController.swift @@ -0,0 +1,84 @@ +import Foundation +import Postbox +import SwiftSignalKit +import Display +import AsyncDisplayKit +import TelegramCore + +final class WebSearchController: ViewController { + private let account: Account + + private var validLayout: ContainerViewLayout? + + private var controllerNode: WebSearchControllerNode { + return self.displayNode as! WebSearchControllerNode + } + + private var _ready = Promise() + override public var ready: Promise { + return self._ready + } + + private var presentationData: PresentationData + private var presentationDataDisposable: Disposable? + + private var navigationContentNode: WebSearchNavigationContentNode? + + init(account: Account) { + self.account = account + self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme).withUpdatedSeparatorColor(self.presentationData.theme.rootController.navigationBar.backgroundColor), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) + + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style + + self.presentationDataDisposable = (account.telegramApplicationContext.presentationData + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previousTheme = strongSelf.presentationData.theme + let previousStrings = strongSelf.presentationData.strings + + strongSelf.presentationData = presentationData + + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { + //strongSelf.updateThemeAndStrings() + } + } + }) + + let navigationContentNode = WebSearchNavigationContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, cancel: { [weak self] in + + }) + self.navigationContentNode = navigationContentNode + navigationContentNode.setQueryUpdated { [weak self] query in + guard let strongSelf = self, strongSelf.isNodeLoaded else { + return + } + //strongSelf.controllerNode.updateSearchQuery(query) + } + self.navigationBar?.setContentNode(navigationContentNode, animated: false) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.presentationDataDisposable?.dispose() + } + + override public func loadDisplayNode() { + self.displayNode = WebSearchControllerNode(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings) + self._ready.set(.single(true)) + + self.displayNodeDidLoad() + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.validLayout = layout + + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) + } +} diff --git a/TelegramUI/WebSearchControllerNode.swift b/TelegramUI/WebSearchControllerNode.swift new file mode 100644 index 0000000000..abf53f5d55 --- /dev/null +++ b/TelegramUI/WebSearchControllerNode.swift @@ -0,0 +1,94 @@ +import Foundation +import AsyncDisplayKit +import Postbox +import SwiftSignalKit +import Display +import TelegramCore + +class WebSearchControllerNode: ASDisplayNode { + private let segmentedBackgroundNode: ASDisplayNode + private let segmentedSeparatorNode: ASDisplayNode + private let segmentedControl: UISegmentedControl + + private let toolbarBackgroundNode: ASDisplayNode + private let toolbarSeparatorNode: ASDisplayNode + private let cancelButton: HighlightableButtonNode + private let sendButton: HighlightableButtonNode + private let attributionNode: ASImageNode + + private let account: Account + + private var containerLayout: (ContainerViewLayout, CGFloat)? + + init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { + self.account = account + + self.segmentedBackgroundNode = ASDisplayNode() + self.segmentedBackgroundNode.backgroundColor = theme.rootController.navigationBar.backgroundColor + + self.segmentedSeparatorNode = ASDisplayNode() + self.segmentedSeparatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor + + self.segmentedControl = UISegmentedControl(items: [strings.WebSearch_Images, strings.WebSearch_GIFs, strings.WebSearch_RecentSectionTitle]) + self.segmentedControl.tintColor = theme.rootController.navigationBar.accentTextColor + self.segmentedControl.selectedSegmentIndex = 0 + + self.toolbarBackgroundNode = ASDisplayNode() + self.toolbarBackgroundNode.backgroundColor = theme.rootController.navigationBar.backgroundColor + + self.toolbarSeparatorNode = ASDisplayNode() + self.toolbarSeparatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor + + self.attributionNode = ASImageNode() + + self.cancelButton = HighlightableButtonNode() + self.cancelButton.setTitle(strings.Common_Cancel, with: Font.regular(17.0), with: theme.rootController.navigationBar.accentTextColor, for: .normal) + self.sendButton = HighlightableButtonNode() + + super.init() + + self.setViewBlock({ + return UITracingLayerView() + }) + + self.backgroundColor = theme.chatList.backgroundColor + + self.addSubnode(self.segmentedBackgroundNode) + self.addSubnode(self.segmentedSeparatorNode) + self.view.addSubview(self.segmentedControl) + self.segmentedControl.addTarget(self, action: #selector(self.indexChanged), for: .valueChanged) + + self.addSubnode(self.toolbarBackgroundNode) + self.addSubnode(self.toolbarSeparatorNode) + self.addSubnode(self.cancelButton) + self.addSubnode(self.sendButton) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.containerLayout = (layout, navigationBarHeight) + + var insets = layout.insets(options: [.input]) + insets.top += navigationBarHeight + + let segmentedHeight: CGFloat = 40.0 + let panelY: CGFloat = insets.top - UIScreenPixel - 4.0 + + transition.updateFrame(node: self.segmentedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: segmentedHeight))) + transition.updateFrame(node: self.segmentedSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY + segmentedHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + + var controlSize = self.segmentedControl.sizeThatFits(layout.size) + controlSize.width = layout.size.width - 8.0 * 2.0 + + transition.updateFrame(view: self.segmentedControl, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: panelY + floor((segmentedHeight - controlSize.height) / 2.0)), size: controlSize)) + } + + @objc private func indexChanged() { +// if self.segmentedControl.selectedSegmentIndex == 0 { +// self.chatController?.displayNode.isHidden = false +// self.listNode.isHidden = true +// } else { +// self.chatController?.displayNode.isHidden = true +// self.listNode.isHidden = false +// } + } +} diff --git a/TelegramUI/WebSearchNavigationContentNode.swift b/TelegramUI/WebSearchNavigationContentNode.swift new file mode 100644 index 0000000000..a298ceab06 --- /dev/null +++ b/TelegramUI/WebSearchNavigationContentNode.swift @@ -0,0 +1,64 @@ +import Foundation +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore + +private let searchBarFont = Font.regular(14.0) + +final class WebSearchNavigationContentNode: NavigationBarContentNode { + private let theme: PresentationTheme + private let strings: PresentationStrings + + private let cancel: () -> Void + + private let searchBar: SearchBarNode + + private var queryUpdated: ((String) -> Void)? + + init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void) { + self.theme = theme + self.strings = strings + + self.cancel = cancel + + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, active: false), strings: strings) + let placeholderText = strings.Common_Search + self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.activeNavigationSearchBar.inputPlaceholderTextColor) + + super.init() + + self.addSubnode(self.searchBar) + + self.searchBar.cancel = { [weak self] in + self?.searchBar.deactivate(clear: false) + self?.cancel() + } + + self.searchBar.textUpdated = { [weak self] query in + self?.queryUpdated?(query) + } + } + + func setQueryUpdated(_ f: @escaping (String) -> Void) { + self.queryUpdated = f + } + + override func layout() { + super.layout() + + let size = self.bounds.size + + let searchBarFrame = CGRect(origin: CGPoint(), size: size) + self.searchBar.frame = searchBarFrame + self.searchBar.updateLayout(boundingSize: size, leftInset: 0.0, rightInset: 0.0, transition: .immediate) + } + + func activate() { + self.searchBar.activate() + } + + func deactivate() { + self.searchBar.deactivate(clear: false) + } +}