diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index 918a08369a..00bbd81a08 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -286,7 +286,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS public let inputModeView: ComponentHostView - private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool)? + private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, textFieldMaxHeight: CGFloat, availableHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool)? private var currentInputMode: AttachmentTextInputMode = .text private var currentAdditionalInputHeight: CGFloat = 0.0 private var currentSafeAreaInset: UIEdgeInsets = .zero @@ -626,7 +626,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS guard let presentationInterfaceState = self.presentationInterfaceState else { return 0.0 } - return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), isMediaInputExpanded: false) + return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), textFieldMaxHeight: size.height, availableHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), isMediaInputExpanded: false) } @objc(updateContainerLayoutSize:safeAreaInset:bottomInset:keyboardHeight:animated:) @@ -637,7 +637,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS guard let presentationInterfaceState = self.presentationInterfaceState else { return 0.0 } - return self.updateLayout(width: size.width, leftInset: 0.0, rightInset: 0.0, bottomInset: 0.0, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), isMediaInputExpanded: false) + return self.updateLayout(width: size.width, leftInset: 0.0, rightInset: 0.0, bottomInset: safeAreaInset.bottom, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), textFieldMaxHeight: size.height, availableHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), isMediaInputExpanded: false) } public func setCaption(_ caption: NSAttributedString?) { @@ -873,13 +873,17 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { - self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: 0.0, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: 0.0, additionalSideInsets: additionalSideInsets, textFieldMaxHeight: maxHeight, availableHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) } public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: keyboardHeight, additionalSideInsets: additionalSideInsets, textFieldMaxHeight: maxHeight, availableHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + } + + public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, textFieldMaxHeight: CGFloat, availableHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { let hadLayout = self.validLayout != nil let previousLayout = self.validLayout - self.validLayout = (width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: keyboardHeight, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, metrics: metrics, isSecondary: isSecondary) + self.validLayout = (width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: keyboardHeight, additionalSideInsets: additionalSideInsets, textFieldMaxHeight: textFieldMaxHeight, availableHeight: availableHeight, metrics: metrics, isSecondary: isSecondary) let previousAdditionalSideInsets = previousLayout?.additionalSideInsets let leftInset = leftInset + 8.0 @@ -1006,8 +1010,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } - let isLandscape = width > maxHeight - let deviceMetrics = DeviceMetrics(screenSize: CGSize(width: width, height: maxHeight), scale: UIScreen.main.scale, statusBarHeight: 0.0, onScreenNavigationHeight: nil) + let isLandscape = width > availableHeight + let deviceMetrics = DeviceMetrics(screenSize: CGSize(width: width, height: availableHeight), scale: UIScreen.main.scale, statusBarHeight: 0.0, onScreenNavigationHeight: nil) let standardInputHeight = deviceMetrics.standardInputHeight(inLandscape: isLandscape) if keyboardHeight > 0.0 || !self.isFocused { self.isTransitioningToTextKeyboard = false @@ -1020,7 +1024,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let minimalHeight: CGFloat = 14.0 + textFieldMinHeight let baseWidth = width - leftInset - rightInset - let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: baseWidth, maxHeight: maxHeight, metrics: metrics) + let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: baseWidth, maxHeight: textFieldMaxHeight, metrics: metrics) var panelContentHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) var inputHasText = false @@ -1103,10 +1107,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS width: width, leftInset: 0.0, rightInset: 0.0, - bottomInset: 0.0, + bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: 0.0, - maximumHeight: maxHeight, + maximumHeight: availableHeight, inputPanelHeight: 0.0, transition: .immediate, interfaceState: interfaceState, @@ -1129,9 +1133,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let targetOriginY: CGFloat if self.usesContainerLayout { if dismissingInputHeight > 0.0 { - targetOriginY = maxHeight - dismissingInputHeight + targetOriginY = availableHeight - dismissingInputHeight } else { - targetOriginY = maxHeight + targetOriginY = availableHeight } } else if dismissingInputHeight > 0.0 { targetOriginY = inputPanelHeight @@ -1164,7 +1168,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS totalHeight += retainedInputHeight } - let isLandscapePhone = width > maxHeight && UIDevice.current.userInterfaceIdiom != .pad + let isLandscapePhone = width > availableHeight && UIDevice.current.userInterfaceIdiom != .pad let collapsedCaptionTopInset = self.currentSafeAreaInset.top + 48.0 let expandedCaptionTopInset = self.currentSafeAreaInset.top + 8.0 @@ -1172,10 +1176,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS var inputMediaFrame = CGRect(origin: CGPoint(x: 0.0, y: inputPanelHeight), size: CGSize(width: width, height: inputMediaHeight)) if self.usesContainerLayout { if isLandscapePhone { - panelOriginY = maxHeight + 16.0 - inputMediaFrame.origin.y = maxHeight + 16.0 + panelOriginY = availableHeight + 16.0 + inputMediaFrame.origin.y = availableHeight + 16.0 } else if case .emoji = self.currentInputMode { - inputMediaFrame.origin.y = maxHeight - inputMediaHeight + inputMediaFrame.origin.y = availableHeight - inputMediaHeight if self.currentIsCaptionAbove { panelOriginY = expandedCaptionTopInset } else { @@ -1186,9 +1190,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS panelOriginY = (retainedInputHeight > 0.0 ? expandedCaptionTopInset : collapsedCaptionTopInset) } else { let bottomOffset = max(self.currentContainerBottomInset, retainedInputHeight) - panelOriginY = maxHeight - inputPanelHeight - bottomOffset + panelOriginY = availableHeight - inputPanelHeight - bottomOffset } - inputMediaFrame.origin.y = maxHeight + inputMediaFrame.origin.y = availableHeight } } @@ -1897,14 +1901,14 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let leftInset = layout.leftInset + 8.0 let rightInset = layout.rightInset + 8.0 - let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: layout.width - leftInset - rightInset - layout.additionalSideInsets.right, maxHeight: layout.maxHeight, metrics: layout.metrics) - let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: layout.metrics) + (self.glass ? 11.0 : 0.0) - let totalHeight = panelHeight + self.currentAdditionalInputHeight + let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: layout.width - leftInset - rightInset - layout.additionalSideInsets.right, maxHeight: layout.textFieldMaxHeight, metrics: layout.metrics) + let panelContentHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: layout.metrics) + let totalHeight = panelContentHeight + (self.glass ? 11.0 : 0.0) + self.currentAdditionalInputHeight if self.currentHeight != totalHeight { self.updateHeight(animated) self.heightUpdated?(animated) } - return panelHeight + return panelContentHeight } else { return nil } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index e7564c0603..21c109bb68 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -2475,6 +2475,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog let defaultPanelSideInset: CGFloat = glassPanelSideInset let panelSideInset: CGFloat = (isSelecting ? textPanelSideInset : defaultPanelSideInset) + layout.safeInsets.left var textPanelHeight: CGFloat = 0.0 + var visualTextPanelHeight: CGFloat = 0.0 var textPanelWidth: CGFloat = 0.0 if let textInputPanelNode = self.textInputPanelNode { textInputPanelNode.isUserInteractionEnabled = isSelecting @@ -2483,7 +2484,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog if textInputPanelNode.frame.width.isZero { panelTransition = .immediate } - let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, bottomInset: 0.0, keyboardHeight: layout.inputHeight ?? 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false) + let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, bottomInset: layout.safeInsets.bottom, keyboardHeight: layout.inputHeight ?? 0.0, additionalSideInsets: UIEdgeInsets(), textFieldMaxHeight: layout.size.height / 2.0, availableHeight: layout.size.height, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false) let panelFrame = CGRect(x: 0.0, y: topAccessoryHeight, width: layout.size.width, height: panelHeight) if textInputPanelNode.frame.width.isZero { textInputPanelNode.frame = panelFrame @@ -2491,8 +2492,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog transition.updateFrame(node: textInputPanelNode, frame: panelFrame) if panelFrame.height > 0.0 { textPanelHeight = panelFrame.height + visualTextPanelHeight = max(0.0, textPanelHeight - textInputPanelNode.additionalInputHeight) } else { textPanelHeight = self.panelStyle == .glass ? 40.0 : 45.0 + visualTextPanelHeight = textPanelHeight } textPanelWidth = layout.size.width - panelSideInset * 2.0 } @@ -2545,7 +2548,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog buttonsPanelWidth = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - panelSideInset * 2.0 } - let basePanelHeight = isSelecting ? max(0.0, textPanelHeight - 11.0) : glassPanelHeight + let basePanelHeight = isSelecting ? max(0.0, visualTextPanelHeight - 11.0) : glassPanelHeight var panelSize = CGSize(width: isSelecting ? textPanelWidth : buttonsPanelWidth, height: basePanelHeight + topAccessoryHeight) if !isSelecting && (buttons.count == 1 || hideButtons) && topAccessoryHeight > 0.0 { panelSize.height = topAccessoryHeight @@ -2605,6 +2608,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog self.backgroundView?.isHidden = true } + let effectiveBottomInset: CGFloat + if let textInputPanelNode = self.textInputPanelNode, textInputPanelNode.additionalInputHeight > 0.0 { + effectiveBottomInset = 0.0 + } else { + effectiveBottomInset = insets.bottom + } + if isAnyButtonVisible { var height: CGFloat if layout.intrinsicInsets.bottom > 0.0 && (layout.inputHeight ?? 0.0).isZero { @@ -2632,7 +2642,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: height)) } else if isSelecting { - containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: textPanelHeight + insets.bottom + topAccessoryHeight)) + containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: textPanelHeight + effectiveBottomInset + topAccessoryHeight)) } else { containerFrame = bounds } @@ -2661,8 +2671,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog if let textInputPanelNode = self.textInputPanelNode { textInputPanelNode.isUserInteractionEnabled = true if case .glass = self.panelStyle { - var textInputPanelHeight = textInputPanelNode.frame.height - if textInputPanelHeight < 1.0 { + var textInputPanelHeight = visualTextPanelHeight + if textInputPanelNode.frame.height < 1.0 { textInputPanelHeight = 51.0 } let heightDelta = glassPanelHeight - textInputPanelHeight @@ -2699,8 +2709,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog if let textInputPanelNode = self.textInputPanelNode { textInputPanelNode.isUserInteractionEnabled = false if case .glass = self.panelStyle { - var textInputPanelHeight = textInputPanelNode.frame.height - if textInputPanelHeight < 1.0 { + var textInputPanelHeight = visualTextPanelHeight + if textInputPanelNode.frame.height < 1.0 { textInputPanelHeight = 51.0 } let heightDelta = glassPanelHeight - textInputPanelHeight diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 52b8ba2e16..9696097f4a 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -2070,7 +2070,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { var defaultFoundRemoteMessagesSignal: Signal<([FoundRemoteMessages], Bool), NoError> = .single(([FoundRemoteMessages(messages: [], readCounters: [:], threadsData: [:], totalCount: 0)], false)) if key == .globalPosts, let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_load_empty_global_posts"] as? Double, value != 0.0 { - let searchSignal = context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: nil), tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: "", state: nil, limit: 50) + let searchSignal = context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: nil), groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: "", state: nil, limit: 50) |> map { resultData -> ChatListSearchMessagesResult in let (result, updatedState) = resultData @@ -2606,28 +2606,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } let searchLocations: [SearchMessagesLocation] if key == .globalPosts { - searchLocations = [SearchMessagesLocation.general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil, folderId: nil)] + searchLocations = [SearchMessagesLocation.general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil)] } else if let options { if case let .forum(peerId) = location { - searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1), .general(scope: .everywhere, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: nil)] + searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1), .general(scope: .everywhere, groupId: nil, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: nil)] } else if let (peerId, _, _) = options.peer { searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1)] } else { - if case let .chatList(groupId) = location, case .archive = groupId { - searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)] + if case let .chatList(groupId) = location { + searchLocations = [.general(scope: searchScope, groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: options.folder?.0)] } else { - searchLocations = [.general(scope: searchScope, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: options.folder?.0)] + searchLocations = [.general(scope: searchScope, groupId: nil, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: options.folder?.0)] } } } else { if case .channels = key { - searchLocations = [.general(scope: .channels, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] + searchLocations = [.general(scope: .channels, groupId: nil, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] } else if case let .forum(peerId) = location { - searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: nil, maxDate: nil), .general(scope: .everywhere, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] - } else if case let .chatList(groupId) = location, case .archive = groupId { - searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil)] + searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: nil, maxDate: nil), .general(scope: .everywhere, groupId: nil, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] + } else if case let .chatList(groupId) = location { + searchLocations = [.general(scope: searchScope, groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] } else { - searchLocations = [.general(scope: searchScope, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] + searchLocations = [.general(scope: searchScope, groupId: nil, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] } } @@ -2726,7 +2726,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let searchSignals: [Signal<(SearchMessagesResult, SearchMessagesState), NoError>] if key == .globalPosts { - searchSignals = [context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: finalQuery, state: nil, limit: 50)] + searchSignals = [context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: finalQuery, state: nil, limit: 50)] } else { searchSignals = searchLocations.map { searchLocation in return context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: nil, limit: 50) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift index c9869932c8..7bb94a7882 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift @@ -54,7 +54,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { if self.publicPosts { search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: nil) } else { - search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: self.query, state: nil) + search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: self.query, state: nil) } self.isSearchingPromise.set(true) @@ -102,7 +102,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { if self.publicPosts { search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: self.currentSearchState) } else { - search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: self.query, state: currentSearchState) + search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: self.query, state: currentSearchState) } self.historyViewDisposable?.dispose() diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index 08f3c04323..1621dfc9f2 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -498,42 +498,44 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio guard let node = node as? ContextReferenceContentNode, let controller = getControllerImpl?(), let invite = invite else { return } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - var items: [ContextMenuItem] = [] - - items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) - }, action: { _, f in - f(.dismissWithoutContent) - - dismissTooltipsImpl?() - - UIPasteboard.general.string = invite.link - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) - }))) - items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) - }, action: { _, f in - f(.dismissWithoutContent) - - let _ = (context.account.postbox.loadedPeerWithId(peerId) - |> deliverOnMainQueue).start(next: { peer in - let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - isGroup = false + let creatorIsBot: Signal + if let adminPeer = admin?.peer.peer as? TelegramUser { + creatorIsBot = .single(adminPeer.botInfo != nil) + } else if case let .link(_, _, _, _, _, adminId, _, _, _, _, _, _, _) = invite, adminId.toInt64() != 0 { + creatorIsBot = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: adminId)) + |> map { peer -> Bool in + if let peer, case let .user(user) = peer, user.botInfo != nil { + return true } else { - isGroup = true + return false } - presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), nil) - }) - }))) + } + } else { + creatorIsBot = .single(false) + } - if case let .link(_, _, _, _, _, adminId, _, _, _, _, _, _, _) = invite, adminId.toInt64() != 0 { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) + let _ = (creatorIsBot + |> take(1) + |> deliverOnMainQueue).start(next: { creatorIsBot in + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) + + dismissTooltipsImpl?() + + UIPasteboard.general.string = invite.link + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + }))) + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) }, action: { _, f in f(.dismissWithoutContent) @@ -545,62 +547,81 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio } else { isGroup = true } - - let controller = ActionSheetController(presentationData: presentationData) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } - controller.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text), - ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { - dismissAction() - - var revoke = false - updateState { state in - if !state.revokingPrivateLink { - revoke = true - var updatedState = state - updatedState.revokingPrivateLink = true - return updatedState - } else { - return state - } - } - if revoke, let inviteLink = invite.link { - revokeLinkDisposable.set((context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: inviteLink) |> deliverOnMainQueue).start(next: { result in - updateState { state in - var updatedState = state - updatedState.revokingPrivateLink = false - return updatedState - } - if let result = result { - switch result { - case let .update(newInvite): - invitesContext.remove(newInvite) - revokedInvitesContext.add(newInvite) - case let .replace(previousInvite, newInvite): - revokedInvitesContext.add(previousInvite) - invitesContext.remove(previousInvite) - invitesContext.add(newInvite) - } - } - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLink_InviteLinkRevoked), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) - })) - } - }) - ]), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), nil) }) }))) - } - let contextController = makeContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - presentInGlobalOverlayImpl?(contextController) + if case let .link(_, _, _, _, _, adminId, _, _, _, _, _, _, _) = invite, adminId.toInt64() != 0, !creatorIsBot { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) + }, action: { _, f in + f(.dismissWithoutContent) + + let _ = (context.account.postbox.loadedPeerWithId(peerId) + |> deliverOnMainQueue).start(next: { peer in + let isGroup: Bool + if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + isGroup = false + } else { + isGroup = true + } + + let controller = ActionSheetController(presentationData: presentationData) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + controller.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text), + ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { + dismissAction() + + var revoke = false + updateState { state in + if !state.revokingPrivateLink { + revoke = true + var updatedState = state + updatedState.revokingPrivateLink = true + return updatedState + } else { + return state + } + } + if revoke, let inviteLink = invite.link { + revokeLinkDisposable.set((context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: inviteLink) |> deliverOnMainQueue).start(next: { result in + updateState { state in + var updatedState = state + updatedState.revokingPrivateLink = false + return updatedState + } + if let result = result { + switch result { + case let .update(newInvite): + invitesContext.remove(newInvite) + revokedInvitesContext.add(newInvite) + case let .replace(previousInvite, newInvite): + revokedInvitesContext.add(previousInvite) + invitesContext.remove(previousInvite) + invitesContext.add(newInvite) + } + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLink_InviteLinkRevoked), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + })) + } + }) + ]), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }) + }))) + } + + let contextController = makeContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + presentInGlobalOverlayImpl?(contextController) + }) }, createLink: { let controller = inviteLinkEditController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: nil, completion: { invite in if let invite = invite { diff --git a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m index d30009fbb9..bc020a1f1d 100644 --- a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m +++ b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m @@ -265,7 +265,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _innerCircleView.center = centerPoint; _outerCircleView.center = centerPoint; _decoration.center = centerPoint; - _innerIconWrapperView.center = CGPointMake(_decoration.frame.size.width / 2.0f, _decoration.frame.size.height / 2.0f); + _innerIconWrapperView.center = CGPointMake(CGRectGetMidX(_decoration.bounds), CGRectGetMidY(_decoration.bounds)); _lockPanelWrapperView.frame = CGRectMake(floor(centerPoint.x - _lockPanelWrapperView.frame.size.width / 2.0f), floor(centerPoint.y - 122.0f - _lockPanelWrapperView.frame.size.height / 2.0f), _lockPanelWrapperView.frame.size.width, _lockPanelWrapperView.frame.size.height); @@ -918,6 +918,8 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _currentTranslation = MIN(0.0, _currentTranslation * 0.7f + _targetTranslation * 0.3f); _cancelTranslation = MIN(0.0, _cancelTranslation * 0.7f + _cancelTargetTranslation * 0.3f); + [self updateOverlay]; + if (t > _animationStartTime) { CGFloat outerScale = outerCircleMinScale + _currentLevel * (1.0f - outerCircleMinScale); CGAffineTransform translation = CGAffineTransformMakeTranslation(0, _currentTranslation); diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index 036bdd4a28..398eee00dd 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -265,10 +265,19 @@ private enum ChannelMembersEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ChannelMembersControllerArguments switch self { - case let .hideMembers(text, disabledReason, isInteractive, value): - return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: isInteractive, enabled: true, displayLocked: !value && disabledReason != nil, sectionId: self.section, style: .blocks, updated: { value in + case let .hideMembers(text, disabledReason, isInteractive, currentValue): + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: currentValue, enableInteractiveChanges: isInteractive, enabled: true, displayLocked: !currentValue && disabledReason != nil, sectionId: self.section, style: .blocks, updated: { value in if let disabledReason { - arguments.displayHideMembersTip(disabledReason) + switch disabledReason { + case .notEnoughMembers: + if currentValue && !value { + arguments.updateHideMembers(value) + } else { + arguments.displayHideMembersTip(disabledReason) + } + case .notAllowed: + arguments.displayHideMembersTip(disabledReason) + } } else { arguments.updateHideMembers(value) } diff --git a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift index 7df68a0898..18a6c194a2 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift @@ -188,6 +188,9 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, } } + if let webpagePreviewAttribute, webpagePreviewAttribute.leadingPreview { + flags |= Int32(1 << 16) + } if let _ = invertMediaAttribute { flags |= Int32(1 << 16) } diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index b4bf907713..c79d57b6d2 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -33,7 +33,7 @@ public struct StandaloneSendMessagesError { public var peerId: PeerId public var reason: PendingMessageFailureReason? - init( + public init( peerId: PeerId, reason: PendingMessageFailureReason? ) { diff --git a/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift b/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift index d90c362ca2..d471cf313f 100644 --- a/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift +++ b/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift @@ -12,6 +12,9 @@ internal func _internal_updateIsPremiumRequiredToContact(account: Account, peerI var inputUsers: [Api.InputUser] = [] var ids: [PeerId] = [] for id in peerIds { + guard id != account.peerId else { + continue + } if let peer = transaction.getPeer(id), let inputUser = apiInputUser(peer) { if peer.isPremium { if let cachedData = transaction.getPeerCachedData(peerId: id) as? CachedUserData { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index a793b7d779..0cfb3c20bc 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -1688,6 +1688,9 @@ public extension TelegramEngine.EngineData.Item { } func _extract(data: TelegramEngine.EngineData, views: [PostboxViewKey: PostboxView]) -> Any { + guard self.id != data.accountPeerId else { + return false + } guard let basicPeerView = views[.basicPeer(data.accountPeerId)] as? BasicPeerView else { assertionFailure() return false diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index 65a2d3a3d2..63967e2685 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -474,10 +474,13 @@ public extension EngineChatList.RelativePosition { } } -private func calculateIsPremiumRequiredToMessage(isPremium: Bool, targetPeer: Peer, cachedIsPremiumRequired: Bool) -> Bool { +private func calculateIsPremiumRequiredToMessage(accountPeerId: PeerId?, isPremium: Bool, targetPeer: Peer, cachedIsPremiumRequired: Bool) -> Bool { if isPremium { return false } + if let accountPeerId, targetPeer.id == accountPeerId { + return false + } guard let targetPeer = targetPeer as? TelegramUser else { return false } @@ -488,7 +491,7 @@ private func calculateIsPremiumRequiredToMessage(isPremium: Bool, targetPeer: Pe } extension EngineChatList.Item { - convenience init?(_ entry: ChatListEntry, isPremium: Bool, displayAsTopicList: Bool) { + convenience init?(_ entry: ChatListEntry, accountPeerId: PeerId?, isPremium: Bool, displayAsTopicList: Bool) { switch entry { case let .MessageEntry(entryData): let index = entryData.index @@ -507,7 +510,7 @@ extension EngineChatList.Item { var isPremiumRequiredToMessage = false if let targetPeer = renderedPeer.chatMainPeer, let extractedData = entryData.extractedCachedData?.base as? ExtractedChatListItemCachedData { - isPremiumRequiredToMessage = calculateIsPremiumRequiredToMessage(isPremium: isPremium, targetPeer: targetPeer, cachedIsPremiumRequired: extractedData.isPremiumRequiredToMessage) + isPremiumRequiredToMessage = calculateIsPremiumRequiredToMessage(accountPeerId: accountPeerId, isPremium: isPremium, targetPeer: targetPeer, cachedIsPremiumRequired: extractedData.isPremiumRequiredToMessage) } var draft: EngineChatList.Draft? @@ -632,7 +635,7 @@ extension EngineChatList.AdditionalItem.PromoInfo { extension EngineChatList.AdditionalItem { convenience init?(_ entry: ChatListAdditionalItemEntry) { - guard let item = EngineChatList.Item(entry.entry, isPremium: false, displayAsTopicList: false) else { + guard let item = EngineChatList.Item(entry.entry, accountPeerId: nil, isPremium: false, displayAsTopicList: false) else { return nil } guard let promoInfo = (entry.info as? PromoChatListItem).flatMap(EngineChatList.AdditionalItem.PromoInfo.init) else { @@ -657,7 +660,7 @@ public extension EngineChatList { loop: for entry in view.entries { switch entry { case .MessageEntry: - if let item = EngineChatList.Item(entry, isPremium: isPremium, displayAsTopicList: entry.index.messageIndex.id.peerId == accountPeerId ? displaySavedMessagesAsTopicList : false) { + if let item = EngineChatList.Item(entry, accountPeerId: accountPeerId, isPremium: isPremium, displayAsTopicList: entry.index.messageIndex.id.peerId == accountPeerId ? displaySavedMessagesAsTopicList : false) { items.append(item) } case .HoleEntry: diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift index f385472317..f67efe8a1c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift @@ -6,8 +6,7 @@ import MtProtoKit public enum SearchMessagesLocation: Equatable { - case general(scope: TelegramSearchPeersScope, tags: MessageTags?, minDate: Int32?, maxDate: Int32?, folderId: Int32?) - case group(groupId: PeerGroupId, tags: MessageTags?, minDate: Int32?, maxDate: Int32?) + case general(scope: TelegramSearchPeersScope, groupId: PeerGroupId?, tags: MessageTags?, minDate: Int32?, maxDate: Int32?, folderId: Int32?) case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, reactions: [MessageReaction.Reaction]?, threadId: Int64?, minDate: Int32?, maxDate: Int32?) case sentMedia(tags: MessageTags?) } @@ -471,17 +470,17 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation } return combineLatest(peerMessages, additionalPeerMessages) } - case let .general(_, tags, minDate, maxDate, _), let .group(_, tags, minDate, maxDate): + case let .general(_, groupId, tags, minDate, maxDate, _): var flags: Int32 = 0 let folderId: Int32? - if case let .group(groupId, _, _, _) = location { + if let groupId { folderId = groupId.rawValue flags |= (1 << 0) } else { folderId = nil } - if case let .general(scope, _, _, _, _) = location, case let .globalPosts(allowPaidStars) = scope { + if case let .general(scope, _, _, _, _, _) = location, case let .globalPosts(allowPaidStars) = scope { remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in var lowerBound: MessageIndex? if let state = state, let message = state.main.messages.last { @@ -508,7 +507,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation } } } else { - if case let .general(scope, _, _, _, _) = location { + if case let .general(scope, _, _, _, _, _) = location { switch scope { case .everywhere: break @@ -567,9 +566,9 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation if state?.additional == nil { switch location { - case let .general(_, tags, minDate, maxDate, _), let .group(_, tags, minDate, maxDate): + case let .general(_, _, tags, minDate, maxDate, _): let secretMessages: [Message] - if case let .general(scope, _, _, _, _) = location, case .channels = scope { + if case let .general(scope, _, _, _, _, _) = location, case .channels = scope { secretMessages = [] } else { secretMessages = transaction.searchMessages(peerId: nil, query: query, tags: tags) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 8a1236f933..534caa98a1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -1591,7 +1591,7 @@ public extension TelegramEngine { } public func subscribeIsPremiumRequiredForMessaging(id: EnginePeer.Id) -> Signal { - if id.namespace != Namespaces.Peer.CloudUser { + if id == self.account.peerId || id.namespace != Namespaces.Peer.CloudUser { return .single(false) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 5f1a6d6d8a..fd6a7c9b32 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -302,7 +302,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee let unofficialSecurityRisk = (userFullFlags2 & (1 << 26)) != 0 var flags: CachedUserFlags = previous.flags - if premiumRequired { + if premiumRequired && peerId != accountPeerId { flags.insert(.premiumRequired) } else { flags.remove(.premiumRequired) diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift index e387d05658..4c569cd3eb 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift @@ -756,7 +756,7 @@ public func makeAttachmentFileControllerImpl( case .audio: recentDocuments = .single(nil) |> then( - context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: [.music], minDate: nil, maxDate: nil, folderId: nil), query: "", state: nil) + context.engine.messages.searchMessages(location: .general(scope: .everywhere, groupId: nil, tags: [.music], minDate: nil, maxDate: nil, folderId: nil), query: "", state: nil) |> map { result -> [Message]? in return result.0.messages } diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift index 2e1463429e..6784fce307 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift @@ -530,7 +530,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon case .audio: shared = .single(nil) |> then( - context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: [.music], minDate: nil, maxDate: nil, folderId: nil), query: query, state: nil) + context.engine.messages.searchMessages(location: .general(scope: .everywhere, groupId: nil, tags: [.music], minDate: nil, maxDate: nil, folderId: nil), query: query, state: nil) |> delay(0.6, queue: Queue.mainQueue()) |> map { result -> [Message]? in return result.0.messages.filter { !$0.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) } diff --git a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift index 3899788a34..3582eae61c 100644 --- a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift +++ b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift @@ -21,19 +21,22 @@ public final class MediaPlaybackHeaderPanelComponent: Component { public let strings: PresentationStrings public let data: GlobalControlPanelsContext.MediaPlayback public let controller: () -> ViewController? + public let shouldPerformAction: ((@escaping () -> Void) -> Void)? public init( context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, data: GlobalControlPanelsContext.MediaPlayback, - controller: @escaping () -> ViewController? + controller: @escaping () -> ViewController?, + shouldPerformAction: ((@escaping () -> Void) -> Void)? = nil ) { self.context = context self.theme = theme self.strings = strings self.data = data self.controller = controller + self.shouldPerformAction = shouldPerformAction } public static func ==(lhs: MediaPlaybackHeaderPanelComponent, rhs: MediaPlaybackHeaderPanelComponent) -> Bool { @@ -71,6 +74,17 @@ public final class MediaPlaybackHeaderPanelComponent: Component { deinit { self.playlistPreloadDisposable?.dispose() } + + private func performAction(_ action: @escaping () -> Void) { + guard let component = self.component else { + return + } + if let shouldPerformAction = component.shouldPerformAction { + shouldPerformAction(action) + } else { + action() + } + } func update(component: MediaPlaybackHeaderPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let themeUpdated = self.component?.theme !== component.theme @@ -220,102 +234,110 @@ public final class MediaPlaybackHeaderPanelComponent: Component { }) } panel.togglePlayPause = { [weak self] in - guard let self, let component = self.component else { - return - } - component.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: component.data.kind) + self?.performAction({ [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: component.data.kind) + }) } panel.playPrevious = { [weak self] in - guard let self, let component = self.component else { - return - } - component.context.sharedContext.mediaManager.playlistControl(.next, type: component.data.kind) + self?.performAction({ [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.playlistControl(.next, type: component.data.kind) + }) } panel.playNext = { [weak self] in - guard let self, let component = self.component else { - return - } - component.context.sharedContext.mediaManager.playlistControl(.previous, type: component.data.kind) + self?.performAction({ [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.playlistControl(.previous, type: component.data.kind) + }) } panel.tapAction = { [weak self] in - guard let self, let component = self.component, let controller = component.controller(), let navigationController = controller.navigationController as? NavigationController else { - return - } - - if let id = component.data.item.id as? PeerMessagesMediaPlaylistItemId, let playlistLocation = component.data.playlistLocation as? PeerMessagesPlaylistLocation { - if case .music = component.data.kind { - switch playlistLocation { - case .custom, .savedMusic: - let controllerContext: AccountContext - if component.data.account.id == component.context.account.id { - controllerContext = component.context - } else { - controllerContext = component.context.sharedContext.makeTempAccountContext(account: component.data.account) - } - let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: playlistLocation, parentNavigationController: navigationController) - self.window?.endEditing(true) - playerController.navigationPresentation = .flatModal - controller.push(playerController) - case let .messages(chatLocation, _, _): - let signal = component.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId)), count: 60, highlight: true, setupReply: false), id: 0), context: component.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic(value: nil), tag: .tag(MessageTags.music)) - - var cancelImpl: (() -> Void)? - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - let progressSignal = Signal { [weak self] subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - self?.component?.controller()?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = MetaDisposable() - var progressStarted = false - self.playlistPreloadDisposable?.dispose() - self.playlistPreloadDisposable = (signal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - |> deliverOnMainQueue).start(next: { [weak self] index in - guard let self, let component = self.component else { - return - } - if let _ = index.0 { - let controllerContext: AccountContext - if component.data.account.id == component.context.account.id { - controllerContext = component.context - } else { - controllerContext = component.context.sharedContext.makeTempAccountContext(account: component.data.account) - } - let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: nil, parentNavigationController: navigationController) - self.window?.endEditing(true) - playerController.navigationPresentation = .flatModal - controller.push(playerController) - } else if index.1 { - if !progressStarted { - progressStarted = true - progressDisposable.set(progressSignal.start()) - } - } - }, completed: { - }) - cancelImpl = { [weak self] in - self?.playlistPreloadDisposable?.dispose() - } - default: - break - } - } else { - component.context.sharedContext.navigateToChat(accountId: component.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId) + self?.performAction({ [weak self] in + guard let self, let component = self.component, let controller = component.controller(), let navigationController = controller.navigationController as? NavigationController else { + return } - } + + if let id = component.data.item.id as? PeerMessagesMediaPlaylistItemId, let playlistLocation = component.data.playlistLocation as? PeerMessagesPlaylistLocation { + if case .music = component.data.kind { + switch playlistLocation { + case .custom, .savedMusic: + let controllerContext: AccountContext + if component.data.account.id == component.context.account.id { + controllerContext = component.context + } else { + controllerContext = component.context.sharedContext.makeTempAccountContext(account: component.data.account) + } + let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: playlistLocation, parentNavigationController: navigationController) + self.window?.endEditing(true) + playerController.navigationPresentation = .flatModal + controller.push(playerController) + case let .messages(chatLocation, _, _): + let signal = component.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId)), count: 60, highlight: true, setupReply: false), id: 0), context: component.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic(value: nil), tag: .tag(MessageTags.music)) + + var cancelImpl: (() -> Void)? + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.component?.controller()?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = MetaDisposable() + var progressStarted = false + self.playlistPreloadDisposable?.dispose() + self.playlistPreloadDisposable = (signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + |> deliverOnMainQueue).start(next: { [weak self] index in + guard let self, let component = self.component else { + return + } + if let _ = index.0 { + let controllerContext: AccountContext + if component.data.account.id == component.context.account.id { + controllerContext = component.context + } else { + controllerContext = component.context.sharedContext.makeTempAccountContext(account: component.data.account) + } + let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: nil, parentNavigationController: navigationController) + self.window?.endEditing(true) + playerController.navigationPresentation = .flatModal + controller.push(playerController) + } else if index.1 { + if !progressStarted { + progressStarted = true + progressDisposable.set(progressSignal.start()) + } + } + }, completed: { + }) + cancelImpl = { [weak self] in + self?.playlistPreloadDisposable?.dispose() + } + default: + break + } + } else { + component.context.sharedContext.navigateToChat(accountId: component.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId) + } + } + }) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 9a6e073c4e..2a2a73c008 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -2333,11 +2333,13 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: result.insert(.editRank) case .admin: switch member { - case let .legacyGroupMember(_, _, invitedBy, _, _, _): - result.insert(.restrict) - if invitedBy == accountPeerId { - result.insert(.promote) - result.insert(.editRank) + case let .legacyGroupMember(_, role, invitedBy, _, _, _): + if case .member = role { + result.insert(.restrict) + if invitedBy == accountPeerId { + result.insert(.promote) + result.insert(.editRank) + } } case .channelMember: break @@ -2346,8 +2348,8 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: } case .member: switch member { - case let .legacyGroupMember(_, _, invitedBy, _, _, _): - if invitedBy == accountPeerId { + case let .legacyGroupMember(_, role, invitedBy, _, _, _): + if case .member = role, invitedBy == accountPeerId { result.insert(.restrict) result.insert(.editRank) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index c392a2381d..33db993053 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -1267,8 +1267,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor)) } } else if let _ = threadData { - let subtitleColor: UIColor - subtitleColor = UIColor.white + let subtitleColor: UIColor = .white let statusText: String if let user = peer as? TelegramUser, user.isForum { @@ -1364,6 +1363,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { ], mainState: TitleNodeStateRegular) self.subtitleNode.accessibilityLabel = subtitleStringText + var subtitleButtonHorizontalOffset: CGFloat = 0.0 if subtitleIsButton { let subtitleBackgroundNode: ASDisplayNode if let current = self.subtitleBackgroundNode { @@ -1400,20 +1400,21 @@ final class PeerInfoHeaderNode: ASDisplayNode { let subtitleArrowNode: ASImageNode if let current = self.subtitleArrowNode { subtitleArrowNode = current - if themeUpdated { - subtitleArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: .white)?.withRenderingMode(.alwaysTemplate) - } } else { subtitleArrowNode = ASImageNode() self.subtitleArrowNode = subtitleArrowNode self.subtitleNode.insertSubnode(subtitleArrowNode, at: 1) - - subtitleArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: .white)?.withRenderingMode(.alwaysTemplate) } - subtitleBackgroundNode.backgroundColor = .white.withMultipliedAlpha(0.1) + if subtitleArrowNode.image == nil || themeUpdated { + subtitleArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: presentationData.theme.list.itemSecondaryTextColor) + } + self.subtitleNode.updateTintColor(color: presentationData.theme.list.itemSecondaryTextColor, transition: navigationTransition) + + transition.updateBackgroundColor(node: subtitleBackgroundNode, color: contentButtonBackgroundColor) let subtitleSize = subtitleNodeLayout[TitleNodeStateRegular]!.size - var subtitleBackgroundFrame = CGRect(origin: CGPoint(), size: subtitleSize).offsetBy(dx: -subtitleSize.width * 0.5, dy: -subtitleSize.height * 0.5).insetBy(dx: -6.0, dy: -4.0) + var subtitleBackgroundFrame = CGRect(origin: CGPoint(), size: subtitleSize).offsetBy(dx: -subtitleSize.width * 0.5, dy: -subtitleSize.height * 0.5).insetBy(dx: -8.0, dy: -4.0) subtitleBackgroundFrame.size.width += 12.0 + subtitleButtonHorizontalOffset = subtitleBackgroundFrame.midX transition.updateFrame(node: subtitleBackgroundNode, frame: subtitleBackgroundFrame) transition.updateCornerRadius(node: subtitleBackgroundNode, cornerRadius: subtitleBackgroundFrame.height * 0.5) @@ -1620,7 +1621,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { titleOffset = -min(titleCollapseOffset, contentOffset) titleCollapseFraction = max(0.0, min(1.0, contentOffset / titleCollapseOffset)) - subtitleFrame = CGRect(origin: CGPoint(x: 16.0, y: minTitleFrame.maxY + 2.0), size: subtitleSize) + subtitleFrame = CGRect(origin: CGPoint(x: 16.0 - subtitleButtonHorizontalOffset * (1.0 - titleCollapseFraction), y: minTitleFrame.maxY + 2.0), size: subtitleSize) if self.subtitleRating != nil { subtitleFrame.origin.x += 22.0 } @@ -1642,10 +1643,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { let totalSubtitleWidth = effectiveSubtitleWidth + usernameSpacing + usernameSize.width if usernameSize.width == 0.0 { - subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - effectiveSubtitleWidth) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) + subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - effectiveSubtitleWidth) / 2.0) - subtitleButtonHorizontalOffset * (1.0 - titleCollapseFraction), y: titleFrame.maxY + 1.0), size: subtitleSize) usernameFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - usernameSize.width) / 2.0), y: subtitleFrame.maxY + 1.0), size: usernameSize) } else { - subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - totalSubtitleWidth) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) + subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - totalSubtitleWidth) / 2.0) - subtitleButtonHorizontalOffset * (1.0 - titleCollapseFraction), y: titleFrame.maxY + 1.0), size: subtitleSize) usernameFrame = CGRect(origin: CGPoint(x: subtitleFrame.maxX + usernameSpacing, y: titleFrame.maxY + 1.0), size: usernameSize) } } @@ -2859,4 +2860,3 @@ final class PeerInfoHeaderNode: ASDisplayNode { transition.updateAnchorPoint(layer: self.avatarListNode.maskNode.layer, anchorPoint: maskAnchorPoint) } } - diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 6ae0f17047..bce7809348 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -1079,12 +1079,9 @@ final class PeerSelectionControllerNode: ASDisplayNode { if textInputPanelNode.frame.width.isZero { panelTransition = .immediate } - var panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, keyboardHeight: layout.inputHeight ?? 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false) - if self.searchDisplayController == nil { - panelHeight += insets.bottom - } else { - panelHeight += cleanInsets.bottom - } + var panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.safeInsets.bottom, keyboardHeight: layout.inputHeight ?? 0.0, additionalSideInsets: UIEdgeInsets(), textFieldMaxHeight: layout.size.height / 2.0, availableHeight: layout.size.height, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false) + let effectiveBottomInset = textInputPanelNode.additionalInputHeight > 0.0 ? 0.0 : (self.searchDisplayController == nil ? insets.bottom : cleanInsets.bottom) + panelHeight += effectiveBottomInset textPanelHeight = panelHeight let panelFrame = CGRect(x: 0.0, y: layout.size.height - panelHeight, width: layout.size.width, height: panelHeight) diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 419d1a1e27..83704317b2 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -378,22 +378,24 @@ public final class AccountContextImpl: AccountContext { }).start() } }) + + let queue = Queue() + self.deviceSpecificContactImportContexts = QueueLocalObject(queue: queue, generate: { + return DeviceSpecificContactImportContexts(queue: queue) + }) let langCode = sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode self.currentCountriesConfiguration = Atomic(value: CountriesConfiguration(countries: loadCountryCodes())) if !temp { let currentCountriesConfiguration = self.currentCountriesConfiguration self.countriesConfigurationDisposable = (self.engine.localization.getCountriesList(accountManager: sharedContext.accountManager, langCode: langCode) - |> deliverOnMainQueue).start(next: { value in - let _ = currentCountriesConfiguration.swap(CountriesConfiguration(countries: value)) + |> deliverOnMainQueue).start(next: { [weak self] value in + let configuration = CountriesConfiguration(countries: value) + let _ = currentCountriesConfiguration.swap(configuration) + self?._countriesConfiguration.set(.single(configuration)) }) } - let queue = Queue() - self.deviceSpecificContactImportContexts = QueueLocalObject(queue: queue, generate: { - return DeviceSpecificContactImportContexts(queue: queue) - }) - if let contactDataManager = sharedContext.contactDataManager { let deviceSpecificContactImportContexts = self.deviceSpecificContactImportContexts self.managedAppSpecificContactsDisposable = (contactDataManager.appSpecificReferences() diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 748ae33859..99229438c0 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -1171,7 +1171,7 @@ extension ChatControllerImpl { } return updatedState }) - self.searchResult.set(.single((results, state, .general(scope: .channels, tags: nil, minDate: nil, maxDate: nil, folderId: nil)))) + self.searchResult.set(.single((results, state, .general(scope: .channels, groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil)))) } } @@ -2160,15 +2160,17 @@ extension ChatControllerImpl { } var invertedMediaAttribute: InvertMediaMessageAttribute? - if let attribute = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) { - invertedMediaAttribute = attribute as? InvertMediaMessageAttribute - } - - if let mediaCaptionIsAbove = editMessage.mediaCaptionIsAbove { - if mediaCaptionIsAbove { - invertedMediaAttribute = InvertMediaMessageAttribute() - } else { - invertedMediaAttribute = nil + if webpagePreviewAttribute == nil { + if let attribute = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) { + invertedMediaAttribute = attribute as? InvertMediaMessageAttribute + } + + if let mediaCaptionIsAbove = editMessage.mediaCaptionIsAbove { + if mediaCaptionIsAbove { + invertedMediaAttribute = InvertMediaMessageAttribute() + } else { + invertedMediaAttribute = nil + } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index fe135f6300..57293d2fef 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10285,7 +10285,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } ) strongSelf.chatDisplayNode.dismissInput() - strongSelf.push(controller) + if strongSelf.videoRecorderValue != nil { + strongSelf.present(controller, in: .window(.root)) + } else { + strongSelf.push(controller) + } }) } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 4f399b6310..3d3e5a53a8 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -1395,6 +1395,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { data: mediaPlayback, controller: { [weak self] in return self?.controller + }, + shouldPerformAction: { [weak self] action in + guard let controller = self?.controller else { + action() + return + } + let _ = controller.presentVoiceMessageDiscardAlert(action: action) } ))) ) diff --git a/submodules/TelegramUI/Sources/ID3ArtworkReader.swift b/submodules/TelegramUI/Sources/ID3ArtworkReader.swift index 0fdc8aef04..29ea8dc742 100644 --- a/submodules/TelegramUI/Sources/ID3ArtworkReader.swift +++ b/submodules/TelegramUI/Sources/ID3ArtworkReader.swift @@ -4,35 +4,37 @@ private enum ID3Tag: CaseIterable { case v2 case v3 - static var headerLength = 5 + static let headerLength = 10 + static let versionHeaderLength = 5 + var header: Data { switch self { case .v2: - return Data([ 0x49, 0x44, 0x33, 0x02, 0x00 ]) + return Data([0x49, 0x44, 0x33, 0x02, 0x00]) case .v3: - return Data([ 0x49, 0x44, 0x33, 0x03, 0x00 ]) + return Data([0x49, 0x44, 0x33, 0x03, 0x00]) } } var artworkHeader: Data { switch self { case .v2: - return Data([ 0x50, 0x49, 0x43 ]) + return Data([0x50, 0x49, 0x43]) case .v3: - return Data([ 0x41, 0x50, 0x49, 0x43 ]) + return Data([0x41, 0x50, 0x49, 0x43]) } } - var frameSizeOffset: Int { + var frameIdentifierLength: Int { switch self { case .v2: - return 2 + return 3 case .v3: return 4 } } - var frameOffset: Int { + var frameHeaderLength: Int { switch self { case .v2: return 6 @@ -41,25 +43,30 @@ private enum ID3Tag: CaseIterable { } } - func frameSize(_ value: Int32) -> Int { + var unsupportedFlagsMask: UInt8 { + switch self { + case .v2, .v3: + return 0xc0 + } + } + + func frameDataSize(in data: Data, at frameOffset: Int) -> Int? { switch self { case .v2: - return Int(value & 0x00ffffff) + self.frameOffset + guard let range = makeRange(start: frameOffset + self.frameIdentifierLength, length: 3, upperBound: data.count) else { + return nil + } + let bytes = data[range] + return Int(UInt32(bytes[bytes.startIndex]) << 16 | UInt32(bytes[bytes.startIndex + 1]) << 8 | UInt32(bytes[bytes.startIndex + 2])) case .v3: - return Int(value) + self.frameOffset + guard let value = readBigEndianUInt32(in: data, at: frameOffset + self.frameIdentifierLength) else { + return nil + } + return Int(value) } } } -private let sizeOffset = 6 -private let tagOffset = 10 -private let artOffset = 10 - -private let v2FrameOffset: UInt32 = 6 -private let v3FrameOffset: UInt32 = 10 - -private let tagEnding = Data([ 0x00, 0x00, 0x00 ]) - private enum ID3ArtworkFormat: CaseIterable { case jpg case png @@ -67,77 +74,103 @@ private enum ID3ArtworkFormat: CaseIterable { var magic: Data { switch self { case .jpg: - return Data([ 0xff, 0xd8, 0xff ]) + return Data([0xff, 0xd8, 0xff]) case .png: - return Data([ 0x89, 0x50, 0x4e, 0x47 ]) + return Data([0x89, 0x50, 0x4e, 0x47]) } } } -private class DataStream { - private let data: Data - private(set) var position = 0 - var reachedEnd: Bool { - return self.position >= self.data.count +private enum ID3ArtworkReaderLimits { + static let maximumTagSize = 16 * 1024 * 1024 + static let maximumFrameSize = 16 * 1024 * 1024 + static let maximumArtworkSize = 16 * 1024 * 1024 +} + +private let id3Prefix = Data([0x49, 0x44, 0x33]) +private let tagEnding = Data([0x00, 0x00, 0x00]) +private let jpegEndMarker = Data([0xff, 0xd9]) +private let pngEndMarker = Data([0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]) + +private func makeRange(start: Int, length: Int, upperBound: Int) -> Range? { + guard start >= 0, length >= 0 else { + return nil } - - init(_ data: Data) { - self.data = data + let (end, overflow) = start.addingReportingOverflow(length) + guard !overflow, end <= upperBound else { + return nil } - - func next() -> UInt8? { - guard !self.reachedEnd else { - return nil - } - let byte = self.data[self.position] - self.position += 1 - return byte + return start ..< end +} + +private func checkedAdd(_ lhs: Int, _ rhs: Int) -> Int? { + let (result, overflow) = lhs.addingReportingOverflow(rhs) + return overflow ? nil : result +} + +private func readBigEndianUInt32(in data: Data, at offset: Int) -> UInt32? { + guard let range = makeRange(start: offset, length: 4, upperBound: data.count) else { + return nil } - - func nextValue() -> T? { - let count = MemoryLayout.size - guard self.position + count <= self.data.count else { - return nil - } - let value = self.data.subdata(in: self.position ..< self.position + count).withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> T in - return pointer.baseAddress!.assumingMemoryBound(to: T.self).pointee - } - self.position += count - return value + let bytes = data[range] + return UInt32(bytes[bytes.startIndex]) << 24 + | UInt32(bytes[bytes.startIndex + 1]) << 16 + | UInt32(bytes[bytes.startIndex + 2]) << 8 + | UInt32(bytes[bytes.startIndex + 3]) +} + +private func decodeSynchsafeUInt32(_ value: UInt32) -> Int? { + guard value & 0x80808080 == 0 else { + return nil } + let b1 = Int((value >> 24) & 0x7f) + let b2 = Int((value >> 16) & 0x7f) + let b3 = Int((value >> 8) & 0x7f) + let b4 = Int(value & 0x7f) + return (b1 << 21) | (b2 << 14) | (b3 << 7) | b4 +} + +private func extractArtworkData(from data: Data, frameDataRange: Range) -> Data? { + let frameData = data.subdata(in: frameDataRange) - func next(_ count: Int, offset: Int = 0, peek: Bool = false) -> Data? { - guard self.position + offset + count <= self.data.count else { - return nil + var bestMatch: (format: ID3ArtworkFormat, range: Range)? + for format in ID3ArtworkFormat.allCases { + guard let range = frameData.range(of: format.magic) else { + continue } - let subdata = self.data.subdata(in: self.position + offset ..< self.position + offset + count) - if !peek { - self.position += count + offset - } - return subdata - } - - func upTo(_ marker: UInt8) -> Data? { - if let end = (self.position ..< self.data.count).firstIndex( where: { self.data[$0] == marker } ) { - let upTo = self.next(end - self.position) - self.skip() - return upTo + if let current = bestMatch { + if range.lowerBound < current.range.lowerBound { + bestMatch = (format, range) + } } else { - return nil + bestMatch = (format, range) } } - func skip(_ count: Int = 1) { - self.position += count + guard let match = bestMatch else { + return nil } - func skipThrough(_ marker: UInt8) { - if let end = (self.position ..< self.data.count).firstIndex( where: { self.data[$0] == marker } ) { - self.position = end + 1 - } else { - self.position = self.data.count - } + let payload: Data + switch match.format { + case .jpg: + if let endMarkerRange = frameData[match.range.lowerBound ..< frameData.endIndex].range(of: jpegEndMarker) { + payload = frameData.subdata(in: match.range.lowerBound ..< endMarkerRange.upperBound) + } else { + payload = frameData.subdata(in: match.range.lowerBound ..< frameData.endIndex) + } + case .png: + if let endMarkerRange = frameData[match.range.lowerBound ..< frameData.endIndex].range(of: pngEndMarker) { + payload = frameData.subdata(in: match.range.lowerBound ..< endMarkerRange.upperBound) + } else { + payload = frameData.subdata(in: match.range.lowerBound ..< frameData.endIndex) + } } + + guard payload.count <= ID3ArtworkReaderLimits.maximumArtworkSize else { + return nil + } + return payload } enum ID3ArtworkResult { @@ -147,12 +180,17 @@ enum ID3ArtworkResult { } func readAlbumArtworkData(_ data: Data) -> ID3ArtworkResult { - guard data.count >= 4 else { - return .notFound + if data.count < id3Prefix.count { + return id3Prefix.starts(with: data) ? .moreDataNeeded(ID3Tag.headerLength) : .notFound + } + if data.count < ID3Tag.headerLength { + return data.starts(with: id3Prefix) ? .moreDataNeeded(ID3Tag.headerLength) : .notFound } - let stream = DataStream(data) - let versionHeader = stream.next(ID3Tag.headerLength) + guard let versionHeaderRange = makeRange(start: 0, length: ID3Tag.versionHeaderLength, upperBound: data.count) else { + return .notFound + } + let versionHeader = data.subdata(in: versionHeaderRange) var version: ID3Tag? for tag in ID3Tag.allCases { @@ -165,79 +203,57 @@ func readAlbumArtworkData(_ data: Data) -> ID3ArtworkResult { return .notFound } - stream.skip() - guard let value: UInt32 = stream.nextValue() else { + let flags = data[5] + guard flags & id3Tag.unsupportedFlagsMask == 0 else { return .notFound } - let size = CFSwapInt32HostToBig(value) - let b1 = (size & 0x7f000000) >> 3 - let b2 = (size & 0x007f0000) >> 2 - let b3 = (size & 0x00007f00) >> 1 - let b4 = size & 0x0000007f - let tagSize = Int(b1 + b2 + b3 + b4) - while !stream.reachedEnd { - guard let frameHeader = stream.next(4, peek: true) else { - return .moreDataNeeded(tagSize) - } - - stream.skip(id3Tag.frameSizeOffset) - guard let value: UInt32 = stream.nextValue() else { - return .moreDataNeeded(tagSize) - } - let val = CFSwapInt32HostToBig(value) - if val > Int32.max { - return .notFound - } - let frameSize = id3Tag.frameSize(Int32(val)) - let bytesLeft = frameSize - id3Tag.frameSizeOffset - 4 - - if frameHeader == id3Tag.artworkHeader { - var image: (ID3ArtworkFormat, Int)? - outer: for i in 0 ..< frameSize - 4 { - if let head = stream.next(4, offset: i, peek: true) { - for format in ID3ArtworkFormat.allCases { - if head.prefix(format.magic.count) == format.magic { - image = (format, i) - break outer - } - } - } - } - - if let (format, offset) = image { - stream.skip(offset) - - switch format { - case .jpg: - var data = Data(capacity: frameSize + 1024) - var previousByte: UInt8 = 0xff - - let limit = Int(Double(frameSize - offset) * 0.8) - for _ in 0 ..< frameSize - offset { - if let byte: UInt8 = stream.nextValue() { - data.append(byte) - if byte == 0xd9 && previousByte == 0xff && data.count > limit { - break - } - previousByte = byte - } else { - return .moreDataNeeded(tagSize) - } - } - return .artworkData(data) - case .png: - if let data = stream.next(frameSize - offset) { - return .artworkData(data) - } else { - return .moreDataNeeded(tagSize) - } - } - } - } else if frameHeader.prefix(3) == tagEnding { - return .notFound - } - stream.skip(bytesLeft) + guard let rawTagSize = readBigEndianUInt32(in: data, at: 6), let tagPayloadSize = decodeSynchsafeUInt32(rawTagSize) else { + return .notFound } + guard let totalTagSize = checkedAdd(ID3Tag.headerLength, tagPayloadSize), totalTagSize <= ID3ArtworkReaderLimits.maximumTagSize else { + return .notFound + } + + let availableTagEnd = min(totalTagSize, data.count) + var frameOffset = ID3Tag.headerLength + + while frameOffset < availableTagEnd { + let remainingTagBytes = availableTagEnd - frameOffset + if remainingTagBytes < id3Tag.frameHeaderLength { + return totalTagSize > data.count ? .moreDataNeeded(totalTagSize) : .notFound + } + + guard let frameIdentifierRange = makeRange(start: frameOffset, length: id3Tag.frameIdentifierLength, upperBound: data.count) else { + return totalTagSize > data.count ? .moreDataNeeded(totalTagSize) : .notFound + } + let frameIdentifier = data.subdata(in: frameIdentifierRange) + if frameIdentifier.prefix(3) == tagEnding { + return .notFound + } + + guard let framePayloadSize = id3Tag.frameDataSize(in: data, at: frameOffset), framePayloadSize <= ID3ArtworkReaderLimits.maximumFrameSize else { + return .notFound + } + guard let frameSize = checkedAdd(id3Tag.frameHeaderLength, framePayloadSize), let frameEnd = checkedAdd(frameOffset, frameSize) else { + return .notFound + } + guard frameEnd <= totalTagSize else { + return .notFound + } + if frameEnd > data.count { + return .moreDataNeeded(totalTagSize) + } + + if frameIdentifier == id3Tag.artworkHeader { + let frameDataStart = frameOffset + id3Tag.frameHeaderLength + if let artworkData = extractArtworkData(from: data, frameDataRange: frameDataStart ..< frameEnd) { + return .artworkData(artworkData) + } + } + + frameOffset = frameEnd + } + return .notFound } diff --git a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift index 5af46a886f..5d960c53b3 100644 --- a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift +++ b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift @@ -168,8 +168,10 @@ private final class SheetContent: CombinedComponent { case let .invoice(invoice, replyMarkupValue): media = [invoice] replyMarkup = replyMarkupValue - default: - break + case let .webpage(textValue, entitiesValue, _, _, replyMarkupValue): + text = textValue + entities = entitiesValue + replyMarkup = replyMarkupValue } case let .externalReference(reference): switch reference.message { @@ -195,8 +197,10 @@ private final class SheetContent: CombinedComponent { case let .invoice(invoice, replyMarkupValue): media = [invoice] replyMarkup = replyMarkupValue - default: - break + case let .webpage(textValue, entitiesValue, _, _, replyMarkupValue): + text = textValue + entities = entitiesValue + replyMarkup = replyMarkupValue } }