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": {
|
||||
"//command_line_option:compilation_mode": "dbg",
|
||||
},
|
||||
"Release": {
|
||||
"//command_line_option:compilation_mode": "opt",
|
||||
},
|
||||
#"Release": {
|
||||
# "//command_line_option:compilation_mode": "opt",
|
||||
#},
|
||||
},
|
||||
default_xcode_configuration = "Debug"
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
accountPeerId: PeerId,
|
||||
postbox: Postbox,
|
||||
|
@ -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,7 +34,8 @@ 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 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
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
@ -43,7 +44,7 @@ final class StoryItemLoadingEffectView: UIView {
|
||||
|
||||
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
|
||||
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())
|
||||
})
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user