mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various fixes
This commit is contained in:
parent
31e3a7dc79
commit
532a3ae3e1
27 changed files with 548 additions and 435 deletions
|
|
@ -286,7 +286,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
|
||||
public let inputModeView: ComponentHostView<Empty>
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<Bool, NoError>
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public struct StandaloneSendMessagesError {
|
|||
public var peerId: PeerId
|
||||
public var reason: PendingMessageFailureReason?
|
||||
|
||||
init(
|
||||
public init(
|
||||
peerId: PeerId,
|
||||
reason: PendingMessageFailureReason?
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1591,7 +1591,7 @@ public extension TelegramEngine {
|
|||
}
|
||||
|
||||
public func subscribeIsPremiumRequiredForMessaging(id: EnginePeer.Id) -> Signal<Bool, NoError> {
|
||||
if id.namespace != Namespaces.Peer.CloudUser {
|
||||
if id == self.account.peerId || id.namespace != Namespaces.Peer.CloudUser {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }) }
|
||||
|
|
|
|||
|
|
@ -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<Empty>, 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<ChatLocationContextHolder?>(value: nil), tag: .tag(MessageTags.music))
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let progressSignal = Signal<Never, NoError> { [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<ChatLocationContextHolder?>(value: nil), tag: .tag(MessageTags.music))
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let progressSignal = Signal<Never, NoError> { [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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
)))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<Int>? {
|
||||
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: BinaryInteger>() -> T? {
|
||||
let count = MemoryLayout<T>.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<Int>) -> 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<Data.Index>)?
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue