diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 88130623c6..17e4f68aa4 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -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)) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index bd7a1156a3..ae68de007a 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -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) diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift index 1466f46530..a20da581bf 100644 --- a/submodules/Display/Source/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -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) { diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift index ee86e2921e..7a35effcd8 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift @@ -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) } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index f4ad980981..e2ebe82020 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -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 diff --git a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift index b003061a4e..ab8e64372e 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift @@ -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) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift index a2deee0065..78e282955c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift @@ -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 in let needUpdate: Signal @@ -148,10 +153,10 @@ public final class EngineStoryViewListContext { return needUpdate |> mapToSignal { _ -> Signal 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)) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 616521e098..9197e9be03 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -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 diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift index e26a52b0ae..552efbf7aa 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift @@ -254,6 +254,7 @@ final class StoryPreviewComponent: Component { maxLength: nil, queryTypes: [], alwaysDarkWhenHasText: false, + resetInputContents: nil, nextInputMode: { _ in return .stickers }, areVoiceMessagesAvailable: false, presentController: { _ in }, diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 558423d46b..0a00ad8193 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -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 { - 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 + 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)) + } 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) diff --git a/submodules/TelegramUI/Components/NavigationSearchComponent/Sources/NavigationSearchComponent.swift b/submodules/TelegramUI/Components/NavigationSearchComponent/Sources/NavigationSearchComponent.swift index a2f34b8ab4..6f311df38d 100644 --- a/submodules/TelegramUI/Components/NavigationSearchComponent/Sources/NavigationSearchComponent.swift +++ b/submodules/TelegramUI/Components/NavigationSearchComponent/Sources/NavigationSearchComponent.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 2f4049e26c..a3f9b820cb 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -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)) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index c53f7148ab..7956cd8b7d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -30,6 +30,8 @@ public final class StoryContentItem: Equatable { } public final class SharedState { + public var replyDrafts: [StoryId: NSAttributedString] = [:] + public init() { } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index c23ece425f..d3534ac517 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -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 - verticalPanState.accumulatedOffset += -overflowY - verticalPanState.accumulatedOffset = max(0.0, verticalPanState.accumulatedOffset) + 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 + } + } - 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() 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.viewListDisplayState = .half + } + self.state?.updated(transition: verticalPanState.accumulatedOffset == 0.0 ? .immediate : Transition(animation: .curve(duration: 0.4, curve: .spring))) } else { - self.state?.updated(transition: .immediate) + 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 - } + 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, transition: Transition) -> CGSize { @@ -2287,17 +2327,46 @@ 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 { @@ -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 { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index e2d7352c02..71f8f69839 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift index e941e52f40..3f4fe3e683 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift @@ -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? + 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 + } + + 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) { - 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) + 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 - 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( 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() private let orderSelector = ComponentView() + 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 } } diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index dc81a1c4fb..4e2d143e65 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -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,11 +529,13 @@ 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) } - 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() { @@ -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 {