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

View File

@ -1255,7 +1255,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.avatarContainerNode.addSubnode(self.avatarNode)
self.contextContainer.addSubnode(self.avatarContainerNode)
self.contextContainer.addSubnode(self.onlineNode)
self.avatarNode.addSubnode(self.onlineNode)
self.mainContentContainerNode.addSubnode(self.titleNode)
self.mainContentContainerNode.addSubnode(self.authorNode)
@ -3001,9 +3001,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let onlineFrame: CGRect
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 {
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)
@ -3040,11 +3040,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
avatarTimerTransition = .immediate
avatarTimerBadge = AvatarBadgeView(frame: CGRect())
strongSelf.avatarTimerBadge = avatarTimerBadge
strongSelf.contextContainer.view.addSubview(avatarTimerBadge)
strongSelf.avatarNode.view.addSubview(avatarTimerBadge)
}
let avatarBadgeSize = CGSize(width: avatarTimerBadgeDiameter, height: avatarTimerBadgeDiameter)
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.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)

View File

@ -331,7 +331,7 @@ public extension CALayer {
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) {

View File

@ -209,7 +209,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
self.backgroundView.update(size: contentBounds.size, transition: transition)
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)
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(view: self.contentContainerMask, frame: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size), 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))
let xOffset = sourceBackgroundFrame.midX - visualBackgroundFrame.midX
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.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)
} else if let animateOutToAnchorRect = animateOutToAnchorRect {
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 headerTextView.superview == nil {
headerTextView.layer.anchorPoint = CGPoint()
self.view.addSubview(headerTextView)
self.view.insertSubview(headerTextView, at: 0)
}
headerTextView.center = headerTextFrame.origin
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 {
self.parentSource = parentSource
let matchesMode = parentSource.listMode == listMode
if parentState.items.count < 100 && !matchesMode {
parentSource.loadMore()
}
self.disposable.set((parentSource.statePromise.get()
|> mapToSignal { state -> Signal<InternalState, NoError> in
let needUpdate: Signal<Void, NoError>
@ -148,10 +153,10 @@ public final class EngineStoryViewListContext {
return needUpdate
|> mapToSignal { _ -> Signal<InternalState, NoError> in
return account.postbox.transaction { transaction -> InternalState in
if state.canLoadMore {
/*if state.canLoadMore && !matchesMode {
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] = []
switch listMode {
@ -188,6 +193,7 @@ public final class EngineStoryViewListContext {
})
}
var totalCount = items.count
var totalReactedCount = 0
for item in items {
if item.reaction != nil {
@ -195,8 +201,17 @@ public final class EngineStoryViewListContext {
}
}
if state.canLoadMore {
totalCount = state.totalCount
totalReactedCount = state.totalReactedCount
}
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)
}))
} 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)
self.state = state
self.statePromise.set(.single(state))

View File

@ -1104,6 +1104,7 @@ final class MediaEditorScreenComponent: Component {
maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
queryTypes: [.mention],
alwaysDarkWhenHasText: false,
resetInputContents: nil,
nextInputMode: { _ in return nextInputMode },
areVoiceMessagesAvailable: false,
presentController: { [weak self] c in

View File

@ -254,6 +254,7 @@ final class StoryPreviewComponent: Component {
maxLength: nil,
queryTypes: [],
alwaysDarkWhenHasText: false,
resetInputContents: nil,
nextInputMode: { _ in return .stickers },
areVoiceMessagesAvailable: false,
presentController: { _ in },

View File

@ -100,6 +100,7 @@ public final class MessageInputPanelComponent: Component {
public let maxLength: Int?
public let queryTypes: ContextQueryTypes
public let alwaysDarkWhenHasText: Bool
public let resetInputContents: SendMessageInput?
public let nextInputMode: (Bool) -> InputMode?
public let areVoiceMessagesAvailable: Bool
public let presentController: (ViewController) -> Void
@ -149,6 +150,7 @@ public final class MessageInputPanelComponent: Component {
maxLength: Int?,
queryTypes: ContextQueryTypes,
alwaysDarkWhenHasText: Bool,
resetInputContents: SendMessageInput?,
nextInputMode: @escaping (Bool) -> InputMode?,
areVoiceMessagesAvailable: Bool,
presentController: @escaping (ViewController) -> Void,
@ -198,6 +200,7 @@ public final class MessageInputPanelComponent: Component {
self.maxLength = maxLength
self.queryTypes = queryTypes
self.alwaysDarkWhenHasText = alwaysDarkWhenHasText
self.resetInputContents = resetInputContents
self.areVoiceMessagesAvailable = areVoiceMessagesAvailable
self.presentController = presentController
self.presentInGlobalOverlay = presentInGlobalOverlay
@ -265,6 +268,9 @@ public final class MessageInputPanelComponent: Component {
if lhs.alwaysDarkWhenHasText != rhs.alwaysDarkWhenHasText {
return false
}
if lhs.resetInputContents != rhs.resetInputContents {
return false
}
if lhs.areVoiceMessagesAvailable != rhs.areVoiceMessagesAvailable {
return false
}
@ -334,7 +340,7 @@ public final class MessageInputPanelComponent: Component {
return true
}
public enum SendMessageInput {
public enum SendMessageInput: Equatable {
case text(NSAttributedString)
}
@ -386,6 +392,8 @@ public final class MessageInputPanelComponent: Component {
private var component: MessageInputPanelComponent?
private weak var state: EmptyComponentState?
private var pendingSetMessageInput: SendMessageInput?
public var likeButtonView: UIView? {
return self.likeButton.view
}
@ -451,6 +459,17 @@ public final class MessageInputPanelComponent: Component {
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? {
guard let attachmentButtonView = self.attachmentButton.view else {
return nil
@ -458,9 +477,9 @@ public final class MessageInputPanelComponent: Component {
return attachmentButtonView
}
public func clearSendMessageInput() {
public func clearSendMessageInput(updateState: Bool) {
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),
insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 48.0),
hideKeyboard: component.hideKeyboard,
resetText: component.resetInputContents.flatMap { resetInputContents in
switch resetInputContents {
case let .text(value):
return value
}
},
formatMenuAvailability: component.isFormattingLocked ? .locked : .available,
lockedFormatAction: {
component.presentTextFormattingTooltip?()
@ -714,9 +739,12 @@ public final class MessageInputPanelComponent: Component {
} else if isEditing {
fieldBackgroundFrame = fieldFrame
} else {
if component.forwardAction != nil && component.likeAction != 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))
} 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))
if component.likeAction != nil && component.forwardAction != nil {
fieldBackgroundFrame.size.width -= 49.0
} 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)
if let textFieldView = self.textField.view {
if let textFieldView = self.textField.view as? TextFieldComponent.View {
if textFieldView.superview == nil {
self.addSubview(textFieldView)
if let viewForOverlayContent = self.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)
transition.setFrame(view: textFieldView, frame: textFieldFrame)
@ -1168,7 +1204,10 @@ public final class MessageInputPanelComponent: Component {
if likeButtonView.superview == nil {
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.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size))
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) {
self.backgroundView = UIView()
self.backgroundView.layer.cornerRadius = 10.0
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 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
if component.isSearchActive {
var buttonTransition = transition
@ -235,8 +237,10 @@ public final class NavigationSearchComponent: Component {
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.setCornerRadius(layer: self.backgroundView.layer, cornerRadius: min(10.0, backgroundFrame.height * 0.5))
if previousComponent?.colors.background != 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 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
if let placeholderTextView = self.placeholderText.view {
if placeholderTextView.superview == nil {
@ -267,14 +271,16 @@ public final class NavigationSearchComponent: Component {
}
transition.setPosition(view: placeholderTextView, position: placeholderTextFrame.origin)
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.setAlpha(view: self.searchIconView, alpha: contentAlpha)
if let image = self.clearIconView.image {
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))
transition.setFrame(view: self.clearButton, frame: clearButtonFrame)

View File

@ -964,9 +964,6 @@ private final class StoryContainerScreenComponent: Component {
}
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.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.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 var replyDrafts: [StoryId: NSAttributedString] = [:]
public init() {
}
}

View File

@ -41,6 +41,7 @@ import Speak
import TranslateUI
import TelegramUIPreferences
import StoryFooterPanelComponent
import TelegramNotices
public final class StoryAvailableReactions: Equatable {
let reactionItems: [ReactionItem]
@ -349,6 +350,8 @@ public final class StoryItemSetContainerComponent: Component {
var startContentOffsetY: CGFloat = 0.0
var accumulatedOffset: CGFloat = 0.0
var dismissedTooltips: Bool = false
var didLockScrolling: Bool = false
var contentOffset: CGFloat?
init(fraction: CGFloat, scrollView: UIScrollView?) {
self.fraction = fraction
@ -423,6 +426,7 @@ public final class StoryItemSetContainerComponent: Component {
var viewListDisplayState: ViewListDisplayState = .hidden
private var targetViewListDisplayStateIsFull: Bool = false
private var viewListMetrics: (minHeight: CGFloat, maxHeight: CGFloat, currentHeight: CGFloat)?
var isSearchActive: Bool = false
@ -1014,7 +1018,7 @@ public final class StoryItemSetContainerComponent: Component {
verticalPanState.fraction = fraction
} else {
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) {
func findTargetScrollView(target: UIView, minParent: UIView) -> UIScrollView? {
if target === viewListView {
@ -1041,14 +1045,42 @@ public final class StoryItemSetContainerComponent: Component {
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 overflowY = scrollView.contentOffset.y - relativeTranslationY
if !verticalPanState.didLockScrolling {
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
}
}
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 {
if verticalPanState.accumulatedOffset > 0.0 || resetContentOffset {
scrollView.contentOffset = CGPoint()
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
} else {
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)))
} else if verticalPanState.accumulatedOffset < 0.0 && self.targetViewListDisplayStateIsFull {
if verticalPanState.fraction <= -0.05 || velocity.y <= -80.0 {
self.viewListDisplayState = .full
} else {
self.state?.updated(transition: .immediate)
self.viewListDisplayState = .half
}
self.state?.updated(transition: verticalPanState.accumulatedOffset == 0.0 ? .immediate : Transition(animation: .curve(duration: 0.4, curve: .spring)))
} else {
self.state?.updated(transition: verticalPanState.accumulatedOffset == 0.0 ? .immediate : Transition(animation: .curve(duration: 0.4, curve: .spring)))
}
} else {
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
}
if case .regular = component.metrics.widthClass {
footerAlpha *= itemAlpha
}
itemTransition.setAlpha(view: footerPanelView, alpha: footerAlpha)
}
@ -1983,7 +2021,7 @@ public final class StoryItemSetContainerComponent: Component {
let contentContainerView = visibleItem.contentContainerView
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 contentSourceFrame = contentContainerView.frame
@ -2280,6 +2318,8 @@ public final class StoryItemSetContainerComponent: Component {
self.sendMessageContext.tooltipScreen = tooltipScreen
self.updateIsProgressPaused()
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 {
@ -2287,18 +2327,47 @@ public final class StoryItemSetContainerComponent: Component {
if self.component == nil {
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 resetInputContents: MessageInputPanelComponent.SendMessageInput?
if self.component?.slice.item.storyItem.id != component.slice.item.storyItem.id {
itemChanged = self.component != nil
self.initializedOffset = false
resetInputContents = .text(NSAttributedString())
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View {
Queue.mainQueue().justDispatch {
inputPanelView.clearSendMessageInput()
if let previousComponent = self.component {
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 = tooltipScreen as? UndoOverlayController, let tag = tooltipScreen.tag as? String, tag == "no_auto_dismiss" {
@ -2405,6 +2474,9 @@ public final class StoryItemSetContainerComponent: Component {
transition.setBounds(view: self.componentContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
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.setBounds(view: self.overlayContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
transition.setScale(view: self.overlayContainerView, scale: dismissPanScale)
@ -2428,6 +2500,9 @@ public final class StoryItemSetContainerComponent: Component {
}
}
}
if itemChanged {
inputPanelTransition = .immediate
}
var isUnsupported = false
var disabledPlaceholder: String?
@ -2473,6 +2548,7 @@ public final class StoryItemSetContainerComponent: Component {
maxLength: 4096,
queryTypes: [.mention, .emoji],
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
resetInputContents: resetInputContents,
nextInputMode: { [weak self] hasText in
if case .media = self?.sendMessageContext.currentInputMode {
return .text
@ -2834,6 +2910,7 @@ public final class StoryItemSetContainerComponent: Component {
viewListHeight = max(minViewListHeight, min(maxViewListHeight, viewListHeight))
self.targetViewListDisplayStateIsFull = viewListHeight > midViewListHeight
self.viewListMetrics = (midViewListHeight, maxViewListHeight, viewListHeight)
let viewListHeightMidFraction: CGFloat = max(0.0, min(1.0, viewListHeight / midViewListHeight))
viewListInset = defaultHeight * (1.0 - viewListHeightMidFraction) + viewListHeightMidFraction * midViewListHeight
@ -3219,6 +3296,8 @@ public final class StoryItemSetContainerComponent: Component {
}
}
}
} else {
self.viewListMetrics = nil
}
var removeViewListIds: [Int32] = []
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
if hasFirstResponder(view) {
@ -1259,7 +1260,16 @@ final class StoryItemSetContainerSendMessage {
guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else {
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 {

View File

@ -159,23 +159,27 @@ final class StoryItemSetViewListComponent: Component {
private struct ItemLayout: Equatable {
var containerSize: CGSize
var availableSize: CGSize
var bottomInset: CGFloat
var topInset: CGFloat
var sideInset: CGFloat
var itemHeight: CGFloat
var itemCount: Int
var premiumFooterSize: CGSize?
var isSearchActive: Bool
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.availableSize = availableSize
self.bottomInset = bottomInset
self.topInset = topInset
self.sideInset = sideInset
self.itemHeight = itemHeight
self.itemCount = itemCount
self.premiumFooterSize = premiumFooterSize
self.isSearchActive = isSearchActive
self.contentSize = CGSize(width: containerSize.width, height: topInset + CGFloat(itemCount) * itemHeight + bottomInset)
if let premiumFooterSize {
@ -245,6 +249,7 @@ final class StoryItemSetViewListComponent: Component {
let configuration: ContentConfigurationKey
var query: String?
var stateComponent: StoryItemSetViewListComponent?
var component: StoryItemSetViewListComponent?
weak var state: EmptyComponentState?
@ -284,6 +289,15 @@ final class StoryItemSetViewListComponent: Component {
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) {
self.configuration = configuration
@ -329,6 +343,28 @@ final class StoryItemSetViewListComponent: Component {
if eventCycleState.ignoreScrolling {
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
}
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
if let hint = transition.userData(PeerListItemComponent.TransitionHint.self) {
@ -538,15 +575,80 @@ final class StoryItemSetViewListComponent: Component {
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
}
func update(component: StoryItemSetViewListComponent, state: EmptyComponentState?, baseContentView: ContentView?, query: String?, availableSize: CGSize, visualHeight: CGFloat, sideInset: CGFloat, navigationHeight: CGFloat, transition: Transition) {
let themeUpdated = self.component?.theme !== component.theme
let itemUpdated = self.component?.storyItem.id != component.storyItem.id
let viewsNilUpdated = (self.component?.storyItem.views == nil) != (component.storyItem.views == nil)
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 updateState(component: StoryItemSetViewListComponent, state: EmptyComponentState?, baseContentView: ContentView?, query: String?) {
let itemUpdated = self.stateComponent?.storyItem.id != component.storyItem.id
let viewsNilUpdated = (self.stateComponent?.storyItem.views == nil) != (component.storyItem.views == nil)
let queryUpdated = self.query != query
self.component = component
self.stateComponent = component
self.state = state
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 {
self.viewListDisposable?.dispose()
@ -693,7 +790,6 @@ final class StoryItemSetViewListComponent: Component {
}
})
applyState = true
let _ = synchronous
} else {
if let _ = component.storyItem.views {
} else {
@ -708,10 +804,21 @@ final class StoryItemSetViewListComponent: Component {
hasContent = true
}
self.hasContent = hasContent
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(
transition: .immediate,
@ -818,12 +925,14 @@ final class StoryItemSetViewListComponent: Component {
let itemLayout = ItemLayout(
containerSize: CGSize(width: availableSize.width, height: visualHeight),
availableSize: availableSize,
bottomInset: component.safeInsets.bottom,
topInset: navigationHeight,
sideInset: sideInset,
itemHeight: measureItemSize.height,
itemCount: self.viewListState?.items.count ?? 0,
premiumFooterSize: premiumFooterSize
premiumFooterSize: premiumFooterSize,
isSearchActive: isSearchActive
)
self.itemLayout = itemLayout
@ -1087,6 +1196,10 @@ final class StoryItemSetViewListComponent: Component {
private let title = 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 weak var disappearingCurrentContentView: ContentView?
@ -1115,8 +1228,8 @@ final class StoryItemSetViewListComponent: Component {
self.addSubview(self.backgroundView)
self.addSubview(self.navigationBarBackground)
self.layer.addSublayer(self.navigationSeparator)
self.navigationContainerView.addSubview(self.navigationBarBackground)
self.navigationContainerView.layer.addSublayer(self.navigationSeparator)
self.addSubview(self.navigationContainerView)
}
@ -1125,10 +1238,11 @@ final class StoryItemSetViewListComponent: Component {
}
deinit {
self.mainViewListDisposable?.dispose()
}
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
}
@ -1245,12 +1359,108 @@ final class StoryItemSetViewListComponent: Component {
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 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(
transition: transition,
transition: tabSelectorTransition,
component: AnyComponent(TabSelectorComponent(
colors: TabSelectorComponent.Colors(
foreground: .white,
@ -1273,7 +1483,7 @@ final class StoryItemSetViewListComponent: Component {
}
if 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)
)
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
if let views = component.storyItem.views, views.seenCount != 0 {
if component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
titleText = component.strings.Story_ViewList_ViewerCount(Int32(views.seenCount))
if let totalCount = currentTotalCount, let currentTotalReactionCount {
if totalCount > 0 && totalCount > currentTotalReactionCount {
titleText = component.strings.Story_ViewList_ViewerCount(Int32(totalCount))
} else {
titleText = component.strings.Story_ViewList_TitleViewers
}
@ -1319,44 +1539,7 @@ final class StoryItemSetViewListComponent: Component {
containerSize: CGSize(width: 100.0, height: 100.0)
)
let navigationSearchSize = self.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: 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)
)
let navigationSearchSize = CGSize(width: availableSize.width - component.safeInsets.left - component.safeInsets.right, height: 52.0)
var displayModeSelector = false
var displaySearchBar = false
@ -1365,7 +1548,7 @@ final class StoryItemSetViewListComponent: Component {
if !component.hasPremium, component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
} else {
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 {
displayModeSelector = true
displaySearchBar = true
@ -1374,13 +1557,13 @@ final class StoryItemSetViewListComponent: Component {
displaySortSelector = true
}
} else {
if views.seenCount >= 20 || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
/*if views.seenCount >= 20 || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
displayModeSelector = true
displaySearchBar = true
}
if (views.reactedCount >= 10 && views.seenCount >= 20) || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
displaySortSelector = true
}
}*/
}
}
if let privacy = component.storyItem.privacy, case .everyone = privacy.base {
@ -1390,18 +1573,18 @@ final class StoryItemSetViewListComponent: Component {
}
let navigationHeight: CGFloat
let navigationSearchPartHeight: CGFloat
if component.isSearchActive {
navigationHeight = navigationSearchSize.height
navigationSearchPartHeight = 0.0
} else if displaySearchBar {
navigationHeight = 56.0 + navigationSearchSize.height - 6.0
navigationSearchPartHeight = navigationSearchSize.height - 6.0
} else {
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 tabSelectorView.superview == nil {
self.navigationContainerView.addSubview(tabSelectorView)
@ -1432,60 +1615,26 @@ final class StoryItemSetViewListComponent: Component {
orderSelectorView.isHidden = !displaySortSelector
}
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)
}
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.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 {
var contentViewTransition = transition
if currentContentView.superview == nil {
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))
currentContentView.update(
component: component,
state: state,
baseContentView: nil,
query: nil,
availableSize: availableSize,
visualHeight: visualHeight,
sideInset: sideInset,
navigationHeight: navigationHeight,
navigationSearchPartHeight: navigationSearchPartHeight,
isSearchActive: component.isSearchActive,
transition: contentViewTransition
)
if currentContentView.contentLoaded {
@ -1512,7 +1661,7 @@ final class StoryItemSetViewListComponent: Component {
var contentViewTransition = transition
if currentSearchContentView.superview == nil {
contentViewTransition = contentViewTransition.withAnimation(.none)
self.insertSubview(currentSearchContentView, belowSubview: self.navigationBarBackground)
self.insertSubview(currentSearchContentView, belowSubview: self.navigationContainerView)
}
currentSearchContentView.hasContentUpdated = { [weak self] hasContent in
@ -1523,15 +1672,20 @@ final class StoryItemSetViewListComponent: Component {
self.currentSearchContentView?.isHidden = !hasContent
}
contentViewTransition.setFrame(view: currentSearchContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
currentSearchContentView.update(
currentSearchContentView.updateState(
component: component,
state: state,
baseContentView: self.currentContentView,
query: self.currentSearchQuery,
query: self.currentSearchQuery
)
currentSearchContentView.update(
component: component,
availableSize: availableSize,
visualHeight: visualHeight,
sideInset: sideInset,
navigationHeight: navigationHeight,
navigationSearchPartHeight: navigationSearchPartHeight,
isSearchActive: component.isSearchActive,
transition: contentViewTransition
)
@ -1564,13 +1718,12 @@ final class StoryItemSetViewListComponent: Component {
transition.setFrame(view: disappearingCurrentContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
disappearingCurrentContentView.update(
component: component,
state: state,
baseContentView: nil,
query: disappearingCurrentContentView.query,
availableSize: availableSize,
visualHeight: visualHeight,
sideInset: sideInset,
navigationHeight: navigationHeight,
navigationSearchPartHeight: navigationSearchPartHeight,
isSearchActive: component.isSearchActive,
transition: transition
)
}
@ -1578,19 +1731,28 @@ final class StoryItemSetViewListComponent: Component {
transition.setFrame(view: disappearingSearchContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
disappearingSearchContentView.update(
component: component,
state: state,
baseContentView: nil,
query: disappearingSearchContentView.query,
availableSize: availableSize,
visualHeight: visualHeight,
sideInset: sideInset,
navigationHeight: navigationHeight,
navigationSearchPartHeight: navigationSearchPartHeight,
isSearchActive: component.isSearchActive,
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)))
updateSubState = true
return availableSize
}
}

View File

@ -90,6 +90,7 @@ public final class TextFieldComponent: Component {
public let textColor: UIColor
public let insets: UIEdgeInsets
public let hideKeyboard: Bool
public let resetText: NSAttributedString?
public let formatMenuAvailability: FormatMenuAvailability
public let lockedFormatAction: () -> Void
public let present: (ViewController) -> Void
@ -103,6 +104,7 @@ public final class TextFieldComponent: Component {
textColor: UIColor,
insets: UIEdgeInsets,
hideKeyboard: Bool,
resetText: NSAttributedString?,
formatMenuAvailability: FormatMenuAvailability,
lockedFormatAction: @escaping () -> Void,
present: @escaping (ViewController) -> Void,
@ -115,6 +117,7 @@ public final class TextFieldComponent: Component {
self.textColor = textColor
self.insets = insets
self.hideKeyboard = hideKeyboard
self.resetText = resetText
self.formatMenuAvailability = formatMenuAvailability
self.lockedFormatAction = lockedFormatAction
self.present = present
@ -140,6 +143,9 @@ public final class TextFieldComponent: Component {
if lhs.hideKeyboard != rhs.hideKeyboard {
return false
}
if lhs.resetText != rhs.resetText {
return false
}
if lhs.formatMenuAvailability != rhs.formatMenuAvailability {
return false
}
@ -523,12 +529,14 @@ public final class TextFieldComponent: Component {
return self.inputState.inputText
}
public func setAttributedText(_ string: NSAttributedString) {
public func setAttributedText(_ string: NSAttributedString, updateState: Bool) {
self.updateInputState { _ in
return TextFieldComponent.InputState(inputText: string, selectionRange: string.length ..< string.length)
}
if updateState {
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged)))
}
}
public func activateInput() {
self.textView.becomeFirstResponder()
@ -783,6 +791,10 @@ public final class TextFieldComponent: Component {
self.updateInputState { _ in
return TextFieldComponent.InputState(inputText: initialText)
}
} else if let resetText = component.resetText {
self.updateInputState { _ in
return TextFieldComponent.InputState(inputText: resetText)
}
}
if self.emojiViewProvider == nil {