diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index 7be5653b2b..eff5da9fc1 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -2490,6 +2490,13 @@ path = TelegramUI/Resources/Animations; sourceTree = ""; }; + 093857B422464B2700EB6A54 /* Stats */ = { + isa = PBXGroup; + children = ( + ); + name = Stats; + sourceTree = ""; + }; 0941A99E210B053300EBE194 /* Open In */ = { isa = PBXGroup; children = ( @@ -4325,6 +4332,7 @@ D0EE97131D88BB1A006C18E1 /* Peer Info */ = { isa = PBXGroup; children = ( + 093857B422464B2700EB6A54 /* Stats */, D0B843CC1DA903BB005F29E1 /* PeerInfoController.swift */, D0486F091E523C8500091F0C /* GroupInfoController.swift */, D03E5E0E1E55F8B90029569A /* ChannelVisibilityController.swift */, diff --git a/TelegramUI/AvatarGalleryItemFooterContentNode.swift b/TelegramUI/AvatarGalleryItemFooterContentNode.swift index 7dd96e3d3f..32c596d6a1 100644 --- a/TelegramUI/AvatarGalleryItemFooterContentNode.swift +++ b/TelegramUI/AvatarGalleryItemFooterContentNode.swift @@ -99,7 +99,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { } } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { let width = size.width var panelHeight: CGFloat = 44.0 + bottomInset panelHeight += contentInset diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 323ef3b1f4..d1e9c9623f 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -296,7 +296,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal return false } strongSelf.commitPurposefulAction() - strongSelf.videoUnmuteTooltipController?.dismiss() + strongSelf.dismissAllTooltips() var openMessageByAction: Bool = false @@ -860,7 +860,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } }) } - }, longTap: { [weak self] action, messageId in + }, longTap: { [weak self] action, message in if let strongSelf = self { switch action { case let .url(url): @@ -1027,7 +1027,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal strongSelf.chatDisplayNode.dismissInput() strongSelf.present(actionSheet, in: .window(.root)) case let .timecode(timecode, text): - guard let messageId = messageId else { + guard let message = message else { return } let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) @@ -1036,7 +1036,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { - strongSelf.controllerInteraction?.seekToTimecode(messageId, timecode) + strongSelf.controllerInteraction?.seekToTimecode(message, timecode, true) } }), ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in @@ -1216,21 +1216,22 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal })) } } - }, seekToTimecode: { [weak self] messageId, timestamp in + }, seekToTimecode: { [weak self] message, timestamp, forceOpen in if let strongSelf = self { - let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) - var completed = false - strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in - if !completed, let itemNode = itemNode as? ChatMessageItemView, itemNode.item?.message.id == messageId, let (action, _, _, _, _) = itemNode.playMediaWithSound() { - if case let .visible(fraction) = itemNode.visibility, fraction > 0.7 { - action(Double(timestamp)) - } else if let message = message { - let _ = strongSelf.controllerInteraction?.openMessage(message, .timecode(Double(timestamp))) + var found = false + if !forceOpen { + strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in + if !found, let itemNode = itemNode as? ChatMessageItemView, itemNode.item?.message.id == message.id, let (action, _, _, _, _) = itemNode.playMediaWithSound() { + if case let .visible(fraction) = itemNode.visibility, fraction > 0.7 { + action(Double(timestamp)) + } else { + let _ = strongSelf.controllerInteraction?.openMessage(message, .timecode(Double(timestamp))) + } + found = true } - completed = true } } - if !completed, let message = message { + if !found { let _ = strongSelf.controllerInteraction?.openMessage(message, .timecode(Double(timestamp))) } } @@ -2762,15 +2763,15 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal guard let strongSelf = self, let layout = strongSelf.validLayout, strongSelf.traceVisibility() && isTopmostChatController(strongSelf) else { return } - let deviceMetrics = DeviceMetrics.forScreenSize(layout.size) let icon: UIImage? - if deviceMetrics == .iPhoneX || deviceMetrics == .iPhoneXSMax { - icon = UIImage(bundleImageName: "Chat/Message/VolumeButtonIconX") - } else { - icon = UIImage(bundleImageName: "Chat/Message/VolumeButtonIcon") + switch DeviceMetrics.forScreenSize(layout.size) { + case .iPhoneX?, .iPhoneXSMax?: + icon = UIImage(bundleImageName: "Chat/Message/VolumeButtonIconX") + default: + icon = UIImage(bundleImageName: "Chat/Message/VolumeButtonIcon") } if let location = location, let icon = icon { - strongSelf.mediaRestrictedTooltipController?.dismiss() + strongSelf.videoUnmuteTooltipController?.dismiss() let tooltipController = TooltipController(content: .iconAndText(icon, strongSelf.presentationInterfaceState.strings.Conversation_PressVolumeButtonForSound), timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true) strongSelf.videoUnmuteTooltipController = tooltipController tooltipController.dismissed = { [weak tooltipController] in @@ -2816,7 +2817,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal let _ = ApplicationSpecificNotice.incrementChatMediaMediaRecordingTips(accountManager: strongSelf.context.sharedContext.accountManager, count: 3).start() } - strongSelf.displayMediaRecordingTip() + strongSelf.displayMediaRecordingTooltip() } }, setupMessageAutoremoveTimeout: { [weak self] in if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { @@ -3491,7 +3492,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } if displayTip { let _ = ApplicationSpecificNotice.incrementChatMediaMediaRecordingTips(accountManager: strongSelf.context.sharedContext.accountManager).start() - strongSelf.displayMediaRecordingTip() + strongSelf.displayMediaRecordingTooltip() } }) } @@ -3505,11 +3506,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal self.chatDisplayNode.historyNode.canReadHistory.set(.single(false)) self.saveInterfaceState() - self.messageTooltipController?.dismiss() - self.videoUnmuteTooltipController?.dismiss() - self.silentPostTooltipController?.dismiss() - self.mediaRecordingModeTooltipController?.dismiss() - self.mediaRestrictedTooltipController?.dismiss() + self.dismissAllTooltips() self.window?.forEachController({ controller in if let controller = controller as? UndoOverlayController { @@ -4143,24 +4140,36 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal legacyController.bind(controller: navigationController) legacyController.enableSizeClassSignal = true - let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, editMediaOptions: editMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, openGallery: { + + let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText + let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, editMediaOptions: editMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText.string, openGallery: { self?.presentMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals in + if !inputText.string.isEmpty { + strongSelf.clearInputText() + } if editMediaOptions != nil { self?.editMessageMediaWithLegacySignals(signals) } else { self?.enqueueMediaMessages(signals: signals) } }) - }, openCamera: { cameraView, menuController in + }, openCamera: { [weak self] cameraView, menuController in if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { - presentedLegacyCamera(context: strongSelf.context, peer: peer, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, sendMessagesWithSignals: { signals in - if editMediaOptions != nil { - self?.editMessageMediaWithLegacySignals(signals!) - } else { - self?.enqueueMediaMessages(signals: signals) + presentedLegacyCamera(context: strongSelf.context, peer: peer, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, sendMessagesWithSignals: { [weak self] signals in + if let strongSelf = self { + if editMediaOptions != nil { + strongSelf.editMessageMediaWithLegacySignals(signals!) + } else { + strongSelf.enqueueMediaMessages(signals: signals) + } + if !inputText.string.isEmpty { + strongSelf.clearInputText() + } + } + }, recognizedQRCode: { [weak self] code in + if let strongSelf = self, let (host, port, username, password, secret) = parseProxyUrl(code) { + strongSelf.openResolved(ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret)) } - }, recognizedQRCode: { code in - self?.processQRCode(code) }) } }, openFileGallery: { @@ -4174,6 +4183,9 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal }, openPoll: { self?.presentPollCreation() }, sendMessagesWithSignals: { [weak self] signals in + if !inputText.string.isEmpty { + strongSelf.clearInputText() + } if editMediaOptions != nil { self?.editMessageMediaWithLegacySignals(signals!) } else { @@ -4290,6 +4302,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { return } + let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText let _ = legacyAssetPicker(context: strongSelf.context, presentationData: strongSelf.presentationData, editingMedia: editingMedia, fileMode: fileMode, peer: peer, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true).start(next: { generator in if let strongSelf = self { let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) @@ -4301,7 +4314,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal legacyController.bind(controller: controller) legacyController.deferScreenEdgeGestures = [.top] - configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, presentWebSearch: { [weak self, weak legacyController] in + configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, initialCaption: inputText.string, presentWebSearch: { [weak self, weak legacyController] in if let strongSelf = self { let controller = WebSearchController(context: strongSelf.context, peer: peer, configuration: searchBotsConfiguration, mode: .media(completion: { results, selectionState, editingState in if let legacyController = legacyController { @@ -4321,7 +4334,13 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } }) controller.descriptionGenerator = legacyAssetPickerItemGenerator() - controller.completionBlock = { [weak legacyController] signals in + controller.completionBlock = { [weak legacyController, weak self] signals in + if let strongSelf = self { + if !inputText.string.isEmpty { + strongSelf.clearInputText() + } + } + if let legacyController = legacyController { legacyController.dismiss() completion(signals!) @@ -4811,7 +4830,6 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } else if let videoRecorderValue = self.videoRecorderValue { if case .send = action { videoRecorderValue.completeVideo() - //self.tempVideoRecorderValue = videoRecorderValue self.videoRecorder.set(.single(nil)) } else { self.videoRecorder.set(.single(nil)) @@ -6143,7 +6161,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal self.interfaceInteraction?.beginMessageSearch(.everything, query) } - private func displayMediaRecordingTip() { + private func displayMediaRecordingTooltip() { let rect: CGRect? = self.chatDisplayNode.frameForInputActionButton() let updatedMode: ChatTextInputMediaRecordingButtonMode = self.presentationInterfaceState.interfaceState.mediaRecordingMode @@ -6174,8 +6192,19 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } } + private func dismissAllTooltips() { + self.messageTooltipController?.dismiss() + self.videoUnmuteTooltipController?.dismiss() + self.silentPostTooltipController?.dismiss() + self.mediaRecordingModeTooltipController?.dismiss() + self.mediaRestrictedTooltipController?.dismiss() + } + private func commitPurposefulAction() { - self.purposefulAction?() + if let purposefulAction = self.purposefulAction { + self.purposefulAction = nil + purposefulAction() + } } public var keyShortcuts: [KeyShortcut] { @@ -6287,12 +6316,6 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal return inputShortcuts + otherShortcuts } - private func processQRCode(_ code: String) { - if let (host, port, username, password, secret) = parseProxyUrl(code) { - self.openResolved(ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret)) - } - } - func getTransitionInfo(messageId: MessageId, media: Media) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? { var selectedNode: (ASDisplayNode, () -> (UIView?, UIView?))? self.chatDisplayNode.historyNode.forEachItemNode { itemNode in @@ -6313,4 +6336,17 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal return nil } } + + private func clearInputText() { + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + if !state.interfaceState.effectiveInputState.inputText.string.isEmpty { + return state.updatedInterfaceState { interfaceState in + let effectiveInputState = ChatTextInputState(inputText: NSAttributedString(string: "")) + return interfaceState.withUpdatedEffectiveInputState(effectiveInputState) + } + } else { + return state + } + }) + } } diff --git a/TelegramUI/ChatControllerInteraction.swift b/TelegramUI/ChatControllerInteraction.swift index 8de008980b..e16d330aa3 100644 --- a/TelegramUI/ChatControllerInteraction.swift +++ b/TelegramUI/ChatControllerInteraction.swift @@ -77,7 +77,7 @@ public final class ChatControllerInteraction { let navigationController: () -> NavigationController? let presentGlobalOverlayController: (ViewController, Any?) -> Void let callPeer: (PeerId) -> Void - let longTap: (ChatControllerInteractionLongTapAction, MessageId?) -> Void + let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void let openCheckoutOrReceipt: (MessageId) -> Void let openSearch: () -> Void let setupReply: (MessageId) -> Void @@ -89,7 +89,7 @@ public final class ChatControllerInteraction { let requestSelectMessagePollOption: (MessageId, Data) -> Void let openAppStorePage: () -> Void let displayMessageTooltip: (MessageId, String, ASDisplayNode?, CGRect?) -> Void - let seekToTimecode: (MessageId, Double) -> Void + let seekToTimecode: (Message, Double, Bool) -> Void let requestMessageUpdate: (MessageId) -> Void let cancelInteractiveKeyboardGestures: () -> Void @@ -102,7 +102,7 @@ public final class ChatControllerInteraction { var pollActionState: ChatInterfacePollActionState var searchTextHighightState: String? - init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, MessageId?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (MessageId, Double) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState) { + init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention @@ -166,7 +166,7 @@ public final class ChatControllerInteraction { }, requestSelectMessagePollOption: { _, _ in }, openAppStorePage: { }, displayMessageTooltip: { _, _, _, _ in - }, seekToTimecode: { _, _ in + }, seekToTimecode: { _, _, _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/TelegramUI/ChatItemGalleryFooterContentNode.swift b/TelegramUI/ChatItemGalleryFooterContentNode.swift index e820d67211..ebbc0d2ddf 100644 --- a/TelegramUI/ChatItemGalleryFooterContentNode.swift +++ b/TelegramUI/ChatItemGalleryFooterContentNode.swift @@ -57,6 +57,19 @@ private let playImage = generateImage(CGSize(width: 15.0, height: 18.0), rotated private let cloudFetchIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: UIColor.white) +private let captionMaskImage = generateImage(CGSize(width: 1.0, height: 17.0), opaque: false, rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let gradientColors = [UIColor.white.withAlphaComponent(1.0).cgColor, UIColor.white.withAlphaComponent(0.0).cgColor] as CFArray + + var locations: [CGFloat] = [0.0, 1.0] + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 17.0), options: CGGradientDrawingOptions()) +}) + private let titleFont = Font.medium(15.0) private let dateFont = Font.regular(14.0) @@ -102,7 +115,7 @@ enum ChatItemGalleryFooterContentTapAction { case ignore } -final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { +final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScrollViewDelegate { private let context: AccountContext private var theme: PresentationTheme private var strings: PresentationStrings @@ -110,6 +123,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { private let deleteButton: UIButton private let actionButton: UIButton + private let maskNode: ASDisplayNode + private let scrollWrapperNode: ASDisplayNode + private let scrollNode: ASScrollNode + private let textNode: ImmediateTextNode private let authorNameNode: ASTextNode private let dateNode: ASTextNode @@ -213,10 +230,18 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.deleteButton.setImage(deleteImage, for: [.normal]) self.actionButton.setImage(actionImage, for: [.normal]) + self.scrollWrapperNode = ASDisplayNode() + self.scrollWrapperNode.clipsToBounds = true + + self.scrollNode = ASScrollNode() + self.scrollNode.clipsToBounds = false + + self.maskNode = ASDisplayNode() + self.textNode = ImmediateTextNode() - self.textNode.maximumNumberOfLines = 10 + self.textNode.maximumNumberOfLines = 0 self.textNode.linkHighlightColor = UIColor(rgb: 0x5ac8fa, alpha: 0.2) - + self.authorNameNode = ASTextNode() self.authorNameNode.maximumNumberOfLines = 1 self.authorNameNode.isUserInteractionEnabled = false @@ -271,7 +296,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.view.addSubview(self.deleteButton) self.view.addSubview(self.actionButton) - self.addSubnode(self.textNode) + self.addSubnode(self.scrollWrapperNode) + self.scrollWrapperNode.addSubnode(self.scrollNode) + self.scrollNode.addSubnode(self.textNode) + self.addSubnode(self.authorNameNode) self.addSubnode(self.dateNode) @@ -307,6 +335,21 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.messageContextDisposable.dispose() } + override func didLoad() { + super.didLoad() + self.scrollNode.view.delegate = self + + if let maskImage = captionMaskImage { + let mask = CALayer() + mask.contents = maskImage.cgImage + mask.contentsScale = maskImage.scale + //mask.contentsCenter = CGRect(x: max(corners.topLeft.radius, corners.bottomLeft.radius) / maskImage.size.width, y: max(corners.topLeft.radius, corners.topRight.radius) / maskImage.size.height, width: (maskImage.size.width - max(corners.topLeft.radius, corners.bottomLeft.radius) - max(corners.topRight.radius, corners.bottomRight.radius)) / maskImage.size.width, height: (maskImage.size.height - max(corners.topLeft.radius, corners.topRight.radius) - max(corners.bottomLeft.radius, corners.bottomRight.radius)) / maskImage.size.height) + + //self.scrollWrapperNode.layer.mask = mask + //self.scrollWrapperNode.layer.mask?.frame = self.scrollWrapperNode.bounds + } + } + private func actionForAttributes(_ attributes: [NSAttributedStringKey: Any]) -> GalleryControllerInteractionTapAction? { if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { return .url(url: url, concealed: false) @@ -441,7 +484,20 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.currentWebPageAndMedia = (webPage, media) } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.requestLayout?(.immediate) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if self.scrollWrapperNode.frame.contains(point) { + return self.scrollNode.view + } else { + return result + } + } + + override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { let width = size.width var bottomInset = bottomInset if bottomInset < 30.0 { @@ -451,7 +507,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { panelHeight += contentInset let isLandscape = size.width > size.height - let displayCaption = !self.textNode.isHidden && !isLandscape + let displayCaption: Bool + if case .compact = metrics.widthClass { + displayCaption = !self.textNode.isHidden && !isLandscape + } else { + displayCaption = !self.textNode.isHidden + } var textFrame = CGRect() if !self.textNode.isHidden { @@ -459,10 +520,55 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { let topInset: CGFloat = 8.0 let textBottomInset: CGFloat = 8.0 let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)) + + var textOffset: CGFloat = 0.0 if displayCaption { - panelHeight += textSize.height + topInset + textBottomInset + var visibleTextHeight = textSize.height + if visibleTextHeight > 100.0 { + visibleTextHeight = 80.0 + self.scrollNode.view.isScrollEnabled = true + } else { + self.scrollNode.view.isScrollEnabled = false + } + + let visibleTextPanelHeight = visibleTextHeight + topInset + textBottomInset + let scrollViewContentSize = CGSize(width: width, height: textSize.height + topInset + textBottomInset) + if self.scrollNode.view.contentSize != scrollViewContentSize { + self.scrollNode.view.contentSize = scrollViewContentSize + } + let scrollNodeFrame = CGRect(x: 0.0, y: 0.0, width: width, height: visibleTextPanelHeight) + if self.scrollNode.frame != scrollNodeFrame { + self.scrollNode.frame = scrollNodeFrame + } + + textOffset = min(400.0, self.scrollNode.view.contentOffset.y) + panelHeight = max(0.0, panelHeight + visibleTextPanelHeight + textOffset) + + if self.scrollNode.view.isScrollEnabled { + if self.scrollWrapperNode.layer.mask == nil { + let maskImage = captionMaskImage! + let maskLayer = CALayer() + maskLayer.contents = maskImage.cgImage + maskLayer.contentsScale = maskImage.scale + maskLayer.contentsCenter = CGRect(x: 0.0, y: 0.0, width: 1.0, height: (maskImage.size.height - 16.0) / maskImage.size.height) + self.scrollWrapperNode.layer.mask = maskLayer + + } + } else { + self.scrollWrapperNode.layer.mask = nil + } + + let scrollWrapperNodeFrame = CGRect(x: 0.0, y: 0.0, width: width, height: max(0.0, visibleTextPanelHeight + textOffset)) + if self.scrollWrapperNode.frame != scrollWrapperNodeFrame { + self.scrollWrapperNode.frame = scrollWrapperNodeFrame + self.scrollWrapperNode.layer.mask?.frame = self.scrollWrapperNode.bounds //.offsetBy(dx: 0.0, dy: textOffset) + self.scrollWrapperNode.layer.mask?.removeAllAnimations() + } + } + textFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + textOffset), size: textSize) + if self.textNode.frame != textFrame { + self.textNode.frame = textFrame } - textFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: textSize) } if let scrubberView = self.scrubberView, scrubberView.superview == self.view { @@ -484,8 +590,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { scrubberView.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) transition.updateFrame(layer: scrubberView.layer, frame: scrubberFrame) } - - self.textNode.frame = textFrame transition.updateAlpha(node: self.textNode, alpha: displayCaption ? 1.0 : 0.0) self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0)) @@ -524,8 +628,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { scrubberView.alpha = 1.0 scrubberView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) } - transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight)) - self.textNode.alpha = 1.0 + transition.animatePositionAdditive(node: self.scrollWrapperNode, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight)) + self.scrollWrapperNode.alpha = 1.0 self.dateNode.alpha = 1.0 self.authorNameNode.alpha = 1.0 self.deleteButton.alpha = 1.0 @@ -534,7 +638,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.forwardButton.alpha = 1.0 self.statusNode.alpha = 1.0 self.playbackControlButton.alpha = 1.0 - self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + self.scrollWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) } override func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { @@ -546,8 +650,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { scrubberView.alpha = 0.0 scrubberView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) } - transition.updateFrame(node: self.textNode, frame: self.textNode.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight)) - self.textNode.alpha = 0.0 + transition.updateFrame(node: self.scrollWrapperNode, frame: self.scrollWrapperNode.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight)) + self.scrollWrapperNode.alpha = 0.0 self.dateNode.alpha = 0.0 self.authorNameNode.alpha = 0.0 self.deleteButton.alpha = 0.0 @@ -556,7 +660,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.forwardButton.alpha = 0.0 self.statusNode.alpha = 0.0 self.playbackControlButton.alpha = 0.0 - self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { _ in + self.scrollWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { _ in completion() }) } diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index bae8d4cd77..a1ca3114fc 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -1612,6 +1612,35 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { switch recognizer.state { case .ended: if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { + var mediaMessage: Message? + var forceOpen = false + if let item = self.item { + for media in item.message.media { + if let file = media as? TelegramMediaFile, file.duration != nil { + mediaMessage = item.message + } + } + var forceOpen = false + if mediaMessage == nil { + for attribute in item.message.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + if let replyMessage = item.message.associatedMessages[attribute.messageId] { + for media in replyMessage.media { + if let file = media as? TelegramMediaFile, file.duration != nil { + mediaMessage = replyMessage + forceOpen = true + break + } + } + } + } + } + } + if mediaMessage == nil { + mediaMessage = item.message + } + } + switch gesture { case .tap: if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) { @@ -1729,33 +1758,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { break loop case let .timecode(timecode, _): foundTapAction = true - if let item = self.item { - var messageId: MessageId? - for media in item.message.media { - if let file = media as? TelegramMediaFile, file.duration != nil { - messageId = item.message.id - } - } - if messageId == nil { - for attribute in item.message.attributes { - if let attribute = attribute as? ReplyMessageAttribute { - if let replyMessage = item.message.associatedMessages[attribute.messageId] { - for media in replyMessage.media { - if let file = media as? TelegramMediaFile, file.duration != nil { - messageId = replyMessage.id - break - } - } - } - } - } - } - if messageId == nil { - messageId = item.message.id - } - if let messageId = messageId { - item.controllerInteraction.seekToTimecode(messageId, timecode) - } + if let item = self.item, let mediaMessage = mediaMessage { + item.controllerInteraction.seekToTimecode(mediaMessage, timecode, forceOpen) } break loop } @@ -1765,7 +1769,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { } case .longTap, .doubleTap: if let item = self.item, self.backgroundNode.frame.contains(location) { - let messageId = item.message.id + let message = item.message var foundTapAction = false var tapMessage: Message? = item.content.firstMessage @@ -1783,23 +1787,23 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { break case let .url(url, _): foundTapAction = true - item.controllerInteraction.longTap(.url(url), messageId) + item.controllerInteraction.longTap(.url(url), message) break loop case let .peerMention(peerId, mention): foundTapAction = true - item.controllerInteraction.longTap(.peerMention(peerId, mention), messageId) + item.controllerInteraction.longTap(.peerMention(peerId, mention), message) break loop case let .textMention(name): foundTapAction = true - item.controllerInteraction.longTap(.mention(name), messageId) + item.controllerInteraction.longTap(.mention(name), message) break loop case let .botCommand(command): foundTapAction = true - item.controllerInteraction.longTap(.command(command), messageId) + item.controllerInteraction.longTap(.command(command), message) break loop case let .hashtag(_, hashtag): foundTapAction = true - item.controllerInteraction.longTap(.hashtag(hashtag), messageId) + item.controllerInteraction.longTap(.hashtag(hashtag), message) break loop case .instantPage: break @@ -1812,7 +1816,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { break case let .timecode(timecode, text): foundTapAction = true - item.controllerInteraction.longTap(.timecode(timecode, text), messageId) + if let mediaMessage = mediaMessage { + item.controllerInteraction.longTap(.timecode(timecode, text), mediaMessage) + } break loop } } diff --git a/TelegramUI/ChatMessageItemView.swift b/TelegramUI/ChatMessageItemView.swift index 6d2a1e0fb0..1725e31810 100644 --- a/TelegramUI/ChatMessageItemView.swift +++ b/TelegramUI/ChatMessageItemView.swift @@ -757,7 +757,7 @@ public class ChatMessageItemView: ListViewItemNode { if let item = self.item { switch button.action { case let .url(url): - item.controllerInteraction.longTap(.url(url), item.message.id) + item.controllerInteraction.longTap(.url(url), item.message) default: break } diff --git a/TelegramUI/ChatRecentActionsControllerNode.swift b/TelegramUI/ChatRecentActionsControllerNode.swift index 76ca76d52e..3946c39a41 100644 --- a/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/TelegramUI/ChatRecentActionsControllerNode.swift @@ -220,7 +220,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, presentController: { _, _ in }, navigationController: { [weak self] in return self?.getNavigationController() - }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { [weak self] action, messageId in + }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { [weak self] action, message in if let strongSelf = self { switch action { case let .url(url): @@ -348,7 +348,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { ])]) strongSelf.presentController(actionSheet, nil) case let .timecode(timecode, text): - guard let messageId = messageId else { + guard let message = message else { return } let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) @@ -357,7 +357,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { - strongSelf.controllerInteraction?.seekToTimecode(messageId, timecode) + strongSelf.controllerInteraction?.seekToTimecode(message, timecode, true) } }), ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in @@ -387,7 +387,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { strongSelf.context.sharedContext.applicationBindings.openAppStorePage() } }, displayMessageTooltip: { _, _, _, _ in - }, seekToTimecode: { _, _ in + }, seekToTimecode: { _, _, _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, diff --git a/TelegramUI/DeviceContactData.swift b/TelegramUI/DeviceContactData.swift index 8e5349eb4e..af708e8b3f 100644 --- a/TelegramUI/DeviceContactData.swift +++ b/TelegramUI/DeviceContactData.swift @@ -337,6 +337,9 @@ public extension DeviceContactExtendedData { value.postalCode = address.postcode return CNLabeledValue(label: address.label, value: value) }) + if let birthdayDate = self.birthdayDate { + contact.birthday = Calendar(identifier: .gregorian).dateComponents([.day, .month, .year], from: birthdayDate) + } return contact } diff --git a/TelegramUI/DeviceContactInfoController.swift b/TelegramUI/DeviceContactInfoController.swift index 2611e3b4d8..d538be938b 100644 --- a/TelegramUI/DeviceContactInfoController.swift +++ b/TelegramUI/DeviceContactInfoController.swift @@ -614,7 +614,8 @@ private func deviceContactInfoEntries(account: Account, presentationData: Presen if let birthday = contactData.birthdayDate { let dateText: String let calendar = Calendar(identifier: .gregorian) - let components = calendar.dateComponents(Set([.era, .year, .month, .day]), from: birthday) + var components = calendar.dateComponents(Set([.era, .year, .month, .day]), from: birthday) + components.hour = 12 if let year = components.year, year > 1 { dateText = stringForDate(timestamp: Int32(birthday.timeIntervalSince1970), strings: presentationData.strings) } else { diff --git a/TelegramUI/GalleryFooterContentNode.swift b/TelegramUI/GalleryFooterContentNode.swift index 0b50fefb23..b07153d141 100644 --- a/TelegramUI/GalleryFooterContentNode.swift +++ b/TelegramUI/GalleryFooterContentNode.swift @@ -19,7 +19,7 @@ open class GalleryFooterContentNode: ASDisplayNode { var requestLayout: ((ContainedViewLayoutTransition) -> Void)? var controllerInteraction: GalleryControllerInteraction? - func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { return 0.0 } diff --git a/TelegramUI/GalleryFooterNode.swift b/TelegramUI/GalleryFooterNode.swift index b7dba3d7ed..14b1fe7815 100644 --- a/TelegramUI/GalleryFooterNode.swift +++ b/TelegramUI/GalleryFooterNode.swift @@ -45,7 +45,7 @@ final class GalleryFooterNode: ASDisplayNode { var backgroundHeight: CGFloat = 0.0 if let footerContentNode = self.currentFooterContentNode { - backgroundHeight = footerContentNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, contentInset: thumbnailPanelHeight, transition: transition) + backgroundHeight = footerContentNode.updateLayout(size: layout.size, metrics: layout.metrics, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, contentInset: thumbnailPanelHeight, transition: transition) transition.updateFrame(node: footerContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight), size: CGSize(width: layout.size.width, height: backgroundHeight))) if let removeCurrentFooterContentNode = removeCurrentFooterContentNode { let contentTransition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) diff --git a/TelegramUI/GenerateTextEntities.swift b/TelegramUI/GenerateTextEntities.swift index 97dd1c7dd0..ca352d1e71 100644 --- a/TelegramUI/GenerateTextEntities.swift +++ b/TelegramUI/GenerateTextEntities.swift @@ -111,8 +111,8 @@ private func commitEntity(_ utf16: String.UTF16View, _ type: CurrentEntityType, entityType = .Custom(type: ApplicationSpecificEntityType.Timecode) } - if case .timecode = type, let mediaDuration = mediaDuration, let timecode = parseTimecodeString(String(utf16[range])) { - if timecode <= mediaDuration { + if case .timecode = type { + if let mediaDuration = mediaDuration, let timecode = parseTimecodeString(String(utf16[range])), timecode <= mediaDuration { entities.append(MessageTextEntity(range: indexRange, type: entityType)) } } else { diff --git a/TelegramUI/InstantPageGalleryFooterContentNode.swift b/TelegramUI/InstantPageGalleryFooterContentNode.swift index 4b3b8f029b..fffbe37667 100644 --- a/TelegramUI/InstantPageGalleryFooterContentNode.swift +++ b/TelegramUI/InstantPageGalleryFooterContentNode.swift @@ -99,7 +99,7 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode { self.actionButton.isHidden = shareMedia == nil } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { let width = size.width var panelHeight: CGFloat = 44.0 + bottomInset + contentInset if !self.textNode.isHidden { diff --git a/TelegramUI/ItemListAvatarAndNameItem.swift b/TelegramUI/ItemListAvatarAndNameItem.swift index fd3e7560ec..00cdea2a39 100644 --- a/TelegramUI/ItemListAvatarAndNameItem.swift +++ b/TelegramUI/ItemListAvatarAndNameItem.swift @@ -432,7 +432,7 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite } else if case .generic = item.mode, !servicePeer { let presence = (item.presence as? TelegramUserPresence) ?? TelegramUserPresence(status: .none, lastActivity: 0) let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - let (string, activity) = stringAndActivityForUserPresence(strings: item.strings, dateTimeFormat: item.dateTimeFormat, presence: presence, relativeTo: Int32(timestamp)) + let (string, activity) = stringAndActivityForUserPresence(strings: item.strings, dateTimeFormat: item.dateTimeFormat, presence: presence, relativeTo: Int32(timestamp), expanded: true) statusText = string if activity { statusColor = item.theme.list.itemAccentColor diff --git a/TelegramUI/LegacyAttachmentMenu.swift b/TelegramUI/LegacyAttachmentMenu.swift index c4e8e64e4e..31771f1f4f 100644 --- a/TelegramUI/LegacyAttachmentMenu.swift +++ b/TelegramUI/LegacyAttachmentMenu.swift @@ -6,7 +6,7 @@ import SwiftSignalKit import Postbox import TelegramCore -func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: MessageMediaEditingOptions?, saveEditedPhotos: Bool, allowGrouping: Bool, theme: PresentationTheme, strings: PresentationStrings, parentController: LegacyController, recentlyUsedInlineBots: [Peer], openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, sendMessagesWithSignals: @escaping ([Any]?) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void) -> TGMenuSheetController { +func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: MessageMediaEditingOptions?, saveEditedPhotos: Bool, allowGrouping: Bool, theme: PresentationTheme, strings: PresentationStrings, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, sendMessagesWithSignals: @escaping ([Any]?) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void) -> TGMenuSheetController { let isSecretChat = peer.id.namespace == Namespaces.Peer.SecretChat let controller = TGMenuSheetController(context: parentController.context, dark: false)! @@ -58,6 +58,7 @@ func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: } }; carouselItem.allowCaptions = true + carouselItem.editingContext.setInitialCaption(initialCaption, entities: []) itemViews.append(carouselItem) let galleryItem = TGMenuSheetButtonItemView(title: editing ? strings.Conversation_EditingMessageMediaChange : strings.AttachmentMenu_PhotoOrVideo, type: TGMenuSheetButtonTypeDefault, action: { [weak controller] in diff --git a/TelegramUI/LegacyCamera.swift b/TelegramUI/LegacyCamera.swift index 8c0b917ecf..30a8e19e3f 100644 --- a/TelegramUI/LegacyCamera.swift +++ b/TelegramUI/LegacyCamera.swift @@ -6,7 +6,7 @@ import TelegramCore import Postbox import SwiftSignalKit -func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, sendMessagesWithSignals: @escaping ([Any]?) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }) { +func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, sendMessagesWithSignals: @escaping ([Any]?) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) diff --git a/TelegramUI/LegacyMediaPickers.swift b/TelegramUI/LegacyMediaPickers.swift index 8faf5b13e8..63fbabd967 100644 --- a/TelegramUI/LegacyMediaPickers.swift +++ b/TelegramUI/LegacyMediaPickers.swift @@ -13,7 +13,7 @@ func guessMimeTypeByFileExtension(_ ext: String) -> String { return TGMimeTypeMap.mimeType(forExtension: ext) ?? "application/binary" } -func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: Peer, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, presentWebSearch: (() -> Void)?) { +func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: Peer, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, initialCaption: String, presentWebSearch: (() -> Void)?) { controller.captionsEnabled = captionsEnabled controller.inhibitDocumentCaptions = false controller.suggestionContext = legacySuggestionContext(account: context.account, peerId: peer.id) @@ -26,6 +26,8 @@ func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: controller.shouldStoreAssets = storeCreatedAssets controller.shouldShowFileTipIfNeeded = showFileTooltip controller.requestSearchController = presentWebSearch + + controller.editingContext.setInitialCaption(initialCaption, entities: []) } func legacyAssetPicker(context: AccountContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: Peer?, saveEditedPhotos: Bool, allowGrouping: Bool) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> { diff --git a/TelegramUI/ListMessageSnippetItemNode.swift b/TelegramUI/ListMessageSnippetItemNode.swift index 8edbe0d621..1ce69280bd 100644 --- a/TelegramUI/ListMessageSnippetItemNode.swift +++ b/TelegramUI/ListMessageSnippetItemNode.swift @@ -543,7 +543,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { case .tap, .longTap: if let item = self.item, let url = self.urlAtPoint(location) { if case .longTap = gesture { - item.controllerInteraction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message.id) + item.controllerInteraction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message) } else if url == self.currentPrimaryUrl { if !item.controllerInteraction.openMessage(item.message, .default) { item.controllerInteraction.openUrl(url, false, false) diff --git a/TelegramUI/OverlayPlayerControllerNode.swift b/TelegramUI/OverlayPlayerControllerNode.swift index 9e3457fc63..c118bb2f85 100644 --- a/TelegramUI/OverlayPlayerControllerNode.swift +++ b/TelegramUI/OverlayPlayerControllerNode.swift @@ -73,7 +73,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec }, requestSelectMessagePollOption: { _, _ in }, openAppStorePage: { }, displayMessageTooltip: { _, _, _, _ in - }, seekToTimecode: { _, _ in + }, seekToTimecode: { _, _, _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index d2b18ed493..5c3d11e1a2 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -254,7 +254,7 @@ public class PeerMediaCollectionController: TelegramController { }, requestSelectMessagePollOption: { _, _ in }, openAppStorePage: { }, displayMessageTooltip: { _, _, _, _ in - }, seekToTimecode: { _, _ in + }, seekToTimecode: { _, _, _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/TelegramUI/PresenceStrings.swift b/TelegramUI/PresenceStrings.swift index b4ad6f1174..424434e3ab 100644 --- a/TelegramUI/PresenceStrings.swift +++ b/TelegramUI/PresenceStrings.swift @@ -276,7 +276,7 @@ func stringForRelativeLiveLocationUpdateTimestamp(strings: PresentationStrings, } } -func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, presence: TelegramUserPresence, relativeTo timestamp: Int32) -> (String, Bool) { +func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, presence: TelegramUserPresence, relativeTo timestamp: Int32, expanded: Bool = false) -> (String, Bool) { switch presence.status { case .none: return (strings.LastSeen_Offline, false) @@ -287,7 +287,7 @@ func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeForm let difference = timestamp - statusTimestamp if difference < 60 { return (strings.LastSeen_JustNow, false) - } else if difference < 60 * 60 { + } else if difference < 60 * 60 && !expanded { let minutes = difference / 60 return (strings.LastSeen_MinutesAgo(minutes), false) } else { @@ -307,8 +307,12 @@ func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeForm if dayDifference == 0 || dayDifference == -1 { let day: RelativeTimestampFormatDay if dayDifference == 0 { - let minutes = difference / (60 * 60) - return (strings.LastSeen_HoursAgo(minutes), false) + if expanded { + day = .today + } else { + let minutes = difference / (60 * 60) + return (strings.LastSeen_HoursAgo(minutes), false) + } } else { day = .yesterday } diff --git a/TelegramUI/SecretMediaPreviewFooterContentNode.swift b/TelegramUI/SecretMediaPreviewFooterContentNode.swift index 1902ec9414..4a18fd0a19 100644 --- a/TelegramUI/SecretMediaPreviewFooterContentNode.swift +++ b/TelegramUI/SecretMediaPreviewFooterContentNode.swift @@ -31,7 +31,7 @@ final class SecretMediaPreviewFooterContentNode: GalleryFooterContentNode { } } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { let width = size.width let panelHeight: CGFloat = 44.0 + bottomInset diff --git a/TelegramUI/SecureIdDocumentGalleryFooterContentNode.swift b/TelegramUI/SecureIdDocumentGalleryFooterContentNode.swift index 125a48a6a2..3c77788cf3 100644 --- a/TelegramUI/SecureIdDocumentGalleryFooterContentNode.swift +++ b/TelegramUI/SecureIdDocumentGalleryFooterContentNode.swift @@ -86,7 +86,7 @@ final class SecureIdDocumentGalleryFooterContentNode: GalleryFooterContentNode { } } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { let width = size.width var panelHeight: CGFloat = 44.0 + bottomInset panelHeight += contentInset diff --git a/TelegramUI/SettingsSearchItem.swift b/TelegramUI/SettingsSearchItem.swift index e3f2b4679b..8aab808e68 100644 --- a/TelegramUI/SettingsSearchItem.swift +++ b/TelegramUI/SettingsSearchItem.swift @@ -160,7 +160,6 @@ final class SettingsSearchInteraction { private enum SettingsSearchEntryStableId: Hashable { case result(SettingsSearchableItemId) - case faq(String) } private enum SettingsSearchEntry: Comparable, Identifiable { @@ -302,7 +301,6 @@ private func preparedSettingsSearchContainerRecentTransition(from fromEntries: [ private final class SettingsSearchContainerNode: SearchDisplayControllerContentNode { - private let dimNode: ASDisplayNode private let listNode: ListView private let recentListNode: ListView @@ -323,9 +321,6 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationDataPromise = Promise(self.presentationData) - self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) - self.listNode = ListView() self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.listNode.isHidden = true @@ -338,7 +333,6 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN self.backgroundColor = self.presentationData.theme.chatList.backgroundColor - //self.addSubnode(self.dimNode) self.addSubnode(self.recentListNode) self.addSubnode(self.listNode) @@ -407,7 +401,10 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN var result: [SettingsSearchableItem] = [] for itemId in recentItems { if let searchItem = searchableItemsMap[itemId] { - result.append(searchItem) + if case let .language(id) = searchItem.id, id > 0 { + } else { + result.append(searchItem) + } } } return result @@ -489,12 +486,6 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN self.presentationDataDisposable?.dispose() } - override func didLoad() { - super.didLoad() - - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - } - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { self.listNode.backgroundColor = theme.chatList.backgroundColor self.recentListNode.backgroundColor = theme.chatList.backgroundColor @@ -529,7 +520,6 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN let isSearching = transition.isSearching self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in self?.listNode.isHidden = !isSearching - self?.dimNode.isHidden = isSearching }) } } @@ -563,10 +553,7 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) - - let topInset = navigationBarHeight - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))) - + var duration: Double = 0.0 var curve: UInt = 0 switch transition { @@ -723,7 +710,6 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { } override func queryUpdated(_ query: String) { - //self.containerNode.searchTextUpdated(text: query) } override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { diff --git a/TelegramUI/SettingsSearchableItems.swift b/TelegramUI/SettingsSearchableItems.swift index 3685697bc1..237b5145a3 100644 --- a/TelegramUI/SettingsSearchableItems.swift +++ b/TelegramUI/SettingsSearchableItems.swift @@ -747,17 +747,22 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList } let localizationPreferencesKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.localizationListState])) - let localizations = context.account.postbox.combinedView(keys: [localizationPreferencesKey]) - |> map { view -> [LocalizationInfo] in + let localizations = combineLatest(context.account.postbox.combinedView(keys: [localizationPreferencesKey]), context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.localizationSettings])) + |> map { view, sharedData -> [LocalizationInfo] in if let localizationListState = (view.views[localizationPreferencesKey] as? PreferencesView)?.values[PreferencesKeys.localizationListState] as? LocalizationListState, !localizationListState.availableOfficialLocalizations.isEmpty { var existingIds = Set() let availableSavedLocalizations = localizationListState.availableSavedLocalizations.filter({ info in !localizationListState.availableOfficialLocalizations.contains(where: { $0.languageCode == info.languageCode }) }) + var activeLanguageCode: String? + if let localizationSettings = sharedData.entries[SharedDataKeys.localizationSettings] as? LocalizationSettings { + activeLanguageCode = localizationSettings.primaryComponent.languageCode + } + var localizationItems: [LocalizationInfo] = [] if !availableSavedLocalizations.isEmpty { for info in availableSavedLocalizations { - if existingIds.contains(info.languageCode) { + if existingIds.contains(info.languageCode) || info.languageCode == activeLanguageCode { continue } existingIds.insert(info.languageCode) @@ -765,7 +770,7 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList } } for info in localizationListState.availableOfficialLocalizations { - if existingIds.contains(info.languageCode) { + if existingIds.contains(info.languageCode) || info.languageCode == activeLanguageCode { continue } existingIds.insert(info.languageCode) diff --git a/TelegramUI/TelegramRootController.swift b/TelegramUI/TelegramRootController.swift index 12727622ec..a4fb707bb0 100644 --- a/TelegramUI/TelegramRootController.swift +++ b/TelegramUI/TelegramRootController.swift @@ -4,6 +4,8 @@ import Postbox import TelegramCore import SwiftSignalKit +import TelegramUIPrivateModule + public final class TelegramRootController: NavigationController { private let context: AccountContext @@ -81,8 +83,52 @@ public final class TelegramRootController: NavigationController { self.accountSettingsController = accountSettingsController self.rootTabController = tabBarController self.pushViewController(tabBarController, animated: false) + + + + +// guard let controller = self.viewControllers.last as? ViewController else { +// return +// } +// +// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) { +// let wrapperNode = ASDisplayNode() +// let bounds = controller.displayNode.bounds +// wrapperNode.frame = bounds +// wrapperNode.backgroundColor = .gray +// //controller.displayNode.addSubnode(wrapperNode) +// +// let label = TGMarqLabel(frame: CGRect()) +// label.textColor = .white +// label.font = Font.regular(28.0) +// label.scrollDuration = 15.0 +// label.fadeLength = 25.0 +// label.trailingBuffer = 60.0 +// label.animationDelay = 2.0 +// label.text = "Lorem ipsum dolor sir amet, consecteur" +// label.sizeToFit() +// label.frame = CGRect(x: bounds.width / 2.0 - 100.0, y: 100.0, width: 200.0, height: label.frame.height) +// //wrapperNode.view.addSubview(label) +// +// let data = testLineChartData() +// let node = LineChartContainerNode(data: data) +// node.frame = CGRect(x: 0.0, y: 100.0, width: bounds.width, height: 280.0) +// node.updateLayout(size: node.frame.size) +// wrapperNode.addSubnode(node) +// +// self.wNode = wrapperNode +// +// let gesture = UITapGestureRecognizer(target: self, action: #selector(self.closeIt)) +// wrapperNode.view.addGestureRecognizer(gesture) +// } } + @objc func closeIt() { + self.wNode?.removeFromSupernode() + } + + private var wNode: ASDisplayNode? + public func updateRootControllers(showCallsTab: Bool) { guard let rootTabController = self.rootTabController else { return diff --git a/TelegramUI/WebSearchGalleryFooterContentNode.swift b/TelegramUI/WebSearchGalleryFooterContentNode.swift index 418474405f..a97e8b13d3 100644 --- a/TelegramUI/WebSearchGalleryFooterContentNode.swift +++ b/TelegramUI/WebSearchGalleryFooterContentNode.swift @@ -40,22 +40,11 @@ final class WebSearchGalleryFooterContentNode: GalleryFooterContentNode { } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { let width = size.width let panelSize: CGFloat = 49.0 var panelHeight: CGFloat = panelSize + bottomInset panelHeight += contentInset - var textFrame = CGRect() -// if !self.textNode.isHidden { -// let sideInset: CGFloat = 8.0 + leftInset -// let topInset: CGFloat = 8.0 -// let textBottomInset: CGFloat = 8.0 -// let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)) -// panelHeight += textSize.height + topInset + textBottomInset -// textFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: textSize) -// } - - //self.textNode.frame = textFrame self.cancelButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - panelSize), size: CGSize(width: panelSize, height: panelSize)) self.sendButton.frame = CGRect(origin: CGPoint(x: width - panelSize - rightInset, y: panelHeight - bottomInset - panelSize), size: CGSize(width: panelSize, height: panelSize))