mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stories
This commit is contained in:
parent
e72f7f5c1e
commit
3b0ddece86
@ -2013,9 +2013,9 @@ xcodeproj(
|
|||||||
"Debug": {
|
"Debug": {
|
||||||
"//command_line_option:compilation_mode": "dbg",
|
"//command_line_option:compilation_mode": "dbg",
|
||||||
},
|
},
|
||||||
"Release": {
|
#"Release": {
|
||||||
"//command_line_option:compilation_mode": "opt",
|
# "//command_line_option:compilation_mode": "opt",
|
||||||
},
|
#},
|
||||||
},
|
},
|
||||||
default_xcode_configuration = "Debug"
|
default_xcode_configuration = "Debug"
|
||||||
|
|
||||||
|
@ -1625,7 +1625,7 @@ private final class NotificationServiceHandler {
|
|||||||
} else {
|
} else {
|
||||||
completed()
|
completed()
|
||||||
}
|
}
|
||||||
case let .pollStories(peerId, initialContent, _):
|
case let .pollStories(peerId, initialContent, storyId):
|
||||||
Logger.shared.log("NotificationService \(episode)", "Will poll stories for \(peerId)")
|
Logger.shared.log("NotificationService \(episode)", "Will poll stories for \(peerId)")
|
||||||
if let stateManager = strongSelf.stateManager {
|
if let stateManager = strongSelf.stateManager {
|
||||||
let pollCompletion: (NotificationContent) -> Void = { content in
|
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)
|
fetchStoriesSignal = _internal_pollPeerStories(postbox: stateManager.postbox, network: stateManager.network, accountPeerId: stateManager.accountPeerId, peerId: peerId)
|
||||||
|> map { _ -> Void in
|
|> 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)
|
let fetchMediaSignal: Signal<Data?, NoError> = .single(nil)
|
||||||
|
|
||||||
|
@ -582,6 +582,13 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
|||||||
|
|
||||||
public var storyParams: (peer: EnginePeer, items: [EngineStoryItem], count: Int, hasUnseen: Bool)?
|
public var storyParams: (peer: EnginePeer, items: [EngineStoryItem], count: Int, hasUnseen: Bool)?
|
||||||
private var expandedStorySetIndicator: ComponentView<Empty>?
|
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
|
public let contentNode: ASDisplayNode
|
||||||
let leftHighlightNode: ASDisplayNode
|
let leftHighlightNode: ASDisplayNode
|
||||||
|
@ -939,6 +939,14 @@ public func resourceFetchInfo(resource: TelegramMediaResource) -> MediaResourceF
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func resourceFetchInfo(reference: MediaResourceReference) -> MediaResourceFetchInfo? {
|
||||||
|
return TelegramCloudMediaResourceFetchInfo(
|
||||||
|
reference: reference,
|
||||||
|
preferBackgroundReferenceRevalidation: false,
|
||||||
|
continueInBackground: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private func multipartFetchV1(
|
private func multipartFetchV1(
|
||||||
accountPeerId: PeerId,
|
accountPeerId: PeerId,
|
||||||
postbox: Postbox,
|
postbox: Postbox,
|
||||||
|
@ -11,17 +11,17 @@ final class StoryItemLoadingEffectView: UIView {
|
|||||||
private let backgroundView: UIImageView
|
private let backgroundView: UIImageView
|
||||||
|
|
||||||
private let borderGradientView: UIImageView
|
private let borderGradientView: UIImageView
|
||||||
private let borderContaineView: UIView
|
private let borderContainerView: UIView
|
||||||
private let borderMaskLayer: SimpleShapeLayer
|
private let borderMaskLayer: SimpleShapeLayer
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
|
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||||
|
|
||||||
self.gradientWidth = 500.0
|
self.gradientWidth = 200.0
|
||||||
self.backgroundView = UIImageView()
|
self.backgroundView = UIImageView()
|
||||||
|
|
||||||
self.borderGradientView = UIImageView()
|
self.borderGradientView = UIImageView()
|
||||||
self.borderContaineView = UIView()
|
self.borderContainerView = UIView()
|
||||||
self.borderMaskLayer = SimpleShapeLayer()
|
self.borderMaskLayer = SimpleShapeLayer()
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@ -34,7 +34,8 @@ final class StoryItemLoadingEffectView: UIView {
|
|||||||
self.updateAnimations(size: self.bounds.size)
|
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 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 backgroundColor = UIColor.clear
|
||||||
|
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
@ -43,7 +44,7 @@ final class StoryItemLoadingEffectView: UIView {
|
|||||||
|
|
||||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
let foregroundColor = UIColor(white: 1.0, alpha: 0.2)
|
let foregroundColor = UIColor(white: 1.0, alpha: baseAlpha)
|
||||||
|
|
||||||
let numColors = 7
|
let numColors = 7
|
||||||
var locations: [CGFloat] = []
|
var locations: [CGFloat] = []
|
||||||
@ -63,7 +64,14 @@ final class StoryItemLoadingEffectView: UIView {
|
|||||||
|
|
||||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
|
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.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) {
|
required init?(coder: NSCoder) {
|
||||||
@ -75,17 +83,28 @@ final class StoryItemLoadingEffectView: UIView {
|
|||||||
return
|
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
|
animation.repeatCount = Float.infinity
|
||||||
self.backgroundView.layer.add(animation, forKey: "shimmer")
|
self.backgroundView.layer.add(animation, forKey: "shimmer")
|
||||||
|
self.borderGradientView.layer.add(animation, forKey: "shimmer")
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, transition: Transition) {
|
func update(size: CGSize, transition: Transition) {
|
||||||
if self.backgroundView.bounds.size != size {
|
if self.backgroundView.bounds.size != size {
|
||||||
self.backgroundView.layer.removeAllAnimations()
|
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)
|
self.updateAnimations(size: size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,6 +252,12 @@ public final class StorySetIndicatorComponent: Component {
|
|||||||
private var component: StorySetIndicatorComponent?
|
private var component: StorySetIndicatorComponent?
|
||||||
private weak var state: EmptyComponentState?
|
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) {
|
override init(frame: CGRect) {
|
||||||
self.button = HighlightTrackingButton()
|
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 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
|
let effectiveItemsWidth: CGFloat = outerDiameter * 0.5 + CGFloat(max(0, items.count - 1)) * (outerDiameter - overflow) + outerDiameter * 0.5
|
||||||
|
self.effectiveItemsWidth = effectiveItemsWidth
|
||||||
|
|
||||||
let borderColors: [UInt32]
|
let borderColors: [UInt32]
|
||||||
|
|
||||||
|
@ -4122,6 +4122,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
sourceIsAvatar: true
|
sourceIsAvatar: true
|
||||||
)
|
)
|
||||||
self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = 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(
|
let storyContainerScreen = StoryContainerScreen(
|
||||||
@ -4134,6 +4142,44 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
if !fromAvatar {
|
if !fromAvatar {
|
||||||
self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = false
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user