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
e81b1fee96
commit
5db3755cbf
@ -1055,7 +1055,7 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
)
|
)
|
||||||
if let storyIndicatorView = storyIndicator.view {
|
if let storyIndicatorView = storyIndicator.view {
|
||||||
if storyIndicatorView.superview == nil {
|
if storyIndicatorView.superview == nil {
|
||||||
self.view.addSubview(storyIndicatorView)
|
self.view.insertSubview(storyIndicatorView, aboveSubview: self.contentNode.view)
|
||||||
}
|
}
|
||||||
indicatorTransition.setFrame(view: storyIndicatorView, frame: CGRect(origin: CGPoint(x: (size.width - indicatorSize.width) * 0.5, y: (size.height - indicatorSize.height) * 0.5), size: indicatorSize))
|
indicatorTransition.setFrame(view: storyIndicatorView, frame: CGRect(origin: CGPoint(x: (size.width - indicatorSize.width) * 0.5, y: (size.height - indicatorSize.height) * 0.5), size: indicatorSize))
|
||||||
}
|
}
|
||||||
|
@ -1255,7 +1255,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
self.avatarContainerNode.addSubnode(self.avatarNode)
|
self.avatarContainerNode.addSubnode(self.avatarNode)
|
||||||
self.contextContainer.addSubnode(self.avatarContainerNode)
|
self.contextContainer.addSubnode(self.avatarContainerNode)
|
||||||
self.contextContainer.addSubnode(self.onlineNode)
|
self.avatarNode.addSubnode(self.onlineNode)
|
||||||
|
|
||||||
self.mainContentContainerNode.addSubnode(self.titleNode)
|
self.mainContentContainerNode.addSubnode(self.titleNode)
|
||||||
self.mainContentContainerNode.addSubnode(self.authorNode)
|
self.mainContentContainerNode.addSubnode(self.authorNode)
|
||||||
@ -3001,9 +3001,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let onlineFrame: CGRect
|
let onlineFrame: CGRect
|
||||||
if onlineIsVoiceChat {
|
if onlineIsVoiceChat {
|
||||||
onlineFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX - onlineLayout.width + 1.0 - UIScreenPixel, y: avatarFrame.maxY - onlineLayout.height + 1.0 - UIScreenPixel), size: onlineLayout)
|
onlineFrame = CGRect(origin: CGPoint(x: avatarFrame.width - onlineLayout.width + 1.0 - UIScreenPixel, y: avatarFrame.height - onlineLayout.height + 1.0 - UIScreenPixel), size: onlineLayout)
|
||||||
} else {
|
} else {
|
||||||
onlineFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX - onlineLayout.width - 2.0, y: avatarFrame.maxY - onlineLayout.height - 2.0), size: onlineLayout)
|
onlineFrame = CGRect(origin: CGPoint(x: avatarFrame.width - onlineLayout.width - 2.0, y: avatarFrame.height - onlineLayout.height - 2.0), size: onlineLayout)
|
||||||
}
|
}
|
||||||
transition.updateFrame(node: strongSelf.onlineNode, frame: onlineFrame)
|
transition.updateFrame(node: strongSelf.onlineNode, frame: onlineFrame)
|
||||||
|
|
||||||
@ -3040,11 +3040,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
avatarTimerTransition = .immediate
|
avatarTimerTransition = .immediate
|
||||||
avatarTimerBadge = AvatarBadgeView(frame: CGRect())
|
avatarTimerBadge = AvatarBadgeView(frame: CGRect())
|
||||||
strongSelf.avatarTimerBadge = avatarTimerBadge
|
strongSelf.avatarTimerBadge = avatarTimerBadge
|
||||||
strongSelf.contextContainer.view.addSubview(avatarTimerBadge)
|
strongSelf.avatarNode.view.addSubview(avatarTimerBadge)
|
||||||
}
|
}
|
||||||
let avatarBadgeSize = CGSize(width: avatarTimerBadgeDiameter, height: avatarTimerBadgeDiameter)
|
let avatarBadgeSize = CGSize(width: avatarTimerBadgeDiameter, height: avatarTimerBadgeDiameter)
|
||||||
avatarTimerBadge.update(size: avatarBadgeSize, text: shortTimeIntervalString(strings: item.presentationData.strings, value: autoremoveTimeout, useLargeFormat: true))
|
avatarTimerBadge.update(size: avatarBadgeSize, text: shortTimeIntervalString(strings: item.presentationData.strings, value: autoremoveTimeout, useLargeFormat: true))
|
||||||
let avatarBadgeFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX - avatarBadgeSize.width, y: avatarFrame.maxY - avatarBadgeSize.height), size: avatarBadgeSize)
|
let avatarBadgeFrame = CGRect(origin: CGPoint(x: avatarFrame.width - avatarBadgeSize.width, y: avatarFrame.height - avatarBadgeSize.height), size: avatarBadgeSize)
|
||||||
avatarTimerTransition.updatePosition(layer: avatarTimerBadge.layer, position: avatarBadgeFrame.center)
|
avatarTimerTransition.updatePosition(layer: avatarTimerBadge.layer, position: avatarBadgeFrame.center)
|
||||||
avatarTimerTransition.updateBounds(layer: avatarTimerBadge.layer, bounds: CGRect(origin: CGPoint(), size: avatarBadgeFrame.size))
|
avatarTimerTransition.updateBounds(layer: avatarTimerBadge.layer, bounds: CGRect(origin: CGPoint(), size: avatarBadgeFrame.size))
|
||||||
avatarTimerTransition.updateTransformScale(layer: avatarTimerBadge.layer, scale: autoremoveTimeoutFraction * 1.0 + (1.0 - autoremoveTimeoutFraction) * 0.00001)
|
avatarTimerTransition.updateTransformScale(layer: avatarTimerBadge.layer, scale: autoremoveTimeoutFraction * 1.0 + (1.0 - autoremoveTimeoutFraction) * 0.00001)
|
||||||
|
@ -331,7 +331,7 @@ public extension CALayer {
|
|||||||
|
|
||||||
adjustFrameRate(animation: animation)
|
adjustFrameRate(animation: animation)
|
||||||
|
|
||||||
self.add(animation, forKey: keyPath)
|
self.add(animation, forKey: additive ? nil : keyPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateAdditive(from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
func animateAdditive(from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||||
|
@ -209,7 +209,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||||||
self.backgroundView.update(size: contentBounds.size, transition: transition)
|
self.backgroundView.update(size: contentBounds.size, transition: transition)
|
||||||
|
|
||||||
if let vibrancyEffectView = self.vibrancyEffectView {
|
if let vibrancyEffectView = self.vibrancyEffectView {
|
||||||
transition.updateFrame(view: vibrancyEffectView, frame: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: contentBounds.size))
|
transition.updateFrame(view: vibrancyEffectView, frame: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: contentBounds.size), beginWithCurrentState: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1123,7 +1123,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
var scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.isExpanded ? (46.0 + 54.0 - 4.0) : self.contentTopInset), size: actualBackgroundFrame.size)
|
var scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.isExpanded ? (46.0 + 54.0 - 4.0) : self.contentTopInset), size: actualBackgroundFrame.size)
|
||||||
scrollFrame.origin.y += floorToScreenPixels(self.extensionDistance / 2.0)
|
scrollFrame.origin.y += floorToScreenPixels(self.extensionDistance / 2.0)
|
||||||
|
|
||||||
transition.updateFrame(node: self.contentContainer, frame: visualBackgroundFrame, beginWithCurrentState: true)
|
transition.updatePosition(node: self.contentContainer, position: visualBackgroundFrame.center, beginWithCurrentState: true)
|
||||||
|
|
||||||
|
if !self.contentContainer.bounds.equalTo(CGRect(origin: CGPoint(), size: visualBackgroundFrame.size)) {
|
||||||
|
transition.updateBounds(node: self.contentContainer, bounds: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size), beginWithCurrentState: true)
|
||||||
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.contentTintContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: visualBackgroundFrame.size), beginWithCurrentState: true)
|
transition.updateFrame(node: self.contentTintContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: visualBackgroundFrame.size), beginWithCurrentState: true)
|
||||||
transition.updateFrame(view: self.contentContainerMask, frame: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size), beginWithCurrentState: true)
|
transition.updateFrame(view: self.contentContainerMask, frame: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size), beginWithCurrentState: true)
|
||||||
transition.updateFrame(node: self.scrollNode, frame: scrollFrame, beginWithCurrentState: true)
|
transition.updateFrame(node: self.scrollNode, frame: scrollFrame, beginWithCurrentState: true)
|
||||||
@ -1287,11 +1292,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.backgroundNode.animateInFromAnchorRect(size: visualBackgroundFrame.size, sourceBackgroundFrame: sourceBackgroundFrame.offsetBy(dx: -visualBackgroundFrame.minX, dy: -visualBackgroundFrame.minY))
|
self.backgroundNode.animateInFromAnchorRect(size: visualBackgroundFrame.size, sourceBackgroundFrame: sourceBackgroundFrame.offsetBy(dx: -visualBackgroundFrame.minX, dy: -visualBackgroundFrame.minY))
|
||||||
|
|
||||||
|
let xOffset = sourceBackgroundFrame.midX - visualBackgroundFrame.midX
|
||||||
self.animateInInfo = (sourceBackgroundFrame.minX - visualBackgroundFrame.minX, visualBackgroundFrame.width)
|
self.animateInInfo = (sourceBackgroundFrame.minX - visualBackgroundFrame.minX, visualBackgroundFrame.width)
|
||||||
self.contentContainer.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - visualBackgroundFrame.midX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
self.contentContainer.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: xOffset, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||||
self.contentContainer.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(x: (sourceBackgroundFrame.minX - visualBackgroundFrame.minX), y: 0.0), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
self.contentContainer.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(x: (sourceBackgroundFrame.minX - visualBackgroundFrame.minX), y: 0.0), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||||
|
|
||||||
self.contentTintContainer.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - visualBackgroundFrame.midX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
self.contentTintContainer.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: xOffset, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||||
self.contentTintContainer.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(x: (sourceBackgroundFrame.minX - visualBackgroundFrame.minX), y: 0.0), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
self.contentTintContainer.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(x: (sourceBackgroundFrame.minX - visualBackgroundFrame.minX), y: 0.0), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||||
} else if let animateOutToAnchorRect = animateOutToAnchorRect {
|
} else if let animateOutToAnchorRect = animateOutToAnchorRect {
|
||||||
let targetBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: backgroundInsets, anchorRect: animateOutToAnchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight), centerAligned: false).0
|
let targetBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: backgroundInsets, anchorRect: animateOutToAnchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight), centerAligned: false).0
|
||||||
|
@ -1700,7 +1700,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
if let headerTextView = headerText.view {
|
if let headerTextView = headerText.view {
|
||||||
if headerTextView.superview == nil {
|
if headerTextView.superview == nil {
|
||||||
headerTextView.layer.anchorPoint = CGPoint()
|
headerTextView.layer.anchorPoint = CGPoint()
|
||||||
self.view.addSubview(headerTextView)
|
self.view.insertSubview(headerTextView, at: 0)
|
||||||
}
|
}
|
||||||
headerTextView.center = headerTextFrame.origin
|
headerTextView.center = headerTextFrame.origin
|
||||||
headerTextView.bounds = CGRect(origin: CGPoint(), size: headerTextFrame.size)
|
headerTextView.bounds = CGRect(origin: CGPoint(), size: headerTextFrame.size)
|
||||||
|
@ -119,6 +119,11 @@ public final class EngineStoryViewListContext {
|
|||||||
if let parentSource = parentSource, (parentSource.listMode == .everyone || parentSource.listMode == listMode), let parentState = parentSource.state, parentState.totalCount <= 100 {
|
if let parentSource = parentSource, (parentSource.listMode == .everyone || parentSource.listMode == listMode), let parentState = parentSource.state, parentState.totalCount <= 100 {
|
||||||
self.parentSource = parentSource
|
self.parentSource = parentSource
|
||||||
|
|
||||||
|
let matchesMode = parentSource.listMode == listMode
|
||||||
|
if parentState.items.count < 100 && !matchesMode {
|
||||||
|
parentSource.loadMore()
|
||||||
|
}
|
||||||
|
|
||||||
self.disposable.set((parentSource.statePromise.get()
|
self.disposable.set((parentSource.statePromise.get()
|
||||||
|> mapToSignal { state -> Signal<InternalState, NoError> in
|
|> mapToSignal { state -> Signal<InternalState, NoError> in
|
||||||
let needUpdate: Signal<Void, NoError>
|
let needUpdate: Signal<Void, NoError>
|
||||||
@ -148,10 +153,10 @@ public final class EngineStoryViewListContext {
|
|||||||
return needUpdate
|
return needUpdate
|
||||||
|> mapToSignal { _ -> Signal<InternalState, NoError> in
|
|> mapToSignal { _ -> Signal<InternalState, NoError> in
|
||||||
return account.postbox.transaction { transaction -> InternalState in
|
return account.postbox.transaction { transaction -> InternalState in
|
||||||
if state.canLoadMore {
|
/*if state.canLoadMore && !matchesMode {
|
||||||
return InternalState(
|
return InternalState(
|
||||||
totalCount: 0, totalReactedCount: 0, items: [], canLoadMore: true, nextOffset: state.nextOffset)
|
totalCount: listMode == .everyone ? state.totalCount : 100, totalReactedCount: state.totalReactedCount, items: [], canLoadMore: true, nextOffset: state.nextOffset)
|
||||||
}
|
}*/
|
||||||
|
|
||||||
var items: [Item] = []
|
var items: [Item] = []
|
||||||
switch listMode {
|
switch listMode {
|
||||||
@ -188,6 +193,7 @@ public final class EngineStoryViewListContext {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var totalCount = items.count
|
||||||
var totalReactedCount = 0
|
var totalReactedCount = 0
|
||||||
for item in items {
|
for item in items {
|
||||||
if item.reaction != nil {
|
if item.reaction != nil {
|
||||||
@ -195,8 +201,17 @@ public final class EngineStoryViewListContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state.canLoadMore {
|
||||||
|
totalCount = state.totalCount
|
||||||
|
totalReactedCount = state.totalReactedCount
|
||||||
|
}
|
||||||
|
|
||||||
return InternalState(
|
return InternalState(
|
||||||
totalCount: items.count, totalReactedCount: totalReactedCount, items: items, canLoadMore: false)
|
totalCount: totalCount,
|
||||||
|
totalReactedCount: totalReactedCount,
|
||||||
|
items: items,
|
||||||
|
canLoadMore: state.canLoadMore
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,7 +222,7 @@ public final class EngineStoryViewListContext {
|
|||||||
self.updateInternalState(state: state)
|
self.updateInternalState(state: state)
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
let initialState = State(totalCount: views.seenCount, totalReactedCount: views.reactedCount, items: [], loadMoreToken: LoadMoreToken(value: ""))
|
let initialState = State(totalCount: listMode == .everyone ? views.seenCount : 100, totalReactedCount: views.reactedCount, items: [], loadMoreToken: LoadMoreToken(value: ""))
|
||||||
let state = InternalState(totalCount: initialState.totalCount, totalReactedCount: initialState.totalReactedCount, items: initialState.items, canLoadMore: initialState.loadMoreToken != nil, nextOffset: nil)
|
let state = InternalState(totalCount: initialState.totalCount, totalReactedCount: initialState.totalReactedCount, items: initialState.items, canLoadMore: initialState.loadMoreToken != nil, nextOffset: nil)
|
||||||
self.state = state
|
self.state = state
|
||||||
self.statePromise.set(.single(state))
|
self.statePromise.set(.single(state))
|
||||||
|
@ -1104,6 +1104,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
|
maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
|
||||||
queryTypes: [.mention],
|
queryTypes: [.mention],
|
||||||
alwaysDarkWhenHasText: false,
|
alwaysDarkWhenHasText: false,
|
||||||
|
resetInputContents: nil,
|
||||||
nextInputMode: { _ in return nextInputMode },
|
nextInputMode: { _ in return nextInputMode },
|
||||||
areVoiceMessagesAvailable: false,
|
areVoiceMessagesAvailable: false,
|
||||||
presentController: { [weak self] c in
|
presentController: { [weak self] c in
|
||||||
|
@ -254,6 +254,7 @@ final class StoryPreviewComponent: Component {
|
|||||||
maxLength: nil,
|
maxLength: nil,
|
||||||
queryTypes: [],
|
queryTypes: [],
|
||||||
alwaysDarkWhenHasText: false,
|
alwaysDarkWhenHasText: false,
|
||||||
|
resetInputContents: nil,
|
||||||
nextInputMode: { _ in return .stickers },
|
nextInputMode: { _ in return .stickers },
|
||||||
areVoiceMessagesAvailable: false,
|
areVoiceMessagesAvailable: false,
|
||||||
presentController: { _ in },
|
presentController: { _ in },
|
||||||
|
@ -100,6 +100,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
public let maxLength: Int?
|
public let maxLength: Int?
|
||||||
public let queryTypes: ContextQueryTypes
|
public let queryTypes: ContextQueryTypes
|
||||||
public let alwaysDarkWhenHasText: Bool
|
public let alwaysDarkWhenHasText: Bool
|
||||||
|
public let resetInputContents: SendMessageInput?
|
||||||
public let nextInputMode: (Bool) -> InputMode?
|
public let nextInputMode: (Bool) -> InputMode?
|
||||||
public let areVoiceMessagesAvailable: Bool
|
public let areVoiceMessagesAvailable: Bool
|
||||||
public let presentController: (ViewController) -> Void
|
public let presentController: (ViewController) -> Void
|
||||||
@ -149,6 +150,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
maxLength: Int?,
|
maxLength: Int?,
|
||||||
queryTypes: ContextQueryTypes,
|
queryTypes: ContextQueryTypes,
|
||||||
alwaysDarkWhenHasText: Bool,
|
alwaysDarkWhenHasText: Bool,
|
||||||
|
resetInputContents: SendMessageInput?,
|
||||||
nextInputMode: @escaping (Bool) -> InputMode?,
|
nextInputMode: @escaping (Bool) -> InputMode?,
|
||||||
areVoiceMessagesAvailable: Bool,
|
areVoiceMessagesAvailable: Bool,
|
||||||
presentController: @escaping (ViewController) -> Void,
|
presentController: @escaping (ViewController) -> Void,
|
||||||
@ -198,6 +200,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
self.maxLength = maxLength
|
self.maxLength = maxLength
|
||||||
self.queryTypes = queryTypes
|
self.queryTypes = queryTypes
|
||||||
self.alwaysDarkWhenHasText = alwaysDarkWhenHasText
|
self.alwaysDarkWhenHasText = alwaysDarkWhenHasText
|
||||||
|
self.resetInputContents = resetInputContents
|
||||||
self.areVoiceMessagesAvailable = areVoiceMessagesAvailable
|
self.areVoiceMessagesAvailable = areVoiceMessagesAvailable
|
||||||
self.presentController = presentController
|
self.presentController = presentController
|
||||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||||
@ -265,6 +268,9 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
if lhs.alwaysDarkWhenHasText != rhs.alwaysDarkWhenHasText {
|
if lhs.alwaysDarkWhenHasText != rhs.alwaysDarkWhenHasText {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.resetInputContents != rhs.resetInputContents {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.areVoiceMessagesAvailable != rhs.areVoiceMessagesAvailable {
|
if lhs.areVoiceMessagesAvailable != rhs.areVoiceMessagesAvailable {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -334,7 +340,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SendMessageInput {
|
public enum SendMessageInput: Equatable {
|
||||||
case text(NSAttributedString)
|
case text(NSAttributedString)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,6 +392,8 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
private var component: MessageInputPanelComponent?
|
private var component: MessageInputPanelComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
private var pendingSetMessageInput: SendMessageInput?
|
||||||
|
|
||||||
public var likeButtonView: UIView? {
|
public var likeButtonView: UIView? {
|
||||||
return self.likeButton.view
|
return self.likeButton.view
|
||||||
}
|
}
|
||||||
@ -451,6 +459,17 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
return .text(textFieldView.getAttributedText())
|
return .text(textFieldView.getAttributedText())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func setSendMessageInput(value: SendMessageInput, updateState: Bool) {
|
||||||
|
if let textFieldView = self.textField.view as? TextFieldComponent.View {
|
||||||
|
switch value {
|
||||||
|
case let .text(text):
|
||||||
|
textFieldView.setAttributedText(text, updateState: updateState)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.pendingSetMessageInput = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func getAttachmentButtonView() -> UIView? {
|
public func getAttachmentButtonView() -> UIView? {
|
||||||
guard let attachmentButtonView = self.attachmentButton.view else {
|
guard let attachmentButtonView = self.attachmentButton.view else {
|
||||||
return nil
|
return nil
|
||||||
@ -458,9 +477,9 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
return attachmentButtonView
|
return attachmentButtonView
|
||||||
}
|
}
|
||||||
|
|
||||||
public func clearSendMessageInput() {
|
public func clearSendMessageInput(updateState: Bool) {
|
||||||
if let textFieldView = self.textField.view as? TextFieldComponent.View {
|
if let textFieldView = self.textField.view as? TextFieldComponent.View {
|
||||||
textFieldView.setAttributedText(NSAttributedString())
|
textFieldView.setAttributedText(NSAttributedString(), updateState: updateState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,6 +668,12 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
textColor: UIColor(rgb: 0xffffff),
|
textColor: UIColor(rgb: 0xffffff),
|
||||||
insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 48.0),
|
insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 48.0),
|
||||||
hideKeyboard: component.hideKeyboard,
|
hideKeyboard: component.hideKeyboard,
|
||||||
|
resetText: component.resetInputContents.flatMap { resetInputContents in
|
||||||
|
switch resetInputContents {
|
||||||
|
case let .text(value):
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
},
|
||||||
formatMenuAvailability: component.isFormattingLocked ? .locked : .available,
|
formatMenuAvailability: component.isFormattingLocked ? .locked : .available,
|
||||||
lockedFormatAction: {
|
lockedFormatAction: {
|
||||||
component.presentTextFormattingTooltip?()
|
component.presentTextFormattingTooltip?()
|
||||||
@ -714,9 +739,12 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
} else if isEditing {
|
} else if isEditing {
|
||||||
fieldBackgroundFrame = fieldFrame
|
fieldBackgroundFrame = fieldFrame
|
||||||
} else {
|
} else {
|
||||||
fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - insets.right, height: textFieldSize.height))
|
if component.forwardAction != nil && component.likeAction != nil {
|
||||||
if component.likeAction != nil && component.forwardAction != nil {
|
fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - insets.right - 49.0, height: textFieldSize.height))
|
||||||
fieldBackgroundFrame.size.width -= 49.0
|
} else if component.forwardAction != nil {
|
||||||
|
fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - insets.right, height: textFieldSize.height))
|
||||||
|
} else {
|
||||||
|
fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - 50.0, height: textFieldSize.height))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -762,13 +790,21 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
|
|
||||||
let size = CGSize(width: availableSize.width, height: textFieldSize.height + insets.top + insets.bottom)
|
let size = CGSize(width: availableSize.width, height: textFieldSize.height + insets.top + insets.bottom)
|
||||||
|
|
||||||
if let textFieldView = self.textField.view {
|
if let textFieldView = self.textField.view as? TextFieldComponent.View {
|
||||||
if textFieldView.superview == nil {
|
if textFieldView.superview == nil {
|
||||||
self.addSubview(textFieldView)
|
self.addSubview(textFieldView)
|
||||||
|
|
||||||
if let viewForOverlayContent = self.viewForOverlayContent {
|
if let viewForOverlayContent = self.viewForOverlayContent {
|
||||||
self.addSubview(viewForOverlayContent)
|
self.addSubview(viewForOverlayContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let pendingSetMessageInput = self.pendingSetMessageInput {
|
||||||
|
self.pendingSetMessageInput = nil
|
||||||
|
switch pendingSetMessageInput {
|
||||||
|
case let .text(text):
|
||||||
|
textFieldView.setAttributedText(text, updateState: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let textFieldFrame = CGRect(origin: CGPoint(x: fieldBackgroundFrame.minX, y: fieldBackgroundFrame.maxY - textFieldSize.height), size: textFieldSize)
|
let textFieldFrame = CGRect(origin: CGPoint(x: fieldBackgroundFrame.minX, y: fieldBackgroundFrame.maxY - textFieldSize.height), size: textFieldSize)
|
||||||
transition.setFrame(view: textFieldView, frame: textFieldFrame)
|
transition.setFrame(view: textFieldView, frame: textFieldFrame)
|
||||||
@ -1168,7 +1204,10 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
if likeButtonView.superview == nil {
|
if likeButtonView.superview == nil {
|
||||||
self.addSubview(likeButtonView)
|
self.addSubview(likeButtonView)
|
||||||
}
|
}
|
||||||
let likeButtonFrame = CGRect(origin: CGPoint(x: inputActionButtonOriginX, y: size.height - insets.bottom - baseFieldHeight + floor((baseFieldHeight - likeButtonSize.height) * 0.5)), size: likeButtonSize)
|
var likeButtonFrame = CGRect(origin: CGPoint(x: inputActionButtonOriginX, y: size.height - insets.bottom - baseFieldHeight + floor((baseFieldHeight - likeButtonSize.height) * 0.5)), size: likeButtonSize)
|
||||||
|
if component.forwardAction == nil {
|
||||||
|
likeButtonFrame.origin.x += 3.0
|
||||||
|
}
|
||||||
transition.setPosition(view: likeButtonView, position: likeButtonFrame.center)
|
transition.setPosition(view: likeButtonView, position: likeButtonFrame.center)
|
||||||
transition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size))
|
transition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size))
|
||||||
transition.setAlpha(view: likeButtonView, alpha: displayLikeAction ? 1.0 : 0.0)
|
transition.setAlpha(view: likeButtonView, alpha: displayLikeAction ? 1.0 : 0.0)
|
||||||
|
@ -90,7 +90,6 @@ public final class NavigationSearchComponent: Component {
|
|||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.backgroundView = UIView()
|
self.backgroundView = UIView()
|
||||||
self.backgroundView.layer.cornerRadius = 10.0
|
|
||||||
|
|
||||||
self.searchIconView = UIImageView(image: UIImage(bundleImageName: "Components/Search Bar/Loupe")?.withRenderingMode(.alwaysTemplate))
|
self.searchIconView = UIImageView(image: UIImage(bundleImageName: "Components/Search Bar/Loupe")?.withRenderingMode(.alwaysTemplate))
|
||||||
|
|
||||||
@ -183,6 +182,9 @@ public final class NavigationSearchComponent: Component {
|
|||||||
let searchIconSpacing: CGFloat = 4.0
|
let searchIconSpacing: CGFloat = 4.0
|
||||||
let buttonSpacing: CGFloat = 8.0
|
let buttonSpacing: CGFloat = 8.0
|
||||||
|
|
||||||
|
let contentAlphaDistance: CGFloat = 0.35
|
||||||
|
let contentAlpha: CGFloat = 1.0 - max(0.0, min(1.0, component.collapseFraction / contentAlphaDistance))
|
||||||
|
|
||||||
let rightInset: CGFloat
|
let rightInset: CGFloat
|
||||||
if component.isSearchActive {
|
if component.isSearchActive {
|
||||||
var buttonTransition = transition
|
var buttonTransition = transition
|
||||||
@ -235,8 +237,10 @@ public final class NavigationSearchComponent: Component {
|
|||||||
rightInset = sideInset
|
rightInset = sideInset
|
||||||
}
|
}
|
||||||
|
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - fieldHeight) * 0.5)), size: CGSize(width: availableSize.width - sideInset - rightInset, height: fieldHeight))
|
let backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - fieldHeight) * 0.5)), size: CGSize(width: availableSize.width - sideInset - rightInset, height: fieldHeight * (1.0 - component.collapseFraction)))
|
||||||
|
|
||||||
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
|
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
|
||||||
|
transition.setCornerRadius(layer: self.backgroundView.layer, cornerRadius: min(10.0, backgroundFrame.height * 0.5))
|
||||||
if previousComponent?.colors.background != component.colors.background {
|
if previousComponent?.colors.background != component.colors.background {
|
||||||
self.backgroundView.backgroundColor = component.colors.background
|
self.backgroundView.backgroundColor = component.colors.background
|
||||||
}
|
}
|
||||||
@ -255,7 +259,7 @@ public final class NavigationSearchComponent: Component {
|
|||||||
let searchIconSize = self.searchIconView.image?.size ?? CGSize(width: 20.0, height: 20.0)
|
let searchIconSize = self.searchIconView.image?.size ?? CGSize(width: 20.0, height: 20.0)
|
||||||
//let searchPlaceholderCombinedWidth = searchIconSize.width + searchIconSpacing + placeholderSize.width
|
//let searchPlaceholderCombinedWidth = searchIconSize.width + searchIconSpacing + placeholderSize.width
|
||||||
|
|
||||||
let placeholderTextFrame = CGRect(origin: CGPoint(x: component.isSearchActive ? (backgroundFrame.minX + fieldSideInset + searchIconSize.width + searchIconSpacing) : floor(backgroundFrame.midX - placeholderSize.width * 0.5), y: backgroundFrame.minY + floor((fieldHeight - placeholderSize.height) * 0.5)), size: placeholderSize)
|
let placeholderTextFrame = CGRect(origin: CGPoint(x: component.isSearchActive ? (backgroundFrame.minX + fieldSideInset + searchIconSize.width + searchIconSpacing) : floor(backgroundFrame.midX - placeholderSize.width * 0.5), y: backgroundFrame.minY + (backgroundFrame.height - placeholderSize.height) * 0.5), size: placeholderSize)
|
||||||
var placeholderDeltaX: CGFloat = 0.0
|
var placeholderDeltaX: CGFloat = 0.0
|
||||||
if let placeholderTextView = self.placeholderText.view {
|
if let placeholderTextView = self.placeholderText.view {
|
||||||
if placeholderTextView.superview == nil {
|
if placeholderTextView.superview == nil {
|
||||||
@ -267,14 +271,16 @@ public final class NavigationSearchComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setPosition(view: placeholderTextView, position: placeholderTextFrame.origin)
|
transition.setPosition(view: placeholderTextView, position: placeholderTextFrame.origin)
|
||||||
transition.setBounds(view: placeholderTextView, bounds: CGRect(origin: CGPoint(), size: placeholderTextFrame.size))
|
transition.setBounds(view: placeholderTextView, bounds: CGRect(origin: CGPoint(), size: placeholderTextFrame.size))
|
||||||
|
transition.setAlpha(view: placeholderTextView, alpha: contentAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
let searchIconFrame = CGRect(origin: CGPoint(x: placeholderTextFrame.minX - searchIconSpacing - searchIconSize.width, y: backgroundFrame.minY + floor((fieldHeight - searchIconSize.height) * 0.5)), size: searchIconSize)
|
let searchIconFrame = CGRect(origin: CGPoint(x: placeholderTextFrame.minX - searchIconSpacing - searchIconSize.width, y: backgroundFrame.minY + (backgroundFrame.height - searchIconSize.height) * 0.5), size: searchIconSize)
|
||||||
transition.setFrame(view: self.searchIconView, frame: searchIconFrame)
|
transition.setFrame(view: self.searchIconView, frame: searchIconFrame)
|
||||||
|
transition.setAlpha(view: self.searchIconView, alpha: contentAlpha)
|
||||||
|
|
||||||
if let image = self.clearIconView.image {
|
if let image = self.clearIconView.image {
|
||||||
let clearSize = image.size
|
let clearSize = image.size
|
||||||
let clearFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - 6.0 - clearSize.width, y: backgroundFrame.minY + floor((backgroundFrame.height - clearSize.height) / 2.0)), size: clearSize)
|
let clearFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - 6.0 - clearSize.width, y: backgroundFrame.minY + (backgroundFrame.height - clearSize.height) / 2.0), size: clearSize)
|
||||||
|
|
||||||
let clearButtonFrame = CGRect(origin: CGPoint(x: clearFrame.minX - 4.0, y: backgroundFrame.minY), size: CGSize(width: clearFrame.width + 8.0, height: backgroundFrame.height))
|
let clearButtonFrame = CGRect(origin: CGPoint(x: clearFrame.minX - 4.0, y: backgroundFrame.minY), size: CGSize(width: clearFrame.width + 8.0, height: backgroundFrame.height))
|
||||||
transition.setFrame(view: self.clearButton, frame: clearButtonFrame)
|
transition.setFrame(view: self.clearButton, frame: clearButtonFrame)
|
||||||
|
@ -964,9 +964,6 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.didDisplayReactionTooltip = true
|
self.didDisplayReactionTooltip = true
|
||||||
#if !DEBUG
|
|
||||||
let _ = ApplicationSpecificNotice.setDisplayStoryReactionTooltip(accountManager: component.context.sharedContext.accountManager).start()
|
|
||||||
#endif
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1558,9 +1555,6 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
itemSetTransition.setPosition(view: itemSetView, position: itemFrame.center.offsetBy(dx: 0.0, dy: dismissPanOffset))
|
itemSetTransition.setPosition(view: itemSetView, position: itemFrame.center.offsetBy(dx: 0.0, dy: dismissPanOffset))
|
||||||
itemSetTransition.setBounds(view: itemSetView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
itemSetTransition.setBounds(view: itemSetView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||||
|
|
||||||
itemSetTransition.setPosition(view: itemSetComponentView.transitionCloneContainerView, position: itemFrame.center.offsetBy(dx: 0.0, dy: dismissPanOffset))
|
|
||||||
itemSetTransition.setBounds(view: itemSetComponentView.transitionCloneContainerView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
|
||||||
|
|
||||||
itemSetTransition.setPosition(view: itemSetComponentView, position: CGRect(origin: CGPoint(), size: itemFrame.size).center)
|
itemSetTransition.setPosition(view: itemSetComponentView, position: CGRect(origin: CGPoint(), size: itemFrame.size).center)
|
||||||
itemSetTransition.setBounds(view: itemSetComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
itemSetTransition.setBounds(view: itemSetComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ public final class StoryContentItem: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class SharedState {
|
public final class SharedState {
|
||||||
|
public var replyDrafts: [StoryId: NSAttributedString] = [:]
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ import Speak
|
|||||||
import TranslateUI
|
import TranslateUI
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import StoryFooterPanelComponent
|
import StoryFooterPanelComponent
|
||||||
|
import TelegramNotices
|
||||||
|
|
||||||
public final class StoryAvailableReactions: Equatable {
|
public final class StoryAvailableReactions: Equatable {
|
||||||
let reactionItems: [ReactionItem]
|
let reactionItems: [ReactionItem]
|
||||||
@ -349,6 +350,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
var startContentOffsetY: CGFloat = 0.0
|
var startContentOffsetY: CGFloat = 0.0
|
||||||
var accumulatedOffset: CGFloat = 0.0
|
var accumulatedOffset: CGFloat = 0.0
|
||||||
var dismissedTooltips: Bool = false
|
var dismissedTooltips: Bool = false
|
||||||
|
var didLockScrolling: Bool = false
|
||||||
|
var contentOffset: CGFloat?
|
||||||
|
|
||||||
init(fraction: CGFloat, scrollView: UIScrollView?) {
|
init(fraction: CGFloat, scrollView: UIScrollView?) {
|
||||||
self.fraction = fraction
|
self.fraction = fraction
|
||||||
@ -423,6 +426,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
var viewListDisplayState: ViewListDisplayState = .hidden
|
var viewListDisplayState: ViewListDisplayState = .hidden
|
||||||
private var targetViewListDisplayStateIsFull: Bool = false
|
private var targetViewListDisplayStateIsFull: Bool = false
|
||||||
|
private var viewListMetrics: (minHeight: CGFloat, maxHeight: CGFloat, currentHeight: CGFloat)?
|
||||||
|
|
||||||
var isSearchActive: Bool = false
|
var isSearchActive: Bool = false
|
||||||
|
|
||||||
@ -1014,7 +1018,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
verticalPanState.fraction = fraction
|
verticalPanState.fraction = fraction
|
||||||
} else {
|
} else {
|
||||||
var targetScrollView: UIScrollView?
|
var targetScrollView: UIScrollView?
|
||||||
if self.viewListDisplayState != .hidden, let viewList = self.viewLists[component.slice.item.storyItem.id], let viewListView = viewList.view.view as? StoryItemSetViewListComponent.View {
|
if case .began = recognizer.state, self.viewListDisplayState != .hidden, let viewList = self.viewLists[component.slice.item.storyItem.id], let viewListView = viewList.view.view as? StoryItemSetViewListComponent.View {
|
||||||
if let hitResult = viewListView.hitTest(self.convert(recognizer.location(in: self), to: viewListView), with: nil) {
|
if let hitResult = viewListView.hitTest(self.convert(recognizer.location(in: self), to: viewListView), with: nil) {
|
||||||
func findTargetScrollView(target: UIView, minParent: UIView) -> UIScrollView? {
|
func findTargetScrollView(target: UIView, minParent: UIView) -> UIScrollView? {
|
||||||
if target === viewListView {
|
if target === viewListView {
|
||||||
@ -1041,14 +1045,42 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.dismissAllTooltips()
|
self.dismissAllTooltips()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let scrollView = verticalPanState.scrollView {
|
if let viewListMetrics = self.viewListMetrics, let scrollView = verticalPanState.scrollView {
|
||||||
let relativeTranslationY = recognizer.translation(in: self).y - verticalPanState.startContentOffsetY
|
let relativeTranslationY = recognizer.translation(in: self).y - verticalPanState.startContentOffsetY
|
||||||
let overflowY = scrollView.contentOffset.y - relativeTranslationY
|
let overflowY = scrollView.contentOffset.y - relativeTranslationY
|
||||||
|
|
||||||
verticalPanState.accumulatedOffset += -overflowY
|
if !verticalPanState.didLockScrolling {
|
||||||
verticalPanState.accumulatedOffset = max(0.0, verticalPanState.accumulatedOffset)
|
if scrollView.contentOffset.y == 0.0 {
|
||||||
|
verticalPanState.didLockScrolling = true
|
||||||
|
}
|
||||||
|
if let previousContentOffset = verticalPanState.contentOffset, (previousContentOffset < 0.0) != (scrollView.contentOffset.y < 0.0) {
|
||||||
|
verticalPanState.didLockScrolling = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if verticalPanState.accumulatedOffset > 0.0 {
|
var resetContentOffset = false
|
||||||
|
if self.viewListDisplayState == .half && verticalPanState.didLockScrolling {
|
||||||
|
verticalPanState.accumulatedOffset += -overflowY
|
||||||
|
if scrollView.contentOffset.y > 0.0 {
|
||||||
|
//verticalPanState.accumulatedOffset = max(0.0, verticalPanState.accumulatedOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewListHeight = viewListMetrics.minHeight - verticalPanState.accumulatedOffset
|
||||||
|
let viewListExpandDistance = viewListHeight - viewListMetrics.maxHeight
|
||||||
|
let viewListOffsetOverflow = max(0.0, viewListExpandDistance)
|
||||||
|
resetContentOffset = viewListOffsetOverflow <= 0.0
|
||||||
|
//print("viewListOffsetOverflow: \(viewListOffsetOverflow)")
|
||||||
|
|
||||||
|
if verticalPanState.accumulatedOffset < -(viewListMetrics.maxHeight - viewListMetrics.minHeight) {
|
||||||
|
verticalPanState.accumulatedOffset = -(viewListMetrics.maxHeight - viewListMetrics.minHeight)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
verticalPanState.accumulatedOffset += -overflowY
|
||||||
|
verticalPanState.accumulatedOffset = max(0.0, verticalPanState.accumulatedOffset)
|
||||||
|
}
|
||||||
|
//print("accumulatedOffset: \(verticalPanState.accumulatedOffset)")
|
||||||
|
|
||||||
|
if verticalPanState.accumulatedOffset > 0.0 || resetContentOffset {
|
||||||
scrollView.contentOffset = CGPoint()
|
scrollView.contentOffset = CGPoint()
|
||||||
|
|
||||||
if self.viewListDisplayState != .hidden, let viewList = self.viewLists[component.slice.item.storyItem.id], let viewListView = viewList.view.view as? StoryItemSetViewListComponent.View {
|
if self.viewListDisplayState != .hidden, let viewList = self.viewLists[component.slice.item.storyItem.id], let viewListView = viewList.view.view as? StoryItemSetViewListComponent.View {
|
||||||
@ -1065,6 +1097,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verticalPanState.contentOffset = scrollView.contentOffset.y
|
||||||
verticalPanState.startContentOffsetY = recognizer.translation(in: self).y
|
verticalPanState.startContentOffsetY = recognizer.translation(in: self).y
|
||||||
} else {
|
} else {
|
||||||
if verticalPanState.fraction <= -0.15 {
|
if verticalPanState.fraction <= -0.15 {
|
||||||
@ -1099,8 +1132,15 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
|
} else if verticalPanState.accumulatedOffset < 0.0 && self.targetViewListDisplayStateIsFull {
|
||||||
|
if verticalPanState.fraction <= -0.05 || velocity.y <= -80.0 {
|
||||||
|
self.viewListDisplayState = .full
|
||||||
|
} else {
|
||||||
|
self.viewListDisplayState = .half
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: verticalPanState.accumulatedOffset == 0.0 ? .immediate : Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
} else {
|
} else {
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: verticalPanState.accumulatedOffset == 0.0 ? .immediate : Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if verticalPanState.fraction >= 0.3 || (verticalPanState.fraction >= 0.05 && velocity.y >= 150.0) {
|
if verticalPanState.fraction >= 0.3 || (verticalPanState.fraction >= 0.05 && velocity.y >= 150.0) {
|
||||||
@ -1654,9 +1694,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
footerAlpha = 0.0
|
footerAlpha = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .regular = component.metrics.widthClass {
|
footerAlpha *= itemAlpha
|
||||||
footerAlpha *= itemAlpha
|
|
||||||
}
|
|
||||||
|
|
||||||
itemTransition.setAlpha(view: footerPanelView, alpha: footerAlpha)
|
itemTransition.setAlpha(view: footerPanelView, alpha: footerAlpha)
|
||||||
}
|
}
|
||||||
@ -1983,7 +2021,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
let contentContainerView = visibleItem.contentContainerView
|
let contentContainerView = visibleItem.contentContainerView
|
||||||
let unclippedContainerView = visibleItem.unclippedContainerView
|
let unclippedContainerView = visibleItem.unclippedContainerView
|
||||||
|
|
||||||
let sourceLocalFrame = sourceView.convert(transitionOut.destinationRect, to: self)
|
let sourceLocalFrame = sourceView.convert(transitionOut.destinationRect, to: self.componentContainerView)
|
||||||
let innerSourceLocalFrame = CGRect(origin: CGPoint(x: sourceLocalFrame.minX - contentContainerView.frame.minX, y: sourceLocalFrame.minY - contentContainerView.frame.minY), size: sourceLocalFrame.size)
|
let innerSourceLocalFrame = CGRect(origin: CGPoint(x: sourceLocalFrame.minX - contentContainerView.frame.minX, y: sourceLocalFrame.minY - contentContainerView.frame.minY), size: sourceLocalFrame.size)
|
||||||
|
|
||||||
let contentSourceFrame = contentContainerView.frame
|
let contentSourceFrame = contentContainerView.frame
|
||||||
@ -2280,6 +2318,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.sendMessageContext.tooltipScreen = tooltipScreen
|
self.sendMessageContext.tooltipScreen = tooltipScreen
|
||||||
self.updateIsProgressPaused()
|
self.updateIsProgressPaused()
|
||||||
component.controller()?.present(tooltipScreen, in: .current)
|
component.controller()?.present(tooltipScreen, in: .current)
|
||||||
|
|
||||||
|
let _ = ApplicationSpecificNotice.setDisplayStoryReactionTooltip(accountManager: component.context.sharedContext.accountManager).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(component: StoryItemSetContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(component: StoryItemSetContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
@ -2287,17 +2327,46 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
if self.component == nil {
|
if self.component == nil {
|
||||||
self.sendMessageContext.setup(context: component.context, view: self, inputPanelExternalState: self.inputPanelExternalState, keyboardInputData: component.keyboardInputData)
|
self.sendMessageContext.setup(context: component.context, view: self, inputPanelExternalState: self.inputPanelExternalState, keyboardInputData: component.keyboardInputData)
|
||||||
|
|
||||||
|
/*#if DEBUG
|
||||||
|
class Target: NSObject {
|
||||||
|
let f: () -> Void
|
||||||
|
|
||||||
|
init(_ f: @escaping () -> Void) {
|
||||||
|
self.f = f
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func event() {
|
||||||
|
self.f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let displayLink = CADisplayLink(target: Target { [weak self] in
|
||||||
|
self?.state?.updated(transition: .easeInOut(duration: 0.3))
|
||||||
|
}, selector: #selector(Target.event))
|
||||||
|
displayLink.add(to: .main, forMode: .common)
|
||||||
|
#endif*/
|
||||||
}
|
}
|
||||||
|
|
||||||
var itemChanged = false
|
var itemChanged = false
|
||||||
|
var resetInputContents: MessageInputPanelComponent.SendMessageInput?
|
||||||
if self.component?.slice.item.storyItem.id != component.slice.item.storyItem.id {
|
if self.component?.slice.item.storyItem.id != component.slice.item.storyItem.id {
|
||||||
itemChanged = self.component != nil
|
itemChanged = self.component != nil
|
||||||
self.initializedOffset = false
|
self.initializedOffset = false
|
||||||
|
|
||||||
|
resetInputContents = .text(NSAttributedString())
|
||||||
|
|
||||||
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View {
|
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View {
|
||||||
Queue.mainQueue().justDispatch {
|
if let previousComponent = self.component {
|
||||||
inputPanelView.clearSendMessageInput()
|
let previousInput = inputPanelView.getSendMessageInput()
|
||||||
|
switch previousInput {
|
||||||
|
case let .text(value):
|
||||||
|
component.storyItemSharedState.replyDrafts[StoryId(peerId: previousComponent.slice.peer.id, id: previousComponent.slice.item.storyItem.id)] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if let draft = component.storyItemSharedState.replyDrafts[StoryId(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id)] {
|
||||||
|
resetInputContents = .text(draft)
|
||||||
|
}
|
||||||
|
component.storyItemSharedState.replyDrafts.removeValue(forKey: StoryId(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let tooltipScreen = self.sendMessageContext.tooltipScreen {
|
if let tooltipScreen = self.sendMessageContext.tooltipScreen {
|
||||||
@ -2405,6 +2474,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
transition.setBounds(view: self.componentContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setBounds(view: self.componentContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
transition.setScale(view: self.componentContainerView, scale: dismissPanScale)
|
transition.setScale(view: self.componentContainerView, scale: dismissPanScale)
|
||||||
|
|
||||||
|
transition.setPosition(view: self.transitionCloneContainerView, position: CGRect(origin: CGPoint(), size: availableSize).center.offsetBy(dx: 0.0, dy: dismissPanOffset))
|
||||||
|
transition.setBounds(view: self.transitionCloneContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
|
|
||||||
transition.setPosition(view: self.overlayContainerView, position: CGPoint(x: availableSize.width * 0.5, y: availableSize.height * 0.5 + dismissPanOffset))
|
transition.setPosition(view: self.overlayContainerView, position: CGPoint(x: availableSize.width * 0.5, y: availableSize.height * 0.5 + dismissPanOffset))
|
||||||
transition.setBounds(view: self.overlayContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setBounds(view: self.overlayContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
transition.setScale(view: self.overlayContainerView, scale: dismissPanScale)
|
transition.setScale(view: self.overlayContainerView, scale: dismissPanScale)
|
||||||
@ -2428,6 +2500,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if itemChanged {
|
||||||
|
inputPanelTransition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
var isUnsupported = false
|
var isUnsupported = false
|
||||||
var disabledPlaceholder: String?
|
var disabledPlaceholder: String?
|
||||||
@ -2473,6 +2548,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
maxLength: 4096,
|
maxLength: 4096,
|
||||||
queryTypes: [.mention, .emoji],
|
queryTypes: [.mention, .emoji],
|
||||||
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
|
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
|
||||||
|
resetInputContents: resetInputContents,
|
||||||
nextInputMode: { [weak self] hasText in
|
nextInputMode: { [weak self] hasText in
|
||||||
if case .media = self?.sendMessageContext.currentInputMode {
|
if case .media = self?.sendMessageContext.currentInputMode {
|
||||||
return .text
|
return .text
|
||||||
@ -2834,6 +2910,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
viewListHeight = max(minViewListHeight, min(maxViewListHeight, viewListHeight))
|
viewListHeight = max(minViewListHeight, min(maxViewListHeight, viewListHeight))
|
||||||
|
|
||||||
self.targetViewListDisplayStateIsFull = viewListHeight > midViewListHeight
|
self.targetViewListDisplayStateIsFull = viewListHeight > midViewListHeight
|
||||||
|
self.viewListMetrics = (midViewListHeight, maxViewListHeight, viewListHeight)
|
||||||
|
|
||||||
let viewListHeightMidFraction: CGFloat = max(0.0, min(1.0, viewListHeight / midViewListHeight))
|
let viewListHeightMidFraction: CGFloat = max(0.0, min(1.0, viewListHeight / midViewListHeight))
|
||||||
viewListInset = defaultHeight * (1.0 - viewListHeightMidFraction) + viewListHeightMidFraction * midViewListHeight
|
viewListInset = defaultHeight * (1.0 - viewListHeightMidFraction) + viewListHeightMidFraction * midViewListHeight
|
||||||
@ -3219,6 +3296,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.viewListMetrics = nil
|
||||||
}
|
}
|
||||||
var removeViewListIds: [Int32] = []
|
var removeViewListIds: [Int32] = []
|
||||||
for (id, viewList) in self.viewLists {
|
for (id, viewList) in self.viewLists {
|
||||||
|
@ -601,7 +601,8 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
inputPanelView.clearSendMessageInput()
|
component.storyItemSharedState.replyDrafts.removeValue(forKey: StoryId(peerId: peerId, id: focusedItem.storyItem.id))
|
||||||
|
inputPanelView.clearSendMessageInput(updateState: true)
|
||||||
|
|
||||||
self.currentInputMode = .text
|
self.currentInputMode = .text
|
||||||
if hasFirstResponder(view) {
|
if hasFirstResponder(view) {
|
||||||
@ -1259,7 +1260,16 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else {
|
guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inputPanelView.clearSendMessageInput()
|
inputPanelView.clearSendMessageInput(updateState: true)
|
||||||
|
|
||||||
|
guard let component = view.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let focusedItem = component.slice.item
|
||||||
|
guard let peerId = focusedItem.peerId else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.storyItemSharedState.replyDrafts.removeValue(forKey: StoryId(peerId: peerId, id: focusedItem.storyItem.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AttachMenuSubject {
|
enum AttachMenuSubject {
|
||||||
|
@ -159,23 +159,27 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
|
|
||||||
private struct ItemLayout: Equatable {
|
private struct ItemLayout: Equatable {
|
||||||
var containerSize: CGSize
|
var containerSize: CGSize
|
||||||
|
var availableSize: CGSize
|
||||||
var bottomInset: CGFloat
|
var bottomInset: CGFloat
|
||||||
var topInset: CGFloat
|
var topInset: CGFloat
|
||||||
var sideInset: CGFloat
|
var sideInset: CGFloat
|
||||||
var itemHeight: CGFloat
|
var itemHeight: CGFloat
|
||||||
var itemCount: Int
|
var itemCount: Int
|
||||||
var premiumFooterSize: CGSize?
|
var premiumFooterSize: CGSize?
|
||||||
|
var isSearchActive: Bool
|
||||||
|
|
||||||
var contentSize: CGSize
|
var contentSize: CGSize
|
||||||
|
|
||||||
init(containerSize: CGSize, bottomInset: CGFloat, topInset: CGFloat, sideInset: CGFloat, itemHeight: CGFloat, itemCount: Int, premiumFooterSize: CGSize?) {
|
init(containerSize: CGSize, availableSize: CGSize, bottomInset: CGFloat, topInset: CGFloat, sideInset: CGFloat, itemHeight: CGFloat, itemCount: Int, premiumFooterSize: CGSize?, isSearchActive: Bool) {
|
||||||
self.containerSize = containerSize
|
self.containerSize = containerSize
|
||||||
|
self.availableSize = availableSize
|
||||||
self.bottomInset = bottomInset
|
self.bottomInset = bottomInset
|
||||||
self.topInset = topInset
|
self.topInset = topInset
|
||||||
self.sideInset = sideInset
|
self.sideInset = sideInset
|
||||||
self.itemHeight = itemHeight
|
self.itemHeight = itemHeight
|
||||||
self.itemCount = itemCount
|
self.itemCount = itemCount
|
||||||
self.premiumFooterSize = premiumFooterSize
|
self.premiumFooterSize = premiumFooterSize
|
||||||
|
self.isSearchActive = isSearchActive
|
||||||
|
|
||||||
self.contentSize = CGSize(width: containerSize.width, height: topInset + CGFloat(itemCount) * itemHeight + bottomInset)
|
self.contentSize = CGSize(width: containerSize.width, height: topInset + CGFloat(itemCount) * itemHeight + bottomInset)
|
||||||
if let premiumFooterSize {
|
if let premiumFooterSize {
|
||||||
@ -245,6 +249,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
let configuration: ContentConfigurationKey
|
let configuration: ContentConfigurationKey
|
||||||
var query: String?
|
var query: String?
|
||||||
|
|
||||||
|
var stateComponent: StoryItemSetViewListComponent?
|
||||||
var component: StoryItemSetViewListComponent?
|
var component: StoryItemSetViewListComponent?
|
||||||
weak var state: EmptyComponentState?
|
weak var state: EmptyComponentState?
|
||||||
|
|
||||||
@ -284,6 +289,15 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
|
|
||||||
var dismissInput: (() -> Void)?
|
var dismissInput: (() -> Void)?
|
||||||
|
|
||||||
|
var navigationSearch: ComponentView<Empty>?
|
||||||
|
var updateQuery: ((String) -> Void)?
|
||||||
|
var navigationBarBackground: BlurredBackgroundView?
|
||||||
|
var navigationSeparator: SimpleLayer?
|
||||||
|
var backgroundView: UIView?
|
||||||
|
|
||||||
|
var navigationHeight: CGFloat?
|
||||||
|
var navigationSearchPartHeight: CGFloat?
|
||||||
|
|
||||||
init(configuration: ContentConfigurationKey) {
|
init(configuration: ContentConfigurationKey) {
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
|
|
||||||
@ -329,6 +343,28 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
if eventCycleState.ignoreScrolling {
|
if eventCycleState.ignoreScrolling {
|
||||||
targetContentOffset.pointee.y = 0.0
|
targetContentOffset.pointee.y = 0.0
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if let navigationSearchPartHeight = self.navigationSearchPartHeight, navigationSearchPartHeight > 0.0 {
|
||||||
|
if targetContentOffset.pointee.y < navigationSearchPartHeight {
|
||||||
|
if targetContentOffset.pointee.y < navigationSearchPartHeight * 0.5 {
|
||||||
|
targetContentOffset.pointee.y = 0.0
|
||||||
|
} else {
|
||||||
|
targetContentOffset.pointee.y = navigationSearchPartHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||||
|
if let navigationSearchPartHeight = self.navigationSearchPartHeight, navigationSearchPartHeight > 0.0 {
|
||||||
|
if scrollView.contentOffset.y < navigationSearchPartHeight {
|
||||||
|
if scrollView.contentOffset.y < navigationSearchPartHeight * 0.5 {
|
||||||
|
scrollView.setContentOffset(CGPoint(), animated: true)
|
||||||
|
} else {
|
||||||
|
scrollView.setContentOffset(CGPoint(x: 0.0, y: navigationSearchPartHeight), animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +379,8 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -200.0)
|
let actualBounds = self.scrollView.bounds
|
||||||
|
let visibleBounds = actualBounds.insetBy(dx: 0.0, dy: -200.0)
|
||||||
|
|
||||||
var synchronousLoad = false
|
var synchronousLoad = false
|
||||||
if let hint = transition.userData(PeerListItemComponent.TransitionHint.self) {
|
if let hint = transition.userData(PeerListItemComponent.TransitionHint.self) {
|
||||||
@ -538,15 +575,80 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
viewList.loadMore()
|
viewList.loadMore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let navigationSearchCollapseFraction: CGFloat
|
||||||
|
let navigationSearchFieldCollapseFraction: CGFloat
|
||||||
|
if itemLayout.isSearchActive {
|
||||||
|
navigationSearchCollapseFraction = 0.0
|
||||||
|
navigationSearchFieldCollapseFraction = 0.0
|
||||||
|
} else if let navigationSearchPartHeight = self.navigationSearchPartHeight, navigationSearchPartHeight > 8.0 {
|
||||||
|
let searchCollapseDistance: CGFloat = navigationSearchPartHeight
|
||||||
|
navigationSearchCollapseFraction = max(0.0, min(1.0, actualBounds.minY / searchCollapseDistance))
|
||||||
|
|
||||||
|
let searchFieldCollapseDistance: CGFloat = navigationSearchPartHeight - 8.0
|
||||||
|
navigationSearchFieldCollapseFraction = max(0.0, min(1.0, actualBounds.minY / searchFieldCollapseDistance))
|
||||||
|
} else {
|
||||||
|
navigationSearchCollapseFraction = 1.0
|
||||||
|
navigationSearchFieldCollapseFraction = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if let navigationSearch = self.navigationSearch {
|
||||||
|
let _ = navigationSearch.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(NavigationSearchComponent(
|
||||||
|
colors: NavigationSearchComponent.Colors(
|
||||||
|
background: UIColor(white: 1.0, alpha: 0.05),
|
||||||
|
inactiveForeground: UIColor(rgb: 0x8E8E93),
|
||||||
|
foreground: .white,
|
||||||
|
button: component.theme.rootController.navigationBar.accentTextColor
|
||||||
|
),
|
||||||
|
cancel: component.strings.Common_Cancel,
|
||||||
|
placeholder: component.strings.Common_Search,
|
||||||
|
isSearchActive: component.isSearchActive,
|
||||||
|
collapseFraction: navigationSearchFieldCollapseFraction,
|
||||||
|
activateSearch: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.setIsSearchActive(true)
|
||||||
|
},
|
||||||
|
deactivateSearch: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.setIsSearchActive(false)
|
||||||
|
},
|
||||||
|
updateQuery: { [weak self] query in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.updateQuery?(query)
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: itemLayout.containerSize.width - component.safeInsets.left - component.safeInsets.right, height: 100.0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let navigationHeight = self.navigationHeight, let navigationSearchPartHeight = self.navigationSearchPartHeight, let navigationBarBackground = self.navigationBarBackground, let navigationSeparator = self.navigationSeparator, let backgroundView = self.backgroundView {
|
||||||
|
let effectiveNavigationHeight = navigationHeight - navigationSearchPartHeight * navigationSearchCollapseFraction
|
||||||
|
let navigationBarBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: itemLayout.containerSize.width, height: effectiveNavigationHeight))
|
||||||
|
|
||||||
|
transition.setFrame(view: navigationBarBackground, frame: navigationBarBackgroundFrame)
|
||||||
|
navigationBarBackground.update(size: navigationBarBackgroundFrame.size, cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition)
|
||||||
|
transition.setFrame(layer: navigationSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarBackgroundFrame.height - UIScreenPixel), size: CGSize(width: navigationBarBackgroundFrame.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
|
let navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: itemLayout.availableSize.height - itemLayout.containerSize.height + 12.0), size: CGSize(width: itemLayout.availableSize.width, height: effectiveNavigationHeight))
|
||||||
|
transition.setFrame(view: backgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarFrame.maxY), size: CGSize(width: itemLayout.availableSize.width, height: itemLayout.availableSize.height)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(component: StoryItemSetViewListComponent, state: EmptyComponentState?, baseContentView: ContentView?, query: String?, availableSize: CGSize, visualHeight: CGFloat, sideInset: CGFloat, navigationHeight: CGFloat, transition: Transition) {
|
func updateState(component: StoryItemSetViewListComponent, state: EmptyComponentState?, baseContentView: ContentView?, query: String?) {
|
||||||
let themeUpdated = self.component?.theme !== component.theme
|
let itemUpdated = self.stateComponent?.storyItem.id != component.storyItem.id
|
||||||
let itemUpdated = self.component?.storyItem.id != component.storyItem.id
|
let viewsNilUpdated = (self.stateComponent?.storyItem.views == nil) != (component.storyItem.views == nil)
|
||||||
let viewsNilUpdated = (self.component?.storyItem.views == nil) != (component.storyItem.views == nil)
|
|
||||||
let queryUpdated = self.query != query
|
let queryUpdated = self.query != query
|
||||||
|
|
||||||
self.component = component
|
self.stateComponent = component
|
||||||
self.state = state
|
self.state = state
|
||||||
self.query = query
|
self.query = query
|
||||||
|
|
||||||
@ -627,11 +729,6 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var synchronous = false
|
|
||||||
if let animationHint = transition.userData(AnimationHint.self) {
|
|
||||||
synchronous = animationHint.synchronous
|
|
||||||
}
|
|
||||||
|
|
||||||
if itemUpdated || viewsNilUpdated || queryUpdated {
|
if itemUpdated || viewsNilUpdated || queryUpdated {
|
||||||
self.viewListDisposable?.dispose()
|
self.viewListDisposable?.dispose()
|
||||||
|
|
||||||
@ -693,7 +790,6 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
applyState = true
|
applyState = true
|
||||||
let _ = synchronous
|
|
||||||
} else {
|
} else {
|
||||||
if let _ = component.storyItem.views {
|
if let _ = component.storyItem.views {
|
||||||
} else {
|
} else {
|
||||||
@ -708,10 +804,21 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
hasContent = true
|
hasContent = true
|
||||||
}
|
}
|
||||||
self.hasContent = hasContent
|
self.hasContent = hasContent
|
||||||
self.contentLoaded = true
|
if self.contentLoaded != true {
|
||||||
|
self.contentLoaded = true
|
||||||
|
self.contentLoadedUpdated?(self.contentLoaded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: StoryItemSetViewListComponent, availableSize: CGSize, visualHeight: CGFloat, sideInset: CGFloat, navigationHeight: CGFloat, navigationSearchPartHeight: CGFloat, isSearchActive: Bool, transition: Transition) {
|
||||||
|
let themeUpdated = self.component?.theme !== component.theme
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
self.navigationHeight = navigationHeight
|
||||||
|
self.navigationSearchPartHeight = navigationSearchPartHeight
|
||||||
|
|
||||||
let measureItemSize = self.measureItem.update(
|
let measureItemSize = self.measureItem.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
@ -818,12 +925,14 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
|
|
||||||
let itemLayout = ItemLayout(
|
let itemLayout = ItemLayout(
|
||||||
containerSize: CGSize(width: availableSize.width, height: visualHeight),
|
containerSize: CGSize(width: availableSize.width, height: visualHeight),
|
||||||
|
availableSize: availableSize,
|
||||||
bottomInset: component.safeInsets.bottom,
|
bottomInset: component.safeInsets.bottom,
|
||||||
topInset: navigationHeight,
|
topInset: navigationHeight,
|
||||||
sideInset: sideInset,
|
sideInset: sideInset,
|
||||||
itemHeight: measureItemSize.height,
|
itemHeight: measureItemSize.height,
|
||||||
itemCount: self.viewListState?.items.count ?? 0,
|
itemCount: self.viewListState?.items.count ?? 0,
|
||||||
premiumFooterSize: premiumFooterSize
|
premiumFooterSize: premiumFooterSize,
|
||||||
|
isSearchActive: isSearchActive
|
||||||
)
|
)
|
||||||
self.itemLayout = itemLayout
|
self.itemLayout = itemLayout
|
||||||
|
|
||||||
@ -1087,6 +1196,10 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
private let orderSelector = ComponentView<Empty>()
|
private let orderSelector = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private var mainViewListDisposable: Disposable?
|
||||||
|
private var mainViewList: EngineStoryViewListContext?
|
||||||
|
private var mainViewListState: EngineStoryViewListContext.State?
|
||||||
|
|
||||||
private var currentContentView: ContentView?
|
private var currentContentView: ContentView?
|
||||||
private weak var disappearingCurrentContentView: ContentView?
|
private weak var disappearingCurrentContentView: ContentView?
|
||||||
|
|
||||||
@ -1115,8 +1228,8 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
|
|
||||||
self.addSubview(self.backgroundView)
|
self.addSubview(self.backgroundView)
|
||||||
|
|
||||||
self.addSubview(self.navigationBarBackground)
|
self.navigationContainerView.addSubview(self.navigationBarBackground)
|
||||||
self.layer.addSublayer(self.navigationSeparator)
|
self.navigationContainerView.layer.addSublayer(self.navigationSeparator)
|
||||||
self.addSubview(self.navigationContainerView)
|
self.addSubview(self.navigationContainerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1125,10 +1238,11 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
self.mainViewListDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
if !self.backgroundView.frame.contains(point) && !self.navigationBarBackground.frame.contains(point) {
|
if !self.backgroundView.frame.contains(point) && !self.navigationContainerView.frame.contains(point) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1245,12 +1359,108 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
self.currentSearchQuery = ""
|
self.currentSearchQuery = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updateSubState = false
|
||||||
|
|
||||||
|
if self.mainViewList == nil {
|
||||||
|
self.mainViewListDisposable?.dispose()
|
||||||
|
self.mainViewListDisposable = nil
|
||||||
|
|
||||||
|
if let views = component.storyItem.views {
|
||||||
|
let viewList: EngineStoryViewListContext
|
||||||
|
if let current = component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] {
|
||||||
|
viewList = current
|
||||||
|
} else {
|
||||||
|
viewList = component.context.engine.messages.storyViewList(id: component.storyItem.id, views: views, listMode: .everyone, sortMode: .reactionsFirst)
|
||||||
|
component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] = viewList
|
||||||
|
}
|
||||||
|
self.mainViewList = viewList
|
||||||
|
self.mainViewListDisposable = (viewList.state
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] listState in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.mainViewListState = listState
|
||||||
|
|
||||||
|
if updateSubState {
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.mainViewList = nil
|
||||||
|
self.mainViewListState = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentConfiguration = ContentConfigurationKey(listMode: self.listMode, sortMode: self.sortMode)
|
||||||
|
if self.currentContentView?.configuration != currentConfiguration {
|
||||||
|
let previousContentView = self.currentContentView
|
||||||
|
self.disappearingCurrentContentView?.removeFromSuperview()
|
||||||
|
self.disappearingCurrentContentView = self.currentContentView
|
||||||
|
self.currentContentView = nil
|
||||||
|
|
||||||
|
let currentContentView = ContentView(configuration: currentConfiguration)
|
||||||
|
self.currentContentView = currentContentView
|
||||||
|
currentContentView.updateQuery = { [weak self] query in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.currentSearchQuery != query {
|
||||||
|
self.currentSearchQuery = query
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentContentView.isHidden = true
|
||||||
|
currentContentView.contentLoadedUpdated = { [weak self, weak currentContentView, weak previousContentView] value in
|
||||||
|
guard value, let self, let currentContentView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentContentView.isHidden = false
|
||||||
|
if let previousContentView {
|
||||||
|
previousContentView.removeFromSuperview()
|
||||||
|
if self.disappearingCurrentContentView === previousContentView {
|
||||||
|
self.disappearingCurrentContentView = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentContentView.navigationSearch = self.navigationSearch
|
||||||
|
currentContentView.navigationBarBackground = self.navigationBarBackground
|
||||||
|
currentContentView.navigationSeparator = self.navigationSeparator
|
||||||
|
currentContentView.backgroundView = self.backgroundView
|
||||||
|
if updateSubState {
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentContentView = self.currentContentView {
|
||||||
|
var contentViewTransition = transition
|
||||||
|
if currentContentView.superview == nil {
|
||||||
|
contentViewTransition = contentViewTransition.withAnimation(.none)
|
||||||
|
self.insertSubview(currentContentView, belowSubview: self.navigationContainerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentViewTransition.setFrame(view: currentContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
|
currentContentView.updateState(
|
||||||
|
component: component,
|
||||||
|
state: state,
|
||||||
|
baseContentView: nil,
|
||||||
|
query: nil
|
||||||
|
)
|
||||||
|
if currentContentView.contentLoaded {
|
||||||
|
currentContentView.isHidden = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
|
|
||||||
let visualHeight: CGFloat = max(component.minHeight, component.effectiveHeight)
|
let visualHeight: CGFloat = max(component.minHeight, component.effectiveHeight)
|
||||||
|
|
||||||
|
var tabSelectorTransition = transition
|
||||||
|
if transition.animation.isImmediate, self.tabSelector.view != nil {
|
||||||
|
tabSelectorTransition = Transition(animation: .curve(duration: 0.35, curve: .spring))
|
||||||
|
}
|
||||||
let tabSelectorSize = self.tabSelector.update(
|
let tabSelectorSize = self.tabSelector.update(
|
||||||
transition: transition,
|
transition: tabSelectorTransition,
|
||||||
component: AnyComponent(TabSelectorComponent(
|
component: AnyComponent(TabSelectorComponent(
|
||||||
colors: TabSelectorComponent.Colors(
|
colors: TabSelectorComponent.Colors(
|
||||||
foreground: .white,
|
foreground: .white,
|
||||||
@ -1273,7 +1483,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
}
|
}
|
||||||
if self.listMode != listMode {
|
if self.listMode != listMode {
|
||||||
self.listMode = listMode
|
self.listMode = listMode
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
|
self.state?.updated(transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
@ -1281,10 +1491,20 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
containerSize: CGSize(width: availableSize.width - 10.0 * 2.0, height: 50.0)
|
containerSize: CGSize(width: availableSize.width - 10.0 * 2.0, height: 50.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var currentTotalCount: Int?
|
||||||
|
var currentTotalReactionCount: Int?
|
||||||
|
if let mainViewListState = self.mainViewListState {
|
||||||
|
currentTotalCount = mainViewListState.totalCount
|
||||||
|
currentTotalReactionCount = mainViewListState.totalReactedCount
|
||||||
|
} else {
|
||||||
|
currentTotalCount = component.storyItem.views?.seenCount
|
||||||
|
currentTotalReactionCount = component.storyItem.views?.reactedCount
|
||||||
|
}
|
||||||
|
|
||||||
let titleText: String
|
let titleText: String
|
||||||
if let views = component.storyItem.views, views.seenCount != 0 {
|
if let totalCount = currentTotalCount, let currentTotalReactionCount {
|
||||||
if component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
|
if totalCount > 0 && totalCount > currentTotalReactionCount {
|
||||||
titleText = component.strings.Story_ViewList_ViewerCount(Int32(views.seenCount))
|
titleText = component.strings.Story_ViewList_ViewerCount(Int32(totalCount))
|
||||||
} else {
|
} else {
|
||||||
titleText = component.strings.Story_ViewList_TitleViewers
|
titleText = component.strings.Story_ViewList_TitleViewers
|
||||||
}
|
}
|
||||||
@ -1319,44 +1539,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
let navigationSearchSize = self.navigationSearch.update(
|
let navigationSearchSize = CGSize(width: availableSize.width - component.safeInsets.left - component.safeInsets.right, height: 52.0)
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(NavigationSearchComponent(
|
|
||||||
colors: NavigationSearchComponent.Colors(
|
|
||||||
background: UIColor(white: 1.0, alpha: 0.05),
|
|
||||||
inactiveForeground: UIColor(rgb: 0x8E8E93),
|
|
||||||
foreground: .white,
|
|
||||||
button: component.theme.rootController.navigationBar.accentTextColor
|
|
||||||
),
|
|
||||||
cancel: component.strings.Common_Cancel,
|
|
||||||
placeholder: component.strings.Common_Search,
|
|
||||||
isSearchActive: component.isSearchActive,
|
|
||||||
collapseFraction: 1.0,
|
|
||||||
activateSearch: { [weak self] in
|
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
component.setIsSearchActive(true)
|
|
||||||
},
|
|
||||||
deactivateSearch: { [weak self] in
|
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
component.setIsSearchActive(false)
|
|
||||||
},
|
|
||||||
updateQuery: { [weak self] query in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if self.currentSearchQuery != query {
|
|
||||||
self.currentSearchQuery = query
|
|
||||||
self.state?.updated(transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: availableSize.width - component.safeInsets.left - component.safeInsets.right, height: 100.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
var displayModeSelector = false
|
var displayModeSelector = false
|
||||||
var displaySearchBar = false
|
var displaySearchBar = false
|
||||||
@ -1365,7 +1548,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
if !component.hasPremium, component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
|
if !component.hasPremium, component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
|
||||||
} else {
|
} else {
|
||||||
if let views = component.storyItem.views, views.hasList {
|
if let views = component.storyItem.views, views.hasList {
|
||||||
if let currentContentView = self.currentContentView, let totalCount = currentContentView.totalCount {
|
if let totalCount = currentTotalCount {
|
||||||
if totalCount >= 20 || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
|
if totalCount >= 20 || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
|
||||||
displayModeSelector = true
|
displayModeSelector = true
|
||||||
displaySearchBar = true
|
displaySearchBar = true
|
||||||
@ -1374,13 +1557,13 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
displaySortSelector = true
|
displaySortSelector = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if views.seenCount >= 20 || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
|
/*if views.seenCount >= 20 || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
|
||||||
displayModeSelector = true
|
displayModeSelector = true
|
||||||
displaySearchBar = true
|
displaySearchBar = true
|
||||||
}
|
}
|
||||||
if (views.reactedCount >= 10 && views.seenCount >= 20) || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
|
if (views.reactedCount >= 10 && views.seenCount >= 20) || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
|
||||||
displaySortSelector = true
|
displaySortSelector = true
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let privacy = component.storyItem.privacy, case .everyone = privacy.base {
|
if let privacy = component.storyItem.privacy, case .everyone = privacy.base {
|
||||||
@ -1390,18 +1573,18 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let navigationHeight: CGFloat
|
let navigationHeight: CGFloat
|
||||||
|
let navigationSearchPartHeight: CGFloat
|
||||||
if component.isSearchActive {
|
if component.isSearchActive {
|
||||||
navigationHeight = navigationSearchSize.height
|
navigationHeight = navigationSearchSize.height
|
||||||
|
navigationSearchPartHeight = 0.0
|
||||||
} else if displaySearchBar {
|
} else if displaySearchBar {
|
||||||
navigationHeight = 56.0 + navigationSearchSize.height - 6.0
|
navigationHeight = 56.0 + navigationSearchSize.height - 6.0
|
||||||
|
navigationSearchPartHeight = navigationSearchSize.height - 6.0
|
||||||
} else {
|
} else {
|
||||||
navigationHeight = 56.0
|
navigationHeight = 56.0
|
||||||
|
navigationSearchPartHeight = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - visualHeight + 12.0), size: CGSize(width: availableSize.width, height: navigationHeight))
|
|
||||||
transition.setFrame(view: self.navigationBarBackground, frame: navigationBarFrame)
|
|
||||||
self.navigationBarBackground.update(size: navigationBarFrame.size, cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition)
|
|
||||||
|
|
||||||
if let tabSelectorView = self.tabSelector.view {
|
if let tabSelectorView = self.tabSelector.view {
|
||||||
if tabSelectorView.superview == nil {
|
if tabSelectorView.superview == nil {
|
||||||
self.navigationContainerView.addSubview(tabSelectorView)
|
self.navigationContainerView.addSubview(tabSelectorView)
|
||||||
@ -1432,60 +1615,26 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
orderSelectorView.isHidden = !displaySortSelector
|
orderSelectorView.isHidden = !displaySortSelector
|
||||||
}
|
}
|
||||||
|
|
||||||
if let navigationSearchView = self.navigationSearch.view {
|
let navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - visualHeight + 12.0), size: CGSize(width: availableSize.width, height: navigationHeight))
|
||||||
if navigationSearchView.superview == nil {
|
|
||||||
self.navigationContainerView.addSubview(navigationSearchView)
|
|
||||||
}
|
|
||||||
transition.setFrame(view: navigationSearchView, frame: CGRect(origin: CGPoint(x: component.safeInsets.left, y: component.isSearchActive ? 0.0 : 50.0), size: navigationSearchSize))
|
|
||||||
transition.setAlpha(view: navigationSearchView, alpha: (displaySearchBar || component.isSearchActive) ? 1.0 : 0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.setFrame(view: self.navigationContainerView, frame: navigationBarFrame)
|
transition.setFrame(view: self.navigationContainerView, frame: navigationBarFrame)
|
||||||
transition.setFrame(layer: self.navigationSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
|
||||||
|
|
||||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
|
||||||
|
|
||||||
let currentConfiguration = ContentConfigurationKey(listMode: self.listMode, sortMode: self.sortMode)
|
|
||||||
if self.currentContentView?.configuration != currentConfiguration {
|
|
||||||
let previousContentView = self.currentContentView
|
|
||||||
self.disappearingCurrentContentView?.removeFromSuperview()
|
|
||||||
self.disappearingCurrentContentView = self.currentContentView
|
|
||||||
self.currentContentView = nil
|
|
||||||
|
|
||||||
let currentContentView = ContentView(configuration: currentConfiguration)
|
|
||||||
self.currentContentView = currentContentView
|
|
||||||
currentContentView.isHidden = true
|
|
||||||
currentContentView.contentLoadedUpdated = { [weak self, weak currentContentView, weak previousContentView] value in
|
|
||||||
guard value, let self, let currentContentView else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
currentContentView.isHidden = false
|
|
||||||
if let previousContentView {
|
|
||||||
previousContentView.removeFromSuperview()
|
|
||||||
if self.disappearingCurrentContentView === previousContentView {
|
|
||||||
self.disappearingCurrentContentView = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let currentContentView = self.currentContentView {
|
if let currentContentView = self.currentContentView {
|
||||||
var contentViewTransition = transition
|
var contentViewTransition = transition
|
||||||
if currentContentView.superview == nil {
|
if currentContentView.superview == nil {
|
||||||
contentViewTransition = contentViewTransition.withAnimation(.none)
|
contentViewTransition = contentViewTransition.withAnimation(.none)
|
||||||
self.insertSubview(currentContentView, belowSubview: self.navigationBarBackground)
|
self.insertSubview(currentContentView, belowSubview: self.navigationContainerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentViewTransition.setFrame(view: currentContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
contentViewTransition.setFrame(view: currentContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
currentContentView.update(
|
currentContentView.update(
|
||||||
component: component,
|
component: component,
|
||||||
state: state,
|
|
||||||
baseContentView: nil,
|
|
||||||
query: nil,
|
|
||||||
availableSize: availableSize,
|
availableSize: availableSize,
|
||||||
visualHeight: visualHeight,
|
visualHeight: visualHeight,
|
||||||
sideInset: sideInset,
|
sideInset: sideInset,
|
||||||
navigationHeight: navigationHeight,
|
navigationHeight: navigationHeight,
|
||||||
|
navigationSearchPartHeight: navigationSearchPartHeight,
|
||||||
|
isSearchActive: component.isSearchActive,
|
||||||
transition: contentViewTransition
|
transition: contentViewTransition
|
||||||
)
|
)
|
||||||
if currentContentView.contentLoaded {
|
if currentContentView.contentLoaded {
|
||||||
@ -1512,7 +1661,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
var contentViewTransition = transition
|
var contentViewTransition = transition
|
||||||
if currentSearchContentView.superview == nil {
|
if currentSearchContentView.superview == nil {
|
||||||
contentViewTransition = contentViewTransition.withAnimation(.none)
|
contentViewTransition = contentViewTransition.withAnimation(.none)
|
||||||
self.insertSubview(currentSearchContentView, belowSubview: self.navigationBarBackground)
|
self.insertSubview(currentSearchContentView, belowSubview: self.navigationContainerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSearchContentView.hasContentUpdated = { [weak self] hasContent in
|
currentSearchContentView.hasContentUpdated = { [weak self] hasContent in
|
||||||
@ -1523,15 +1672,20 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
self.currentSearchContentView?.isHidden = !hasContent
|
self.currentSearchContentView?.isHidden = !hasContent
|
||||||
}
|
}
|
||||||
contentViewTransition.setFrame(view: currentSearchContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
contentViewTransition.setFrame(view: currentSearchContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
currentSearchContentView.update(
|
currentSearchContentView.updateState(
|
||||||
component: component,
|
component: component,
|
||||||
state: state,
|
state: state,
|
||||||
baseContentView: self.currentContentView,
|
baseContentView: self.currentContentView,
|
||||||
query: self.currentSearchQuery,
|
query: self.currentSearchQuery
|
||||||
|
)
|
||||||
|
currentSearchContentView.update(
|
||||||
|
component: component,
|
||||||
availableSize: availableSize,
|
availableSize: availableSize,
|
||||||
visualHeight: visualHeight,
|
visualHeight: visualHeight,
|
||||||
sideInset: sideInset,
|
sideInset: sideInset,
|
||||||
navigationHeight: navigationHeight,
|
navigationHeight: navigationHeight,
|
||||||
|
navigationSearchPartHeight: navigationSearchPartHeight,
|
||||||
|
isSearchActive: component.isSearchActive,
|
||||||
transition: contentViewTransition
|
transition: contentViewTransition
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1564,13 +1718,12 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
transition.setFrame(view: disappearingCurrentContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setFrame(view: disappearingCurrentContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
disappearingCurrentContentView.update(
|
disappearingCurrentContentView.update(
|
||||||
component: component,
|
component: component,
|
||||||
state: state,
|
|
||||||
baseContentView: nil,
|
|
||||||
query: disappearingCurrentContentView.query,
|
|
||||||
availableSize: availableSize,
|
availableSize: availableSize,
|
||||||
visualHeight: visualHeight,
|
visualHeight: visualHeight,
|
||||||
sideInset: sideInset,
|
sideInset: sideInset,
|
||||||
navigationHeight: navigationHeight,
|
navigationHeight: navigationHeight,
|
||||||
|
navigationSearchPartHeight: navigationSearchPartHeight,
|
||||||
|
isSearchActive: component.isSearchActive,
|
||||||
transition: transition
|
transition: transition
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1578,19 +1731,28 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
transition.setFrame(view: disappearingSearchContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setFrame(view: disappearingSearchContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
disappearingSearchContentView.update(
|
disappearingSearchContentView.update(
|
||||||
component: component,
|
component: component,
|
||||||
state: state,
|
|
||||||
baseContentView: nil,
|
|
||||||
query: disappearingSearchContentView.query,
|
|
||||||
availableSize: availableSize,
|
availableSize: availableSize,
|
||||||
visualHeight: visualHeight,
|
visualHeight: visualHeight,
|
||||||
sideInset: sideInset,
|
sideInset: sideInset,
|
||||||
navigationHeight: navigationHeight,
|
navigationHeight: navigationHeight,
|
||||||
|
navigationSearchPartHeight: navigationSearchPartHeight,
|
||||||
|
isSearchActive: component.isSearchActive,
|
||||||
transition: transition
|
transition: transition
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let navigationSearchView = self.navigationSearch.view {
|
||||||
|
if navigationSearchView.superview == nil {
|
||||||
|
self.navigationContainerView.addSubview(navigationSearchView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: navigationSearchView, frame: CGRect(origin: CGPoint(x: component.safeInsets.left, y: component.isSearchActive ? 0.0 : 50.0), size: navigationSearchSize))
|
||||||
|
transition.setAlpha(view: navigationSearchView, alpha: (displaySearchBar || component.isSearchActive) ? 1.0 : 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
transition.setBoundsOrigin(view: self, origin: CGPoint(x: 0.0, y: -max(0.0, visualHeight - component.effectiveHeight)))
|
transition.setBoundsOrigin(view: self, origin: CGPoint(x: 0.0, y: -max(0.0, visualHeight - component.effectiveHeight)))
|
||||||
|
|
||||||
|
updateSubState = true
|
||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,7 @@ public final class TextFieldComponent: Component {
|
|||||||
public let textColor: UIColor
|
public let textColor: UIColor
|
||||||
public let insets: UIEdgeInsets
|
public let insets: UIEdgeInsets
|
||||||
public let hideKeyboard: Bool
|
public let hideKeyboard: Bool
|
||||||
|
public let resetText: NSAttributedString?
|
||||||
public let formatMenuAvailability: FormatMenuAvailability
|
public let formatMenuAvailability: FormatMenuAvailability
|
||||||
public let lockedFormatAction: () -> Void
|
public let lockedFormatAction: () -> Void
|
||||||
public let present: (ViewController) -> Void
|
public let present: (ViewController) -> Void
|
||||||
@ -103,6 +104,7 @@ public final class TextFieldComponent: Component {
|
|||||||
textColor: UIColor,
|
textColor: UIColor,
|
||||||
insets: UIEdgeInsets,
|
insets: UIEdgeInsets,
|
||||||
hideKeyboard: Bool,
|
hideKeyboard: Bool,
|
||||||
|
resetText: NSAttributedString?,
|
||||||
formatMenuAvailability: FormatMenuAvailability,
|
formatMenuAvailability: FormatMenuAvailability,
|
||||||
lockedFormatAction: @escaping () -> Void,
|
lockedFormatAction: @escaping () -> Void,
|
||||||
present: @escaping (ViewController) -> Void,
|
present: @escaping (ViewController) -> Void,
|
||||||
@ -115,6 +117,7 @@ public final class TextFieldComponent: Component {
|
|||||||
self.textColor = textColor
|
self.textColor = textColor
|
||||||
self.insets = insets
|
self.insets = insets
|
||||||
self.hideKeyboard = hideKeyboard
|
self.hideKeyboard = hideKeyboard
|
||||||
|
self.resetText = resetText
|
||||||
self.formatMenuAvailability = formatMenuAvailability
|
self.formatMenuAvailability = formatMenuAvailability
|
||||||
self.lockedFormatAction = lockedFormatAction
|
self.lockedFormatAction = lockedFormatAction
|
||||||
self.present = present
|
self.present = present
|
||||||
@ -140,6 +143,9 @@ public final class TextFieldComponent: Component {
|
|||||||
if lhs.hideKeyboard != rhs.hideKeyboard {
|
if lhs.hideKeyboard != rhs.hideKeyboard {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.resetText != rhs.resetText {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.formatMenuAvailability != rhs.formatMenuAvailability {
|
if lhs.formatMenuAvailability != rhs.formatMenuAvailability {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -523,11 +529,13 @@ public final class TextFieldComponent: Component {
|
|||||||
return self.inputState.inputText
|
return self.inputState.inputText
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setAttributedText(_ string: NSAttributedString) {
|
public func setAttributedText(_ string: NSAttributedString, updateState: Bool) {
|
||||||
self.updateInputState { _ in
|
self.updateInputState { _ in
|
||||||
return TextFieldComponent.InputState(inputText: string, selectionRange: string.length ..< string.length)
|
return TextFieldComponent.InputState(inputText: string, selectionRange: string.length ..< string.length)
|
||||||
}
|
}
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged)))
|
if updateState {
|
||||||
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func activateInput() {
|
public func activateInput() {
|
||||||
@ -783,6 +791,10 @@ public final class TextFieldComponent: Component {
|
|||||||
self.updateInputState { _ in
|
self.updateInputState { _ in
|
||||||
return TextFieldComponent.InputState(inputText: initialText)
|
return TextFieldComponent.InputState(inputText: initialText)
|
||||||
}
|
}
|
||||||
|
} else if let resetText = component.resetText {
|
||||||
|
self.updateInputState { _ in
|
||||||
|
return TextFieldComponent.InputState(inputText: resetText)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.emojiViewProvider == nil {
|
if self.emojiViewProvider == nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user