mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Stories
This commit is contained in:
parent
e6ebc89b85
commit
a1e59a8bff
@ -2490,10 +2490,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
|
if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
|
||||||
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peerId) {
|
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peerId) {
|
||||||
|
let localRect = transitionView.convert(transitionView.bounds, to: self.view)
|
||||||
|
|
||||||
|
Queue.mainQueue().after(0.2 * UIView.animationDurationFactor, { [weak self] in
|
||||||
|
HapticFeedback().impact()
|
||||||
|
self?.animateRipple(centerLocation: localRect.center)
|
||||||
|
})
|
||||||
|
|
||||||
return StoryContainerScreen.TransitionOut(
|
return StoryContainerScreen.TransitionOut(
|
||||||
destinationView: transitionView,
|
destinationView: transitionView,
|
||||||
destinationRect: transitionView.bounds,
|
destinationRect: transitionView.bounds,
|
||||||
destinationCornerRadius: transitionView.bounds.height * 0.5
|
destinationCornerRadius: transitionView.bounds.height * 0.5,
|
||||||
|
completed: { [weak self] in
|
||||||
|
let _ = self
|
||||||
|
//self?.animateRipple(centerLocation: localRect.center)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import Display
|
|||||||
@_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float
|
@_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private extension UIView {
|
public extension UIView {
|
||||||
static var animationDurationFactor: Double {
|
static var animationDurationFactor: Double {
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
return Double(UIAnimationDragCoefficient())
|
return Double(UIAnimationDragCoefficient())
|
||||||
|
@ -579,6 +579,24 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
}
|
}
|
||||||
self.maskAsSeenDisposables.set(signal.start(), forKey: opaqueId)
|
self.maskAsSeenDisposables.set(signal.start(), forKey: opaqueId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func markAction(opaqueId: Data) {
|
||||||
|
let account = self.account
|
||||||
|
let signal: Signal<Never, NoError> = account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||||
|
return transaction.getPeer(self.peerId).flatMap(apiInputChannel)
|
||||||
|
}
|
||||||
|
|> mapToSignal { inputChannel -> Signal<Never, NoError> in
|
||||||
|
guard let inputChannel = inputChannel else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
return account.network.request(Api.functions.channels.clickSponsoredMessage(channel: inputChannel, randomId: Buffer(data: opaqueId)))
|
||||||
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
|
return .single(.boolFalse)
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
let _ = signal.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AdMessagesHistoryContext {
|
public class AdMessagesHistoryContext {
|
||||||
@ -612,4 +630,10 @@ public class AdMessagesHistoryContext {
|
|||||||
impl.markAsSeen(opaqueId: opaqueId)
|
impl.markAsSeen(opaqueId: opaqueId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func markAction(opaqueId: Data) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.markAction(opaqueId: opaqueId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,7 @@ swift_library(
|
|||||||
"-warnings-as-errors",
|
"-warnings-as-errors",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
],
|
],
|
||||||
data = [
|
data = [
|
||||||
":FullScreenEffectViewBundle",
|
":FullScreenEffectViewBundle",
|
||||||
|
@ -2,6 +2,7 @@ import Foundation
|
|||||||
import Metal
|
import Metal
|
||||||
import MetalKit
|
import MetalKit
|
||||||
import simd
|
import simd
|
||||||
|
import ComponentFlow
|
||||||
|
|
||||||
public final class RippleEffectView: MTKView {
|
public final class RippleEffectView: MTKView {
|
||||||
private let centerLocation: CGPoint
|
private let centerLocation: CGPoint
|
||||||
@ -154,7 +155,7 @@ public final class RippleEffectView: MTKView {
|
|||||||
let relativeTime: Double
|
let relativeTime: Double
|
||||||
let timestamp = CACurrentMediaTime()
|
let timestamp = CACurrentMediaTime()
|
||||||
if let startTime = self.startTime {
|
if let startTime = self.startTime {
|
||||||
relativeTime = timestamp - startTime
|
relativeTime = (timestamp - startTime) * (1.0 / UIView.animationDurationFactor)
|
||||||
} else {
|
} else {
|
||||||
self.startTime = timestamp
|
self.startTime = timestamp
|
||||||
relativeTime = 0.0
|
relativeTime = 0.0
|
||||||
|
@ -115,6 +115,8 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
private var environment: ViewControllerComponentContainer.Environment?
|
private var environment: ViewControllerComponentContainer.Environment?
|
||||||
|
|
||||||
|
private let backgroundLayer: SimpleLayer
|
||||||
|
|
||||||
private var focusedItemSet: AnyHashable?
|
private var focusedItemSet: AnyHashable?
|
||||||
private var itemSets: [StoryContentItemSlice] = []
|
private var itemSets: [StoryContentItemSlice] = []
|
||||||
private var visibleItemSetViews: [AnyHashable: ItemSetView] = [:]
|
private var visibleItemSetViews: [AnyHashable: ItemSetView] = [:]
|
||||||
@ -122,10 +124,14 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
private var itemSetPanState: ItemSetPanState?
|
private var itemSetPanState: ItemSetPanState?
|
||||||
private var dismissPanState: ItemSetPanState?
|
private var dismissPanState: ItemSetPanState?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
private var isAnimatingOut: Bool = false
|
||||||
super.init(frame: frame)
|
private var didAnimateOut: Bool = false
|
||||||
|
|
||||||
self.backgroundColor = .black
|
override init(frame: CGRect) {
|
||||||
|
self.backgroundLayer = SimpleLayer()
|
||||||
|
self.backgroundLayer.backgroundColor = UIColor.black.cgColor
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
let horizontalPanRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
|
let horizontalPanRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
|
||||||
guard let self, let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
|
guard let self, let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
|
||||||
@ -320,7 +326,7 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
if let transitionIn = self.component?.transitionIn, transitionIn.sourceView != nil {
|
if let transitionIn = self.component?.transitionIn, transitionIn.sourceView != nil {
|
||||||
self.layer.animate(from: UIColor.black.withAlphaComponent(0.0).cgColor, to: self.layer.backgroundColor ?? UIColor.black.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.28)
|
self.backgroundLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.28, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||||
|
|
||||||
if let transitionIn = self.component?.transitionIn, let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet] {
|
if let transitionIn = self.component?.transitionIn, let focusedItemSet = self.focusedItemSet, let itemSetView = self.visibleItemSetViews[focusedItemSet] {
|
||||||
if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
|
if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
|
||||||
@ -336,17 +342,26 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: @escaping () -> Void) {
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
if let component = self.component, let focusedItemSet = self.focusedItemSet, let peerId = focusedItemSet.base as? EnginePeer.Id, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View, let transitionOut = component.transitionOut(peerId) {
|
self.isAnimatingOut = true
|
||||||
let currentBackgroundColor = self.layer.presentation()?.backgroundColor ?? self.layer.backgroundColor
|
self.state?.updated(transition: .immediate)
|
||||||
self.layer.animate(from: currentBackgroundColor ?? UIColor.black.cgColor, to: UIColor.black.withAlphaComponent(0.0).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.25, removeOnCompletion: false)
|
|
||||||
|
|
||||||
itemSetComponentView.animateOut(transitionOut: transitionOut, completion: completion)
|
if let component = self.component, let focusedItemSet = self.focusedItemSet, let peerId = focusedItemSet.base as? EnginePeer.Id, let itemSetView = self.visibleItemSetViews[focusedItemSet], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View, let transitionOut = component.transitionOut(peerId) {
|
||||||
|
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
||||||
|
transition.setAlpha(layer: self.backgroundLayer, alpha: 0.0)
|
||||||
|
|
||||||
|
let transitionOutCompleted = transitionOut.completed
|
||||||
|
itemSetComponentView.animateOut(transitionOut: transitionOut, completion: {
|
||||||
|
completion()
|
||||||
|
transitionOutCompleted()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
self.layer.allowsGroupOpacity = true
|
self.layer.allowsGroupOpacity = true
|
||||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.didAnimateOut = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePreloads() {
|
private func updatePreloads() {
|
||||||
@ -381,6 +396,10 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func update(component: StoryContainerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
func update(component: StoryContainerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||||
|
if self.didAnimateOut {
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
|
||||||
let isFirstTime = self.component == nil
|
let isFirstTime = self.component == nil
|
||||||
|
|
||||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
@ -405,6 +424,9 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
if self.dismissPanState != nil {
|
if self.dismissPanState != nil {
|
||||||
isProgressPaused = true
|
isProgressPaused = true
|
||||||
}
|
}
|
||||||
|
if self.isAnimatingOut {
|
||||||
|
isProgressPaused = true
|
||||||
|
}
|
||||||
|
|
||||||
var dismissPanOffset: CGFloat = 0.0
|
var dismissPanOffset: CGFloat = 0.0
|
||||||
var dismissPanScale: CGFloat = 1.0
|
var dismissPanScale: CGFloat = 1.0
|
||||||
@ -415,7 +437,7 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
dismissAlphaScale = 1.0 * (1.0 - dismissPanState.fraction) + 0.2 * dismissPanState.fraction
|
dismissAlphaScale = 1.0 * (1.0 - dismissPanState.fraction) + 0.2 * dismissPanState.fraction
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setBackgroundColor(view: self, color: UIColor.black.withAlphaComponent(max(0.5, dismissAlphaScale)))
|
transition.setAlpha(layer: self.backgroundLayer, alpha: max(0.5, dismissAlphaScale))
|
||||||
|
|
||||||
var contentDerivedBottomInset: CGFloat = environment.safeInsets.bottom
|
var contentDerivedBottomInset: CGFloat = environment.safeInsets.bottom
|
||||||
|
|
||||||
@ -727,15 +749,18 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
|
|||||||
public weak var destinationView: UIView?
|
public weak var destinationView: UIView?
|
||||||
public let destinationRect: CGRect
|
public let destinationRect: CGRect
|
||||||
public let destinationCornerRadius: CGFloat
|
public let destinationCornerRadius: CGFloat
|
||||||
|
public let completed: () -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
destinationView: UIView,
|
destinationView: UIView,
|
||||||
destinationRect: CGRect,
|
destinationRect: CGRect,
|
||||||
destinationCornerRadius: CGFloat
|
destinationCornerRadius: CGFloat,
|
||||||
|
completed: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.destinationView = destinationView
|
self.destinationView = destinationView
|
||||||
self.destinationRect = destinationRect
|
self.destinationRect = destinationRect
|
||||||
self.destinationCornerRadius = destinationCornerRadius
|
self.destinationCornerRadius = destinationCornerRadius
|
||||||
|
self.completed = completed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4329,6 +4329,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId)
|
||||||
|
|
||||||
switch adAttribute.target {
|
switch adAttribute.target {
|
||||||
case let .peer(id, messageId, startParam):
|
case let .peer(id, messageId, startParam):
|
||||||
if case let .peer(currentPeerId) = self.chatLocation, currentPeerId == id {
|
if case let .peer(currentPeerId) = self.chatLocation, currentPeerId == id {
|
||||||
|
@ -636,7 +636,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
var openNextChannelToRead: ((EnginePeer, TelegramEngine.NextUnreadChannelLocation) -> Void)?
|
var openNextChannelToRead: ((EnginePeer, TelegramEngine.NextUnreadChannelLocation) -> Void)?
|
||||||
private var contentInsetAnimator: DisplayLinkAnimator?
|
private var contentInsetAnimator: DisplayLinkAnimator?
|
||||||
|
|
||||||
private let adMessagesContext: AdMessagesHistoryContext?
|
let adMessagesContext: AdMessagesHistoryContext?
|
||||||
private var adMessagesDisposable: Disposable?
|
private var adMessagesDisposable: Disposable?
|
||||||
private var preloadAdPeerId: PeerId?
|
private var preloadAdPeerId: PeerId?
|
||||||
private let preloadAdPeerDisposable = MetaDisposable()
|
private let preloadAdPeerDisposable = MetaDisposable()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user