Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mike Renoir 2023-07-04 19:20:42 +02:00
commit 879b9742b7
10 changed files with 248 additions and 58 deletions

View File

@ -2013,9 +2013,9 @@ xcodeproj(
"Debug": {
"//command_line_option:compilation_mode": "dbg",
},
"Release": {
"//command_line_option:compilation_mode": "opt",
},
#"Release": {
# "//command_line_option:compilation_mode": "opt",
#},
},
default_xcode_configuration = "Debug"

View File

@ -1625,7 +1625,7 @@ private final class NotificationServiceHandler {
} else {
completed()
}
case let .pollStories(peerId, initialContent, _):
case let .pollStories(peerId, initialContent, storyId):
Logger.shared.log("NotificationService \(episode)", "Will poll stories for \(peerId)")
if let stateManager = strongSelf.stateManager {
let pollCompletion: (NotificationContent) -> Void = { content in
@ -1643,7 +1643,92 @@ private final class NotificationServiceHandler {
fetchStoriesSignal = _internal_pollPeerStories(postbox: stateManager.postbox, network: stateManager.network, accountPeerId: stateManager.accountPeerId, peerId: peerId)
|> map { _ -> Void in
}
|> then(.single(Void()))
|> then(
stateManager.postbox.transaction { transaction -> (MediaResourceReference, Int64?)? in
guard let state = transaction.getPeerStoryState(peerId: peerId)?.get(Stories.PeerState.self) else {
return nil
}
let firstUnseenItem = transaction.getStoryItems(peerId: peerId).first(where: { entry in
return entry.id > state.maxReadId
})
guard let firstUnseenItem, firstUnseenItem.id == storyId else {
return nil
}
guard let peer = transaction.getPeer(peerId).flatMap(PeerReference.init) else {
return nil
}
if let storyItem = transaction.getStory(id: StoryId(peerId: peerId, id: storyId))?.get(Stories.StoredItem.self), case let .item(item) = storyItem, let media = item.media {
var resource: MediaResource?
var fetchSize: Int64?
if let image = media as? TelegramMediaImage {
resource = largestImageRepresentation(image.representations)?.resource
} else if let file = media as? TelegramMediaFile {
resource = file.resource
for attribute in file.attributes {
if case let .Video(_, _, _, preloadSize) = attribute {
fetchSize = preloadSize.flatMap(Int64.init)
}
}
}
guard let resource else {
return nil
}
return (MediaResourceReference.media(media: .story(peer: peer, id: storyId, media: media), resource: resource), fetchSize)
}
return nil
}
|> mapToSignal { resourceData -> Signal<Void, NoError> in
guard let (resource, _) = resourceData, let resourceValue = resource.resource as? TelegramMultipartFetchableResource else {
return .single(Void())
}
let intervals: Signal<[(Range<Int64>, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int64.max, MediaBoxFetchPriority.maximum)])
return Signal<Void, NoError> { subscriber in
let collectedData = Atomic<Data>(value: Data())
return standaloneMultipartFetch(
accountPeerId: stateManager.accountPeerId,
postbox: stateManager.postbox,
network: stateManager.network,
resource: resourceValue,
datacenterId: resourceValue.datacenterId,
size: nil,
intervals: intervals,
parameters: MediaResourceFetchParameters(
tag: nil,
info: resourceFetchInfo(reference: resource),
location: .init(peerId: peerId, messageId: nil),
contentType: .other,
isRandomAccessAllowed: true
),
encryptionKey: nil,
decryptedSize: nil,
continueInBackground: false,
useMainConnection: true
).start(next: { result in
switch result {
case let .dataPart(_, data, _, _):
let _ = collectedData.modify { current in
var current = current
current.append(data)
return current
}
default:
break
}
}, error: { _ in
subscriber.putNext(Void())
subscriber.putCompletion()
}, completed: {
stateManager.postbox.mediaBox.storeResourceData(resource.resource.id, data: collectedData.with({ $0 }))
subscriber.putNext(Void())
subscriber.putCompletion()
})
}
}
)
let fetchMediaSignal: Signal<Data?, NoError> = .single(nil)

View File

@ -582,6 +582,13 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
public var storyParams: (peer: EnginePeer, items: [EngineStoryItem], count: Int, hasUnseen: Bool)?
private var expandedStorySetIndicator: ComponentView<Empty>?
public var expandedStorySetIndicatorTransitionView: (UIView, CGRect)? {
if let setView = self.expandedStorySetIndicator?.view as? StorySetIndicatorComponent.View {
return setView.transitionView
} else {
return nil
}
}
public let contentNode: ASDisplayNode
let leftHighlightNode: ASDisplayNode

View File

@ -528,7 +528,11 @@ private final class MultipartFetchManager {
if isStory {
self.defaultPartSize = 512 * 1024
self.parallelParts = 4
if let size, size > self.defaultPartSize {
self.parallelParts = 4
} else {
self.parallelParts = 1
}
} else if let size = size {
if size <= 512 * 1024 {
self.defaultPartSize = 16 * 1024
@ -939,6 +943,14 @@ public func resourceFetchInfo(resource: TelegramMediaResource) -> MediaResourceF
)
}
public func resourceFetchInfo(reference: MediaResourceReference) -> MediaResourceFetchInfo? {
return TelegramCloudMediaResourceFetchInfo(
reference: reference,
preferBackgroundReferenceRevalidation: false,
continueInBackground: false
)
}
private func multipartFetchV1(
accountPeerId: PeerId,
postbox: Postbox,

View File

@ -86,10 +86,15 @@ struct NetworkResponseInfo {
var networkDuration: Double
}
final class RequestManagerPriorityContext {
}
private final class MultiplexedRequestManagerContext {
private let queue: Queue
private let takeWorker: (MultiplexedRequestTarget, MediaResourceFetchTag?, Bool) -> Download?
private let priorityContext = RequestManagerPriorityContext()
private var queuedRequests: [RequestData] = []
private var nextId: Int32 = 0
@ -163,9 +168,9 @@ private final class MultiplexedRequestManagerContext {
let maxRequestsPerWorker = 3
let maxWorkersPerTarget = 4
var requestIndex = 0
while requestIndex < self.queuedRequests.count {
let request = self.queuedRequests[requestIndex]
for request in self.queuedRequests.sorted(by: { lhs, rhs in
return lhs.id < rhs.id
}) {
let targetKey = MultiplexedRequestTargetKey(target: request.target, continueInBackground: request.continueInBackground)
if self.targetContexts[targetKey] == nil {
@ -228,11 +233,11 @@ private final class MultiplexedRequestManagerContext {
}
}))
self.queuedRequests.remove(at: requestIndex)
if let requestIndex = self.queuedRequests.firstIndex(where: { $0 === request }) {
self.queuedRequests.remove(at: requestIndex)
}
continue
}
requestIndex += 1
}
self.checkEmptyContexts()

View File

@ -719,7 +719,9 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
var duration: Int32?
var isMin: Bool = false
if let file = selectedMedia as? TelegramMediaFile, !file.isAnimated {
duration = file.duration.flatMap(Int32.init)
if let durationValue = file.duration {
duration = Int32(durationValue)
}
isMin = layer.bounds.width < 80.0
}
layer.updateDuration(duration: duration, isMin: isMin, minFactor: min(1.0, layer.bounds.height / 74.0))

View File

@ -11,17 +11,17 @@ final class StoryItemLoadingEffectView: UIView {
private let backgroundView: UIImageView
private let borderGradientView: UIImageView
private let borderContaineView: UIView
private let borderContainerView: UIView
private let borderMaskLayer: SimpleShapeLayer
override init(frame: CGRect) {
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
self.gradientWidth = 500.0
self.gradientWidth = 200.0
self.backgroundView = UIImageView()
self.borderGradientView = UIImageView()
self.borderContaineView = UIView()
self.borderContainerView = UIView()
self.borderMaskLayer = SimpleShapeLayer()
super.init(frame: frame)
@ -34,36 +34,44 @@ final class StoryItemLoadingEffectView: UIView {
self.updateAnimations(size: self.bounds.size)
}
self.backgroundView.image = generateImage(CGSize(width: self.gradientWidth, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
let backgroundColor = UIColor.clear
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(backgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: CGPoint(), size: size))
let foregroundColor = UIColor(white: 1.0, alpha: 0.2)
let numColors = 7
var locations: [CGFloat] = []
var colors: [CGColor] = []
for i in 0 ..< numColors {
let position: CGFloat = CGFloat(i) / CGFloat(numColors - 1)
locations.append(position)
let generateGradient: (CGFloat) -> UIImage? = { baseAlpha in
return generateImage(CGSize(width: self.gradientWidth, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
let backgroundColor = UIColor.clear
let distanceFromCenterFraction: CGFloat = max(0.0, min(1.0, abs(position - 0.5) / 0.5))
let colorAlpha = sin((1.0 - distanceFromCenterFraction) * CGFloat.pi * 0.5)
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(backgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
colors.append(foregroundColor.withMultipliedAlpha(colorAlpha).cgColor)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
})
context.clip(to: CGRect(origin: CGPoint(), size: size))
let foregroundColor = UIColor(white: 1.0, alpha: baseAlpha)
let numColors = 7
var locations: [CGFloat] = []
var colors: [CGColor] = []
for i in 0 ..< numColors {
let position: CGFloat = CGFloat(i) / CGFloat(numColors - 1)
locations.append(position)
let distanceFromCenterFraction: CGFloat = max(0.0, min(1.0, abs(position - 0.5) / 0.5))
let colorAlpha = sin((1.0 - distanceFromCenterFraction) * CGFloat.pi * 0.5)
colors.append(foregroundColor.withMultipliedAlpha(colorAlpha).cgColor)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
})
}
self.backgroundView.image = generateGradient(0.1)
self.addSubview(self.backgroundView)
self.borderGradientView.image = generateGradient(0.2)
self.borderContainerView.addSubview(self.borderGradientView)
self.addSubview(self.borderContainerView)
self.borderContainerView.layer.mask = self.borderMaskLayer
}
required init?(coder: NSCoder) {
@ -75,17 +83,28 @@ final class StoryItemLoadingEffectView: UIView {
return
}
let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + self.gradientWidth) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.8, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + self.gradientWidth + size.width * 0.2) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
animation.repeatCount = Float.infinity
self.backgroundView.layer.add(animation, forKey: "shimmer")
self.borderGradientView.layer.add(animation, forKey: "shimmer")
}
func update(size: CGSize, transition: Transition) {
if self.backgroundView.bounds.size != size {
self.backgroundView.layer.removeAllAnimations()
self.borderMaskLayer.fillColor = nil
self.borderMaskLayer.strokeColor = UIColor.white.cgColor
let lineWidth: CGFloat = 3.0
self.borderMaskLayer.lineWidth = lineWidth
self.borderMaskLayer.path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5), cornerRadius: 12.0).cgPath
}
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: size))
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: size.height)))
transition.setFrame(view: self.borderContainerView, frame: CGRect(origin: CGPoint(), size: size))
transition.setFrame(view: self.borderGradientView, frame: CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: size.height)))
self.updateAnimations(size: size)
}
}

