This commit is contained in:
Ali 2023-08-18 21:20:37 +04:00
parent e81b1fee96
commit 5db3755cbf
17 changed files with 494 additions and 167 deletions

View File

@ -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))
} }

View File

@ -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)

View File

@ -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) {

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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 },

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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() {
} }
} }

View File

@ -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 {

View File

@ -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 {

View File

@ -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
} }
} }

View File

@ -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 {