View File

@ -252,6 +252,12 @@ public final class StorySetIndicatorComponent: Component {
private var component: StorySetIndicatorComponent?
private weak var state: EmptyComponentState?
private var effectiveItemsWidth: CGFloat = 0.0
public var transitionView: (UIView, CGRect) {
return (self.imageView, CGRect(origin: CGPoint(), size: CGSize(width: self.effectiveItemsWidth, height: self.imageView.bounds.height)))
}
override init(frame: CGRect) {
self.button = HighlightTrackingButton()
@ -339,6 +345,7 @@ public final class StorySetIndicatorComponent: Component {
let maxItemsWidth: CGFloat = outerDiameter * 0.5 + CGFloat(max(0, 3 - 1)) * (outerDiameter - overflow) + outerDiameter * 0.5
let effectiveItemsWidth: CGFloat = outerDiameter * 0.5 + CGFloat(max(0, items.count - 1)) * (outerDiameter - overflow) + outerDiameter * 0.5
self.effectiveItemsWidth = effectiveItemsWidth
let borderColors: [UInt32]

View File

@ -625,6 +625,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
var replyMessage: Message?
var replyStory: StoryId?
for attribute in item.message.attributes {
if let attribute = attribute as? InlineBotMessageAttribute {
var inlineBotNameString: String?
@ -651,12 +652,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
} else {
replyMessage = item.message.associatedMessages[replyAttribute.messageId]
}
} else if let attribute = attribute as? ReplyStoryAttribute {
replyStory = attribute.storyId
} else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty {
replyMarkup = attribute
}
}
var hasReply = replyMessage != nil
var hasReply = replyMessage != nil || replyStory != nil
if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil {
if let threadId = item.message.threadId, let replyMessage = replyMessage, Int64(replyMessage.id.id) == threadId {
hasReply = false
@ -676,14 +679,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
))
}
if let replyMessage = replyMessage, hasReply {
if hasReply, (replyMessage != nil || replyStory != nil) {
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
context: item.context,
type: .standalone,
message: replyMessage,
story: nil,
story: replyStory,
parentMessage: item.message,
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
@ -1302,6 +1305,10 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
return .optionalAction({
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
})
} else if let attribute = attribute as? ReplyStoryAttribute {
return .optionalAction({
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
})
}
}
}
@ -1830,16 +1837,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil, nil)
}
override func targetReactionView(value: MessageReaction.Reaction) -> UIView? {
if let result = self.reactionButtonsNode?.reactionTargetView(value: value) {
return result
}
if !self.dateAndStatusNode.isHidden {
return self.dateAndStatusNode.reactionView(value: value)
}
return nil
}
override func targetForStoryTransition(id: StoryId) -> UIView? {
guard let item = self.item else {
return nil
@ -1856,6 +1853,16 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
return nil
}
override func targetReactionView(value: MessageReaction.Reaction) -> UIView? {
if let result = self.reactionButtonsNode?.reactionTargetView(value: value) {
return result
}
if !self.dateAndStatusNode.isHidden {
return self.dateAndStatusNode.reactionView(value: value)
}
return nil
}
override func contentFrame() -> CGRect {
return self.imageNode.frame
}

View File

@ -4122,6 +4122,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
sourceIsAvatar: true
)
self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = true
} else if let (expandedStorySetIndicatorTransitionView, subRect) = self.headerNode.avatarListNode.listContainerNode.expandedStorySetIndicatorTransitionView {
transitionIn = StoryContainerScreen.TransitionIn(
sourceView: expandedStorySetIndicatorTransitionView,
sourceRect: subRect,
sourceCornerRadius: expandedStorySetIndicatorTransitionView.bounds.height * 0.5,
sourceIsAvatar: false
)
expandedStorySetIndicatorTransitionView.isHidden = true
}
let storyContainerScreen = StoryContainerScreen(
@ -4134,6 +4142,44 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
if !fromAvatar {
self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = false
if let (expandedStorySetIndicatorTransitionView, subRect) = self.headerNode.avatarListNode.listContainerNode.expandedStorySetIndicatorTransitionView {
return StoryContainerScreen.TransitionOut(
destinationView: expandedStorySetIndicatorTransitionView,
transitionView: StoryContainerScreen.TransitionView(
makeView: { [weak expandedStorySetIndicatorTransitionView] in
let parentView = UIView()
if let copyView = expandedStorySetIndicatorTransitionView?.snapshotContentTree(unhide: true) {
copyView.layer.anchorPoint = CGPoint()
parentView.addSubview(copyView)
}
return parentView
},
updateView: { copyView, state, transition in
guard let view = copyView.subviews.first else {
return
}
let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress)
transition.setPosition(view: view, position: CGPoint(x: 0.0, y: 0.0))
transition.setScale(view: view, scale: size.width / state.destinationSize.width)
},
insertCloneTransitionView: nil
),
destinationRect: subRect,
destinationCornerRadius: expandedStorySetIndicatorTransitionView.bounds.height * 0.5,
destinationIsAvatar: false,
completed: { [weak self] in
guard let self else {
return
}
if let (expandedStorySetIndicatorTransitionView, _) = self.headerNode.avatarListNode.listContainerNode.expandedStorySetIndicatorTransitionView {
expandedStorySetIndicatorTransitionView.isHidden = false
}
}
)
}
return nil
}