diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index c2ba110fd8..8634a2c058 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -1384,7 +1384,6 @@ public class AttachmentController: ViewController, MinimizableController { var containerInsets = containerLayout.intrinsicInsets var hasPanel = false -// let previousHasButton = self.hasButton let hasButton = self.panel.isButtonVisible && !self.isDismissing self.hasButton = hasButton if let controller = self.controller, controller.buttons.count > 1 || controller.hasTextInput || self.panel.hasMediaAccessoryPanel { @@ -1405,6 +1404,7 @@ public class AttachmentController: ViewController, MinimizableController { let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting) let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, transition: transition) + if hasPanel || hasButton { containerInsets.bottom = panelHeight + panelOffset } @@ -1526,7 +1526,7 @@ public class AttachmentController: ViewController, MinimizableController { public var forceSourceRect = false fileprivate var isStandalone: Bool { - return self.buttons.contains(.standalone) + return self.buttons.contains(.standalone) || self.buttons.count == 1 } public func convertToStandalone() { diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 73b40a7eab..33187b81c2 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1018,6 +1018,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog private var elevateProgress: Bool = false private var buttons: [AttachmentButtonType] = [] private var selectedIndex: Int = 0 + private var selectionOverrideIndex: Int? private(set) var isSelecting: Bool = false private var selectionCount: Int = 0 @@ -1025,12 +1026,30 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog var isButtonVisible: Bool { return self.mainButtonState.isVisible || self.secondaryButtonState.isVisible } - var hasMediaAccessoryPanel: Bool { - if case .glass = self.panelStyle { - return self.playlistStateAndType != nil - } else { + private var peerMessagesPlaylistLocation: PeerMessagesPlaylistLocation? { + return self.playlistLocation as? PeerMessagesPlaylistLocation + } + private var currentButtonType: AttachmentButtonType? { + let selectedIndex = self.selectionOverrideIndex ?? self.selectedIndex + guard selectedIndex >= 0 && selectedIndex < self.buttons.count else { + return nil + } + return self.buttons[selectedIndex] + } + private var shouldDisplayMediaAccessoryPanel: Bool { + guard case .glass = self.panelStyle, self.playlistStateAndType != nil else { return false } + guard let playlistLocation = self.peerMessagesPlaylistLocation, case .custom = playlistLocation else { + return false + } + guard let currentButtonType = self.currentButtonType, case .audio = currentButtonType else { + return false + } + return true + } + var hasMediaAccessoryPanel: Bool { + return self.shouldDisplayMediaAccessoryPanel } private var validLayout: ContainerViewLayout? @@ -1470,11 +1489,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog return } let hadMediaAccessoryPanel = strongSelf.hasMediaAccessoryPanel + let updatedPeerMessagesPlaylistLocation = playlistStateAndType?.1.playlistLocation as? PeerMessagesPlaylistLocation if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) || !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) || !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) || strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order || - strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 { + strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 || + strongSelf.peerMessagesPlaylistLocation != updatedPeerMessagesPlaylistLocation { if let playlistStateAndType { strongSelf.playlistStateAndType = ( @@ -1702,40 +1723,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog ) } } - - private func openCurrentMusicPlayer() { - guard let (state, _, _, order, type, account) = self.playlistStateAndType, - let id = state.id as? PeerMessagesMediaPlaylistItemId, - let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation else { - return - } - guard type == .music else { - self.context.sharedContext.navigateToChat(accountId: self.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId) - return - } - - if case .custom = playlistLocation { - let controllerContext: AccountContext - if account.id == self.context.account.id { - controllerContext = self.context - } else { - controllerContext = self.context.sharedContext.makeTempAccountContext(account: account) - } - let controller = self.context.sharedContext.makeOverlayAudioPlayerController( - context: controllerContext, - chatLocation: .peer(id: id.messageId.peerId), - type: type, - initialMessageId: id.messageId, - initialOrder: order, - playlistLocation: playlistLocation, - parentNavigationController: self.controller?.navigationController as? NavigationController - ) - self.view.window?.endEditing(true) - self.present(controller) - } - } - private func makeMediaAccessoryPanel() -> MediaNavigationAccessoryPanel { let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, presentationData: self.presentationData, displayBackground: false, customTintColor: self.presentationData.theme.rootController.tabBar.textColor) mediaAccessoryPanel.getController = { [weak self] in @@ -1784,14 +1772,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog self.context.sharedContext.mediaManager.playlistControl(.previous, type: type) } } - mediaAccessoryPanel.tapAction = { [weak self] in - self?.openCurrentMusicPlayer() + mediaAccessoryPanel.tapAction = { } return mediaAccessoryPanel } private func updateMediaAccessoryPanel(frame: CGRect?, transition: ContainedViewLayoutTransition) { guard case .glass = self.panelStyle, + self.hasMediaAccessoryPanel, let frame, let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType else { self.dismissMediaAccessoryPanel(transition: transition) @@ -1869,8 +1857,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } func updateSelectedIndex(_ index: Int) { + let hadMediaAccessoryPanel = self.hasMediaAccessoryPanel self.selectedIndex = index self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) + if hadMediaAccessoryPanel != self.hasMediaAccessoryPanel { + self.requestLayout(transition: .immediate) + } } var buttonSize: CGSize { @@ -1949,6 +1941,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } let button = self.buttons[index] self.skipLensUpdate = true + self.selectionOverrideIndex = index if self.selectionChanged(button) { self.selectedIndex = index if self.buttons.count > 5, let button = self.itemViews[button.key] { @@ -1972,6 +1965,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog self.updateItemContainers(contentOffset: newBounds.minX, transition: transition) } } + self.selectionOverrideIndex = nil self.skipLensUpdate = false } self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) @@ -1986,8 +1980,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog return } + var buttons = self.buttons + if buttons.count == 1 { + buttons.removeAll() + } + var width = layout.size.width - if self.buttons.count == 3 { + if buttons.count == 3 { width = smallPanelWidth } @@ -1999,11 +1998,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog panelSideInset = 3.0 } - var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, self.buttons.count - 1))) - if self.buttons.count == 3 { - distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - 32.0) / CGFloat(max(1, self.buttons.count - 1))) + var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, buttons.count - 1))) + if buttons.count == 3 { + distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - 32.0) / CGFloat(max(1, buttons.count - 1))) } - let internalWidth = distanceBetweenNodes * CGFloat(self.buttons.count - 1) + let internalWidth = distanceBetweenNodes * CGFloat(max(0, buttons.count - 1)) var buttonWidth = self.buttonSize.width var leftNodeOriginX: CGFloat var maxButtonsToFit = 5 @@ -2016,11 +2015,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog case .legacy: leftNodeOriginX = (width - internalWidth) / 2.0 } - if self.buttons.count == 3 { + if buttons.count == 3 { leftNodeOriginX = floor((layout.size.width - width) / 2.0) + 16.0 } - if self.buttons.count > maxButtonsToFit && layout.size.width < layout.size.height { + if buttons.count > maxButtonsToFit && layout.size.width < layout.size.height { switch self.panelStyle { case .glass: buttonWidth = smallGlassButtonSize.width @@ -2036,12 +2035,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog var selectionFrame = CGRect() var mostRightX = 0.0 - for i in 0 ..< self.buttons.count { + for i in 0 ..< buttons.count { let originX = floorToScreenPixels(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0) let buttonFrame = CGRect(origin: CGPoint(x: originX, y: -3.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height)) mostRightX = buttonFrame.maxX - let type = self.buttons[i] + let type = buttons[i] let _ = validIds.insert(type.key) var buttonTransition = transition @@ -2096,20 +2095,22 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog context: self.context, style: self.panelStyle == .glass ? .glass : .legacy, type: type, - isFirstOrLast: i == 0 || i == self.buttons.count - 1, + isFirstOrLast: i == 0 || i == buttons.count - 1, isSelected: false, strings: self.presentationData.strings, theme: self.presentationData.theme, action: { [weak self] in if let strongSelf = self { + strongSelf.selectionOverrideIndex = i if strongSelf.selectionChanged(type) { strongSelf.selectedIndex = i strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) - if strongSelf.buttons.count > 5, let button = strongSelf.itemViews[type.key] { + if buttons.count > 5, let button = strongSelf.itemViews[type.key] { strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true) } } + strongSelf.selectionOverrideIndex = nil } }, longPressAction: { [weak self] in if let strongSelf = self, i == strongSelf.selectedIndex { @@ -2128,7 +2129,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog context: self.context, style: self.panelStyle == .glass ? .glass : .legacy, type: type, - isFirstOrLast: i == 0 || i == self.buttons.count - 1, + isFirstOrLast: i == 0 || i == buttons.count - 1, isSelected: true, strings: self.presentationData.strings, theme: self.presentationData.theme, @@ -2433,7 +2434,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } let topAccessoryHeight: CGFloat - if case .glass = self.panelStyle, self.playlistStateAndType != nil { + if self.hasMediaAccessoryPanel { topAccessoryHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight } else { topAccessoryHeight = 0.0 @@ -2474,7 +2475,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog self.updateViews(transition: .immediate) let glassPanelHeight: CGFloat = 62.0 - let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom + topAccessoryHeight)) + var bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: topAccessoryHeight + self.buttonSize.height + insets.bottom)) + if buttons.count == 1 && topAccessoryHeight > 0.0 { + bounds.size.height -= self.buttonSize.height + } var mediaAccessoryPanelFrame: CGRect? if case .glass = self.panelStyle { let backgroundView: GlassBackgroundView @@ -2517,9 +2521,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } let basePanelHeight = isSelecting ? max(0.0, textPanelHeight - 11.0) : glassPanelHeight - let panelSize = CGSize(width: isSelecting ? textPanelWidth : buttonsPanelWidth, height: basePanelHeight + topAccessoryHeight) + var panelSize = CGSize(width: isSelecting ? textPanelWidth : buttonsPanelWidth, height: basePanelHeight + topAccessoryHeight) + if !isSelecting && buttons.count == 1 && topAccessoryHeight > 0.0 { + panelSize.height = topAccessoryHeight + } - let cornerRadius: CGFloat = isSelecting ? min(20.0, basePanelHeight * 0.5) : glassPanelHeight * 0.5 + let cornerRadius: CGFloat + if isSelecting { + cornerRadius = min(20.0, basePanelHeight * 0.5) + } else if self.hasMediaAccessoryPanel { + cornerRadius = 18.5 + } else { + cornerRadius = glassPanelHeight * 0.5 + } let backgroundOriginX: CGFloat = isSelecting ? panelSideInset : floorToScreenPixels((layout.size.width - panelSize.width) / 2.0) backgroundView.update(size: panelSize, cornerRadius: cornerRadius, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: ComponentTransition(transition)) @@ -2705,7 +2719,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel))) if case .glass = self.panelStyle { - transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: topAccessoryHeight + (self.isSelecting ? -11.0 : 0.0)), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height))) + let scrollFrame = CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: topAccessoryHeight + (self.isSelecting ? -11.0 : 0.0)), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height)) + transition.updatePosition(node: self.scrollNode, position: scrollFrame.center) + transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: self.scrollNode.view.contentOffset.x, y: 0.0), size: scrollFrame.size)) } if let progress = self.loadingProgress { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index e53efef7ef..1f78d6bbac 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2380,6 +2380,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var currentStoryIcon: UIImage? var currentGiftIcon: UIImage? var currentLocationIcon: UIImage? + var currentPollIcon: UIImage? var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)? @@ -2555,6 +2556,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var displayStoryReplyIcon = false var displayGiftIcon = false var displayLocationIcon = false + var displayPollIcon = false var ignoreForwardedIcon = false switch contentData { @@ -2867,7 +2869,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { displayStoryReplyIcon = true } else { for media in message.media { - if let _ = media as? TelegramMediaMap { + if let _ = media as? TelegramMediaPoll { + displayPollIcon = true + } else if let _ = media as? TelegramMediaMap { displayLocationIcon = true } else if let action = media as? TelegramMediaAction { switch action.action { @@ -3048,6 +3052,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { currentLocationIcon = PresentationResourcesChatList.locationIcon(item.presentationData.theme) } + if displayPollIcon { + currentPollIcon = PresentationResourcesChatList.pollIcon(item.presentationData.theme) + } + if let currentForwardedIcon { textLeftCutout += currentForwardedIcon.size.width if !contentImageSpecs.isEmpty { @@ -3084,6 +3092,15 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } } + if let currentPollIcon { + textLeftCutout += currentPollIcon.size.width + if !contentImageSpecs.isEmpty { + textLeftCutout += forwardedIconSpacing + } else { + textLeftCutout += contentImageTrailingSpace + } + } + for i in 0 ..< contentImageSpecs.count { if i != 0 { textLeftCutout += contentImageSpacing @@ -4745,6 +4762,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } else if let currentLocationIcon { messageTypeIcon = currentLocationIcon messageTypeIconOffset.y -= 2.0 - UIScreenPixel + } else if let currentPollIcon { + messageTypeIcon = currentPollIcon + messageTypeIconOffset.y -= 2.0 - UIScreenPixel } if let messageTypeIcon { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index 5ef43faa95..523dee3445 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -395,15 +395,13 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: messageText = text } case let poll as TelegramMediaPoll: - let pollPrefix = "📊 " - let entityOffset = (pollPrefix as NSString).length - messageText = "\(pollPrefix)\(poll.text)" + messageText = poll.text for entity in poll.textEntities { if case let .CustomEmoji(_, fileId) = entity.type { if customEmojiRanges == nil { customEmojiRanges = [] } - let range = NSRange(location: entityOffset + entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) + let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) let attribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile) customEmojiRanges?.append((range, attribute)) } diff --git a/submodules/ComponentFlow/Source/Components/Text.swift b/submodules/ComponentFlow/Source/Components/Text.swift index 65d9e81587..a14dc34dc2 100644 --- a/submodules/ComponentFlow/Source/Components/Text.swift +++ b/submodules/ComponentFlow/Source/Components/Text.swift @@ -83,6 +83,18 @@ public final class Text: Component { self.color = color self.tintColor = tintColor } + + public convenience init(attributedString: NSAttributedString, tintColor: UIColor? = nil) { + let attributes = attributedString.attributes(at: 0, effectiveRange: nil) + let font = attributes[.font] as? UIFont ?? UIFont.systemFont(ofSize: UIFont.systemFontSize) + let color = attributes[.foregroundColor] as? UIColor ?? .black + self.init( + text: attributedString.string, + font: font, + color: color, + tintColor: tintColor + ) + } public static func ==(lhs: Text, rhs: Text) -> Bool { if lhs.text != rhs.text { diff --git a/submodules/CounterControllerTitleView/Sources/CounterControllerTitleView.swift b/submodules/CounterControllerTitleView/Sources/CounterControllerTitleView.swift index 4b1fb6b824..ffff1738f8 100644 --- a/submodules/CounterControllerTitleView/Sources/CounterControllerTitleView.swift +++ b/submodules/CounterControllerTitleView/Sources/CounterControllerTitleView.swift @@ -37,6 +37,7 @@ public final class CounterControllerTitleView: UIView { private var primaryTextColor: UIColor? private var secondaryTextColor: UIColor? + private let verticalOffset: CGFloat private var nextLayoutTransition: ContainedViewLayoutTransition? @@ -65,7 +66,7 @@ public final class CounterControllerTitleView: UIView { let secondaryTextColor = self.secondaryTextColor ?? self.theme.rootController.navigationBar.secondaryTextColor self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: primaryTextColor) - let subtitleText = NSAttributedString(string: self.title.counter ?? "", font: Font.with(size: 13.0, traits: .monospacedNumbers), textColor: secondaryTextColor) + let subtitleText = NSAttributedString(string: self.title.counter ?? "", font: Font.with(size: 12.0, traits: .monospacedNumbers), textColor: secondaryTextColor) if let previousSubtitleText = self.subtitleNode.attributedText, previousSubtitleText.string.isEmpty != subtitleText.string.isEmpty && subtitleText.string.isEmpty { if let disappearingSubtitleNode = self.disappearingSubtitleNode { self.disappearingSubtitleNode = nil @@ -94,8 +95,9 @@ public final class CounterControllerTitleView: UIView { self.setNeedsLayout() } - public init(theme: PresentationTheme) { + public init(theme: PresentationTheme, verticalOffset: CGFloat = 0.0) { self.theme = theme + self.verticalOffset = verticalOffset self.titleNode = ImmediateTextNode() self.titleNode.displaysAsynchronously = false @@ -126,7 +128,7 @@ public final class CounterControllerTitleView: UIView { super.layoutSubviews() let size = self.bounds.size - let spacing: CGFloat = 0.0 + let spacing: CGFloat = 1.0 let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width), height: size.height)) let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: max(1.0, size.width), height: size.height)) @@ -146,11 +148,11 @@ public final class CounterControllerTitleView: UIView { self.nextLayoutTransition = nil } - let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + self.verticalOffset), size: titleSize) self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) transition.updatePosition(node: self.titleNode, position: titleFrame.center) - let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + spacing), size: subtitleSize) + let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + spacing + self.verticalOffset), size: subtitleSize) self.subtitleNode.bounds = CGRect(origin: CGPoint(), size: subtitleFrame.size) transition.updatePosition(node: self.subtitleNode, position: subtitleFrame.center) transition.updateTransformScale(node: self.subtitleNode, scale: self.title.counter != nil ? 1.0 : 0.001) diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 8c4bc2ac28..7d37c55c25 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -2149,7 +2149,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if isActive { self.setLivePhotoVideoVisible(true, animated: animated) videoNode.seek(0.0) - videoNode.play() + videoNode.playOnceWithSound(playAndRecord: false) } else { videoNode.pause() videoNode.seek(0.0) diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index 8eda7718f1..dbc2384f58 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -699,14 +699,14 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl self.titleNode.maximumNumberOfLines = 1 self.titleNode.isOpaque = false - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor) self.subtitleNode = ImmediateTextNode() self.subtitleNode.displaysAsynchronously = false self.subtitleNode.maximumNumberOfLines = 1 self.subtitleNode.isOpaque = false - self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor) + self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(12.0), textColor: theme.rootController.navigationBar.secondaryTextColor) super.init(frame: CGRect()) @@ -719,8 +719,8 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl } func updateTheme(theme: PresentationTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor) - self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor) + self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(12.0), textColor: theme.rootController.navigationBar.secondaryTextColor) if let size = self.validLayout { let _ = self.updateLayout(availableSize: size, transition: .immediate) } @@ -741,7 +741,7 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl let titleSize = self.titleNode.updateLayout(size) let subtitleSize = self.subtitleNode.updateLayout(size) - let spacing: CGFloat = 0.0 + let spacing: CGFloat = 1.0 let contentHeight = titleSize.height + spacing + subtitleSize.height let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - contentHeight) / 2.0)), size: titleSize) let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + spacing), size: subtitleSize) diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyICloudFilePicker.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyICloudFilePicker.swift index 1b6bc2022a..67c925492f 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyICloudFilePicker.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyICloudFilePicker.swift @@ -66,7 +66,7 @@ public enum LegacyICloudFilePickerMode { } } -public func legacyICloudFilePicker(theme: PresentationTheme, mode: LegacyICloudFilePickerMode = .default, url: URL? = nil, documentTypes: [String] = ["public.item"], forceDarkTheme: Bool = false, dismissed: @escaping () -> Void = {}, completion: @escaping ([URL]) -> Void) -> ViewController { +public func legacyICloudFilePicker(theme: PresentationTheme, mode: LegacyICloudFilePickerMode = .default, url: URL? = nil, documentTypes: [String] = ["public.item"], forceDarkTheme: Bool = false, dismissed: @escaping () -> Void = {}, completion: @escaping ([URL]) -> Void) -> ViewController { var dismissImpl: (() -> Void)? let legacyController = LegacyICloudFileController(presentation: .modal(animateIn: true), theme: theme, completion: { urls in dismissImpl?() diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 46e3498a0d..5730fd5119 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -933,7 +933,7 @@ public final class ListMessageFileItemNode: ListMessageNode { if statusUpdated && item.displayFileInfo { if let file = selectedMedia as? TelegramMediaFile { - updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList, isSavedMusic: item.isSavedMusic, isAttachMusic: item.isAttachMusic) + updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList, isSavedMusic: item.isSavedMusic, isAttachMusic: item.isAttachMusic || item.isStoryMusic) |> mapToSignal { value -> Signal in if case .Fetching = value.fetchStatus, !item.isDownloadList { return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue()) diff --git a/submodules/LocationUI/BUILD b/submodules/LocationUI/BUILD index 5b1581f58e..eb9f0aee55 100644 --- a/submodules/LocationUI/BUILD +++ b/submodules/LocationUI/BUILD @@ -53,6 +53,7 @@ swift_library( "//submodules/TelegramUI/Components/SearchInputPanelComponent", "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/CounterControllerTitleView", ], visibility = [ "//visibility:public", diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index df028bd6a0..634e4c5d3f 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -11,6 +11,7 @@ import CoreLocation import PresentationDataUtils import DeviceAccess import AttachmentUI +import CounterControllerTitleView public enum LocationPickerMode { case share(peer: EnginePeer?, selfPeer: EnginePeer?, hasLiveLocation: Bool) @@ -52,9 +53,16 @@ class LocationPickerInteraction { } public final class LocationPickerController: ViewController, AttachmentContainable { - public enum Source { + public enum Source: Equatable { + public enum PollMode: Equatable { + case description + case quizAnswer + case option + } + case generic case story + case poll(PollMode) } private var controllerNode: LocationPickerControllerNode { @@ -63,7 +71,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab private let context: AccountContext private let mode: LocationPickerMode - private let source: Source + let source: Source let initialLocation: CLLocationCoordinate2D? private let completion: (TelegramMediaMap, Int64?, String?, String?, String?) -> Void private var presentationData: PresentationData @@ -115,7 +123,6 @@ public final class LocationPickerController: ViewController, AttachmentContainab if case .glass = style { self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) } else { - self.title = self.presentationData.strings.Map_ChooseLocationTitle self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) } self.updateBarButtons() diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index 5717a31311..1059850d20 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -354,6 +354,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM private let cancelButton = ComponentView() private let searchButton = ComponentView() private let title = ComponentView() + private let subtitle = ComponentView() fileprivate let topEdgeEffectView: EdgeEffectView fileprivate let bottomEdgeEffectView: EdgeEffectView @@ -670,15 +671,18 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM var coordinate = userLocation?.coordinate switch strongSelf.mode { case .share: - if source == .story { + switch source { + case .generic: + title = presentationData.strings.Map_SendMyCurrentLocation + case .story: if let initialLocation = strongSelf.controller?.initialLocation { title = presentationData.strings.Location_AddThisLocation coordinate = initialLocation } else { title = presentationData.strings.Location_AddMyLocation } - } else { - title = presentationData.strings.Map_SendMyCurrentLocation + case .poll: + title = presentationData.strings.Location_AddThisLocation } case .pick: title = presentationData.strings.Map_SetThisLocation @@ -1358,6 +1362,27 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM self.view.addSubview(self.topEdgeEffectView) } + let titleSpacing: CGFloat = 1.0 + var combinedTitleHeight: CGFloat = 0.0 + + var subtitle: String = "" + if let source = self.controller?.source { + switch source { + case let .poll(pollMode): + //TODO:localize + switch pollMode { + case .description: + subtitle = "Add location to the poll description" + case .quizAnswer: + subtitle = "Add location to the quiz explanation" + case .option: + subtitle = "Add location to this option" + } + default: + break + } + } + let titleSize = self.title.update( transition: ComponentTransition(transition), component: AnyComponent( @@ -1374,7 +1399,37 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM environment: {}, containerSize: CGSize(width: 200.0, height: 40.0) ) - let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) + combinedTitleHeight += titleSize.height + + if !subtitle.isEmpty { + let subtitleSize = self.subtitle.update( + transition: ComponentTransition(transition), + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: subtitle, + font: Font.regular(12.0), + textColor: self.headerNode.mapNode.mapMode == .map ? self.presentationData.theme.rootController.navigationBar.primaryTextColor.withMultipliedAlpha(0.5) : .white.withAlphaComponent(0.5) + ) + ) + ) + ), + environment: {}, + containerSize: CGSize(width: 200.0, height: 40.0) + ) + combinedTitleHeight += subtitleSize.height + titleSpacing + + let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - subtitleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight + combinedTitleHeight) / 2.0) + 3.0 - subtitleSize.height), size: subtitleSize) + if let subtitleView = self.subtitle.view { + if subtitleView.superview == nil { + self.view.addSubview(subtitleView) + } + transition.updateFrame(view: subtitleView, frame: subtitleFrame) + } + } + + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight - combinedTitleHeight) / 2.0) + 3.0), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { self.view.addSubview(titleView) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index d5b061c33a..9f465f77a9 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -412,8 +412,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att if case .glass = controller.style { self.containerNode.view.addSubview(self.topEdgeEffectView) - if case let .assets(_, mode) = controller.subject, [.default, .story].contains(mode) { - self.containerNode.view.addSubview(self.bottomEdgeEffectView) + if case let .assets(_, mode) = controller.subject { + switch mode { + case .default, .story, .poll: + self.containerNode.view.addSubview(self.bottomEdgeEffectView) + default: + break + } } } @@ -2046,6 +2051,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.titleView.title = presentationData.strings.MediaPicker_ChooseCover case let .poll(pollMode): self.titleView.title = presentationData.strings.MediaPicker_Recents + //TODO:localize switch pollMode { case .description: self.titleView.subtitle = "Add media to the poll description" @@ -2560,7 +2566,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att titleIsEnabled = false } - self.titleView.updateTitle(title: title, isEnabled: titleIsEnabled, animated: true) + self.titleView.updateTitle(title: title, subtitle: self.titleView.subtitle, isEnabled: titleIsEnabled, animated: true) self.cancelButtonNode.setState(isEnabled ? .cancel : .back, animated: true) isBack = !isEnabled diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 73000e552f..9bac85a6c5 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -130,6 +130,7 @@ public enum PresentationResourceKey: Int32 { case chatListStoryReplyIcon case chatListGiftIcon case chatListLocationIcon + case chatListPollIcon case chatListGeneralTopicIcon case chatListGeneralTopicTemplateIcon @@ -420,4 +421,5 @@ public enum PresentationResourceParameterKey: Hashable { case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType) case chatReplyStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType) + case chatReplyPollIndicatorIcon(type: ChatExpiredStoryIndicatorType) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 29c4126a9c..2fb04174e4 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1277,9 +1277,9 @@ public struct PresentationResourcesChat { let foregroundColor: UIColor switch type { case .incoming: - foregroundColor = theme.chat.message.incoming.mediaActiveControlColor + foregroundColor = theme.chat.message.incoming.accentTextColor case .outgoing: - foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor + foregroundColor = theme.chat.message.outgoing.accentTextColor case .free: foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText } @@ -1303,9 +1303,9 @@ public struct PresentationResourcesChat { let foregroundColor: UIColor switch type { case .incoming: - foregroundColor = theme.chat.message.incoming.mediaActiveControlColor + foregroundColor = theme.chat.message.incoming.accentTextColor case .outgoing: - foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor + foregroundColor = theme.chat.message.outgoing.accentTextColor case .free: foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText } @@ -1322,6 +1322,32 @@ public struct PresentationResourcesChat { }) } + public static func chatReplyPollIndicatorIcon(_ theme: PresentationTheme, type: ChatExpiredStoryIndicatorType) -> UIImage? { + return theme.image(PresentationResourceParameterKey.chatReplyStoryIndicatorIcon(type: type), { theme in + return generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + let foregroundColor: UIColor + switch type { + case .incoming: + foregroundColor = theme.chat.message.incoming.accentTextColor + case .outgoing: + foregroundColor = theme.chat.message.outgoing.accentTextColor + case .free: + foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText + } + + if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/PollReplyIcon"), color: foregroundColor) { + UIGraphicsPushContext(context) + + let fittedSize = image.size + image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - fittedSize.width) * 0.5), y: floor((size.height - fittedSize.height) * 0.5)), size: fittedSize), blendMode: .normal, alpha: 1.0) + + UIGraphicsPopContext() + } + }) + }) + } + public static func storyViewListLikeIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.storyViewListLikeIcon.rawValue, { theme in return generateTintedImage(image: UIImage(bundleImageName: "Stories/InputLikeOn"), color: UIColor(rgb: 0xFF3B30)) @@ -1417,8 +1443,8 @@ public struct PresentationResourcesChat { context.setFillColor(UIColor.white.cgColor) let lineHeight = 2.0 - UIScreenPixel - context.addPath(CGPath(roundedRect: CGRect(x: 1.0, y: 7.0, width: 15.0, height: lineHeight), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil)) - context.addPath(CGPath(roundedRect: CGRect(x: 8.0, y: 0.0, width: lineHeight, height: 15.0), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil)) + context.addPath(CGPath(roundedRect: CGRect(x: 1.0, y: 7.0 - UIScreenPixel, width: 15.0, height: lineHeight), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil)) + context.addPath(CGPath(roundedRect: CGRect(x: 8.0 - UIScreenPixel, y: 0.0, width: lineHeight, height: 15.0), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil)) context.fillPath() }) }) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift index 9016c18d7e..8e5c2cc67f 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift @@ -284,6 +284,24 @@ public struct PresentationResourcesChatList { }) } + public static func pollIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListPollIcon.rawValue, { theme in + if let image = UIImage(bundleImageName: "Chat List/PollBadgeIcon") { + return generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context in + if let cgImage = image.cgImage { + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.clip(to: CGRect(origin: .zero, size: size).insetBy(dx: 2.0, dy: 2.0), mask: cgImage) + context.setFillColor(theme.chatList.muteIconColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + } + }) + } else { + return nil + } + }) + } + public static func verifiedIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") { diff --git a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift index 809a6d3369..ed95f4d6b3 100644 --- a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift +++ b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift @@ -466,7 +466,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation case .expiredVideoMessage: return (NSAttributedString(string: strings.Message_VideoMessageExpired), true) case let .poll(text): - return (NSAttributedString(string: "📊 \(text)"), false) + return (NSAttributedString(string: text), false) case let .todo(text): return (NSAttributedString(string: "☑️ \(text)"), false) case let .restricted(text): diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift index 8e4fc314e4..77775cff94 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift @@ -26,6 +26,7 @@ import ContextUI private final class AttachmentFileControllerArguments { let context: AccountContext let isAudio: Bool + let isAttach: Bool let openGallery: () -> Void let openFiles: () -> Void let scanDocument: () -> Void @@ -38,9 +39,10 @@ private final class AttachmentFileControllerArguments { let setMessageSelection: ([MessageId], Message?, Bool) -> Void let openMessageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void) - init(context: AccountContext, isAudio: Bool, openGallery: @escaping () -> Void, openFiles: @escaping () -> Void, scanDocument: @escaping () -> Void, expandSavedMusic: @escaping () -> Void, expandRecentMusic: @escaping () -> Void, send: @escaping (Message) -> Void, toggleMediaPlayback: @escaping (Message) -> Void, isSelectionActive: @escaping () -> Bool, toggleMessageSelection: @escaping (Message) -> Void, setMessageSelection: @escaping ([MessageId], Message?, Bool) -> Void, openMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)) { + init(context: AccountContext, isAudio: Bool, isAttach: Bool, openGallery: @escaping () -> Void, openFiles: @escaping () -> Void, scanDocument: @escaping () -> Void, expandSavedMusic: @escaping () -> Void, expandRecentMusic: @escaping () -> Void, send: @escaping (Message) -> Void, toggleMediaPlayback: @escaping (Message) -> Void, isSelectionActive: @escaping () -> Bool, toggleMessageSelection: @escaping (Message) -> Void, setMessageSelection: @escaping ([MessageId], Message?, Bool) -> Void, openMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)) { self.context = context self.isAudio = isAudio + self.isAttach = isAttach self.openGallery = openGallery self.openFiles = openFiles self.scanDocument = scanDocument @@ -250,7 +252,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry { let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false)) - return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: arguments.context.account.peerId), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section) + return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: arguments.context.account.peerId), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section) case let .savedShowMore(theme, text): return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: { arguments.expandSavedMusic() @@ -275,8 +277,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry { let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false)) - - return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section) + return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section) case let .recentShowMore(theme, text): return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: { arguments.expandRecentMusic() @@ -301,8 +302,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry { let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false)) - - return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section) + return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section) case let .globalShowMore(theme, text): return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: { @@ -434,6 +434,8 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon fileprivate var caption: NSAttributedString? fileprivate var selectionCount = ValuePromise(0) + fileprivate var bottomEdgeColor: UIColor = .clear + fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?) -> Void)? var delayDisappear = false @@ -441,6 +443,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon var hasBottomEdgeEffect = true var resetForReuseImpl: () -> Void = {} + var onDismissImpl: () -> Void = {} public func resetForReuse() { self.resetForReuseImpl() self.scrollToTop?() @@ -451,12 +454,23 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset) self.delayDisappear = false } + + public func requestDismiss(completion: @escaping () -> Void) { + self.onDismissImpl() + completion() + } + + public func shouldDismissImmediately() -> Bool { + if self.hasBottomEdgeEffect { + self.onDismissImpl() + } + return true + } public var mediaPickerContext: AttachmentMediaPickerContext? { return AttachmentFileContext(controller: self) } - private var topEdgeEffectView: EdgeEffectView? private var bottomEdgeEffectView: EdgeEffectView? var isSearching: Bool = false { @@ -468,21 +482,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) - let topEdgeEffectView: EdgeEffectView - if let current = self.topEdgeEffectView { - topEdgeEffectView = current - } else { - topEdgeEffectView = EdgeEffectView() - if let navigationBar = self.navigationBar { - self.view.insertSubview(topEdgeEffectView, belowSubview: navigationBar.view) - } - self.topEdgeEffectView = topEdgeEffectView - } - let edgeEffectHeight: CGFloat = 88.0 - let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) - transition.updateFrame(view: topEdgeEffectView, frame: topEdgeEffectFrame) - topEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) if self.hasBottomEdgeEffect { let bottomEdgeEffectView: EdgeEffectView @@ -497,7 +497,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) transition.updateFrame(view: bottomEdgeEffectView, frame: bottomEdgeEffectFrame) transition.updateAlpha(layer: bottomEdgeEffectView.layer, alpha: self.isSearching ? 0.0 : 1.0) - bottomEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition)) + bottomEdgeEffectView.update(content: self.bottomEdgeColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition)) } else if let bottomEdgeEffectView = self.bottomEdgeEffectView { bottomEdgeEffectView.removeFromSuperview() } @@ -521,13 +521,32 @@ private func messageSelectionState(state: AttachmentFileControllerState, message public enum AttachmentFileControllerMode { case recent - case audio + case audio(story: Bool) + + var isAudio: Bool { + if case .audio = self { + return true + } else { + return false + } + } +} + +public enum AttachmentFileControllerSource: Equatable { + public enum PollMode: Equatable { + case description + case quizAnswer + } + + case generic + case poll(PollMode) } public func makeAttachmentFileControllerImpl( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: AttachmentFileControllerMode = .recent, + source: AttachmentFileControllerSource = .generic, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, @@ -549,10 +568,20 @@ public func makeAttachmentFileControllerImpl( var updateIsSearchingImpl: ((Bool) -> Void)? var updateSelectionCountImpl: ((Int) -> Void)? var presentInGlobalOverlayImpl: ((ViewController) -> Void)? - + var updateBottomColorImpl: ((UIColor) -> Void)? + + var isAudio = false + var isAttach = true + if case let .audio(isStory) = mode { + isAudio = true + isAttach = !isStory + } + + var didPreviewAudio = false let arguments = AttachmentFileControllerArguments( context: context, - isAudio: mode == .audio, + isAudio: isAudio, + isAttach: isAttach, openGallery: { presentGallery() }, @@ -603,6 +632,8 @@ public func makeAttachmentFileControllerImpl( } }, toggleMediaPlayback: { message in + didPreviewAudio = true + let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil) context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause)) }, @@ -727,7 +758,7 @@ public func makeAttachmentFileControllerImpl( let existingCloseButton = Atomic(value: nil) let existingSearchButton = Atomic(value: nil) - + let previousRecentDocuments = Atomic<[Message]?>(value: nil) let signal = combineLatest(queue: Queue.mainQueue(), presentationData, @@ -823,21 +854,32 @@ public func makeAttachmentFileControllerImpl( } var rightNavigationButton: ItemListNavigationButton? - if bannedSendMedia == nil && (recentDocuments == nil || (recentDocuments?.count ?? 0) > 10 || (mode == .audio && hasAudioSearch)) { + if bannedSendMedia == nil && (recentDocuments == nil || (recentDocuments?.count ?? 0) > 10 || (mode.isAudio && hasAudioSearch)) { rightNavigationButton = searchButtonNode.flatMap { ItemListNavigationButton(content: .node($0), style: .regular, enabled: true, action: {}) } } let title: String + var subtitle: String? switch mode { case .recent: title = presentationData.strings.Attachment_File case .audio: title = presentationData.strings.MediaEditor_Audio_Title } - + + if case let .poll(pollMode) = source { + //TODO:localize + switch pollMode { + case .description: + subtitle = "Add file to the poll description" + case .quizAnswer: + subtitle = "Add file to the quiz explanation" + } + } + let controllerState = ItemListControllerState( presentationData: ItemListPresentationData(presentationData), - title: .text(title), + title: subtitle.flatMap { .textWithSubtitle(title, $0) } ?? .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), @@ -881,6 +923,8 @@ public func makeAttachmentFileControllerImpl( let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: attachmentFileControllerEntries(presentationData: presentationData, mode: mode, state: state, savedMusic: savedMusic, recentDocuments: recentDocuments, hasScan: presentDocumentScanner != nil, empty: bannedSendMedia != nil), style: .blocks, emptyStateItem: emptyItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: animateChanges) + updateBottomColorImpl?(presentationData.theme.list.blocksBackgroundColor) + return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() @@ -888,7 +932,6 @@ public func makeAttachmentFileControllerImpl( } let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true) - controller.mulitpleCompletion = { _, _, _ in let _ = stateValue.with({ state in if let selectedMessageIds = state.selectedMessageIds { @@ -928,12 +971,25 @@ public func makeAttachmentFileControllerImpl( updatedState.selectedMessageIds = nil return updatedState } + + if didPreviewAudio { + context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause)) + } + } + controller.onDismissImpl = { + if didPreviewAudio { + context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause)) + } } updateIsSearchingImpl = { [weak controller] isSearching in controller?.isSearching = isSearching } dismissImpl = { [weak controller] in controller?.dismiss(animated: true) + + if didPreviewAudio { + context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause)) + } } dismissInputImpl = { [weak controller] in controller?.view.endEditing(true) @@ -950,6 +1006,9 @@ public func makeAttachmentFileControllerImpl( presentInGlobalOverlayImpl = { [weak controller] c in controller?.presentInGlobalOverlay(c) } + updateBottomColorImpl = { [weak controller] color in + controller?.bottomEdgeColor = color + } return controller } @@ -962,15 +1021,16 @@ public func storyAudioPickerController( var dismissImpl: (() -> Void)? let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme) let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, style: .glass, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false) + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, style: .glass, chatLocation: nil, buttons: [.audio], initialButton: .audio, fromMenu: false, hasTextInput: false) controller.requestController = { _, present in - let filePickerController = makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: .audio, bannedSendMedia: nil, presentGallery: {}, presentFiles: { + let filePickerController = makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: .audio(story: true), bannedSendMedia: nil, presentGallery: {}, presentFiles: { selectFromFiles() dismissImpl?() }, presentDocumentScanner: nil, send: { files in completion(files.first!) dismissImpl?() }) as! AttachmentFileControllerImpl + filePickerController.hasBottomEdgeEffect = false present(filePickerController, filePickerController.mediaPickerContext) return true } diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift index 013499cdeb..5b26071ced 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift @@ -74,12 +74,11 @@ final class AttachmentFileSearchItem: ItemListControllerSearch { func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { switch self.mode { case .audio: - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } if let current = current as? AttachmentFileSearchNavigationContentNode { - current.updateTheme(presentationData.theme) + current.updateTheme(self.presentationData.theme) return current } else { - return AttachmentFileSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, cancel: self.cancel, focus: self.focus, updateActivity: { _ in + return AttachmentFileSearchNavigationContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, cancel: self.cancel, focus: self.focus, updateActivity: { _ in }) } default: @@ -164,7 +163,7 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode { strings: self.presentationData.strings, metrics: layout.metrics, safeInsets: layout.safeInsets, - placeholder: self.mode == .audio ? self.presentationData.strings.Attachment_FilesSearchPlaceholder : self.presentationData.strings.Attachment_FilesSearchPlaceholder, + placeholder: self.presentationData.strings.Attachment_FilesSearchPlaceholder, updated: { [weak self] query in guard let self else { return @@ -329,9 +328,9 @@ private enum AttachmentFileSearchEntry: Comparable, Identifiable { interaction.toggleMediaPlayback(message) }, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] }) - let displayFileInfo = mode == .audio - let isStoryMusic = mode == .audio - let isDownloadList = mode == .audio + let displayFileInfo = mode.isAudio + let isStoryMusic = mode.isAudio + let isDownloadList = mode.isAudio return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), systemStyle: .glass, context: interaction.context, chatLocation: .peer(id: PeerId(0)), interaction: itemInteraction, message: message, selection: .none, displayHeader: false, isDownloadList: isDownloadList, isStoryMusic: isStoryMusic, isAttachMusic: true, displayFileInfo: displayFileInfo, displayBackground: true, style: .blocks, sectionId: section) case let .showMore(text, section): @@ -661,6 +660,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon } } } else { + entries.append(.header(title: " ", section: 0)) var index: Int32 = 0 for _ in 0 ..< 16 { entries.append(.file(index: index, message: nil, section: 0)) @@ -1009,6 +1009,20 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont func updateTheme(_ theme: PresentationTheme) { self.theme = theme + + let searchBarTheme = SearchBarNodeTheme( + background: .clear, + separator: .clear, + inputFill: .clear, + primaryText: theme.chat.inputPanel.panelControlColor, + placeholder: theme.chat.inputPanel.inputPlaceholderColor, + inputIcon: theme.chat.inputPanel.inputControlColor, + inputClear: theme.chat.inputPanel.panelControlColor, + accent: theme.chat.inputPanel.panelControlAccentColor, + keyboard: theme.rootController.keyboardColor + ) + + self.searchBar.updateThemeAndStrings(theme: searchBarTheme, presentationTheme: theme, strings: self.strings) if let params = self.params { let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) } @@ -1022,7 +1036,7 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont } override var nominalHeight: CGFloat { - return 60.0 + return 68.0 } override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 28e00d2257..8941219f71 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -815,6 +815,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if strongSelf.selectionNode != nil { return .none } + if let item = strongSelf.item, item.controllerInteraction.focusedPollAddOptionMessageId != nil { + return .none + } if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) { if case let .action(action) = action, !action.contextMenuOnLongPress { return .none diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift index 9180569c7f..0a48b1b961 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift @@ -1368,6 +1368,7 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode { var attachPressed: (() -> Void)? var mediaPressed: (() -> Void)? var modeSelectorPressed: (() -> Void)? + var requestSave: (() -> Void)? static let characterLimit = 100 private static let leftInset: CGFloat = 50.0 @@ -1449,17 +1450,25 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode { hideKeyboard: self.currentFocusedTextInputIsMedia, customInputView: nil, placeholder: NSAttributedString(string: strings.CreatePoll_AddOption, font: font, textColor: currentPlaceholderColor), + placeholderVerticalOffset: 1.0 + UIScreenPixel, resetText: nil, isOneLineWhenUnfocused: false, characterLimit: ChatMessagePollAddOptionNode.characterLimit, enableInlineAnimations: true, - emptyLineHandling: .allowed, + emptyLineHandling: .notAllowed, formatMenuAvailability: .none, + returnKeyType: .done, lockedFormatAction: { }, present: { _ in }, paste: { _ in + }, + returnKeyAction: { [weak self] in + self?.requestSave?() + }, + backspaceKeyAction: { + } ) } @@ -2150,17 +2159,14 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { guard let item = self.item else { return } - item.controllerInteraction.updatePresentationState { [weak item] state in - var focusedTextInputIsMedia = false + item.controllerInteraction.updatePresentationState { state in let updatedState = state.updatedInputMode({ inputMode in if case .media = inputMode { return .text } else { - focusedTextInputIsMedia = true return .media(mode: .other, expanded: .none, focused: true) } }) - item?.controllerInteraction.focusedTextInputIsMedia = focusedTextInputIsMedia return updatedState } @@ -2185,6 +2191,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { presentPollAttachmentScreen( context: item.context, updatedPresentationData: item.controllerInteraction.updatedPresentationData, + subject: .option, availableButtons: [.gallery, .sticker, .emoji, .location], present: { [weak item] controller in item?.controllerInteraction.navigationController()?.pushViewController(controller) @@ -2291,6 +2298,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { let optionData = "\(poll.options.count)".data(using: .utf8)! item.controllerInteraction.requestAddMessagePollOption(item.message.id, trimmedNewOptionText, entities, optionData, self.currentNewOptionMedia?.media) return + } else if self.newOptionIsFocused { + return } var hasSelection = false @@ -2932,6 +2941,9 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { addOptionNode.modeSelectorPressed = { [weak self] in self?.toggleNewOptionInputMode() } + addOptionNode.requestSave = { [weak self] in + self?.buttonPressed() + } addOptionNode.focusUpdated = { [weak self] focused in guard let self else { return diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index 23357feb71..8cf534d057 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -203,6 +203,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { let isText: Bool var isExpiredStory: Bool = false var isStory: Bool = false + var isPoll: Bool = false let titleColor: UIColor let mainColor: UIColor @@ -429,6 +430,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } let textColor: UIColor + let iconColor: UIColor switch arguments.type { case let .bubble(incoming): @@ -439,8 +441,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } else { textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor } + iconColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor case .standalone: textColor = titleColor + iconColor = titleColor } var textLeftInset: CGFloat = 0.0 @@ -516,6 +520,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } } else { messageText = NSAttributedString(string: textString.string, font: textFont, textColor: textColor) + + if let _ = arguments.message?.media.first(where: { $0 is TelegramMediaPoll }) as? TelegramMediaPoll { + isPoll = true + } } var leftInset: CGFloat = 11.0 @@ -562,6 +570,26 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { hasRoundImage = true } break + } else if let poll = media as? TelegramMediaPoll, let media = poll.attachedMedia { + if let image = media as? TelegramMediaImage { + updatedMediaReference = .message(message: MessageReference(message), media: image) + if let representation = largestRepresentationForPhoto(image) { + imageDimensions = representation.dimensions.cgSize + } + break + } else if let file = media as? TelegramMediaFile, !file.isVideoSticker { + updatedMediaReference = .message(message: MessageReference(message), media: file) + + if let dimensions = file.dimensions { + imageDimensions = dimensions.cgSize + } else if let representation = largestImageRepresentation(file.previewRepresentations), !file.isSticker { + imageDimensions = representation.dimensions.cgSize + } + if file.isInstantVideo { + hasRoundImage = true + } + break + } } } } else if let story = arguments.story, let storyPeer = arguments.parentMessage.peers[story.peerId], let storyItem = arguments.parentMessage.associatedStories[story] { @@ -646,7 +674,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: maxTitleNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: contrainedTextSize.width - additionalTitleWidth, height: contrainedTextSize.height), alignment: .natural, cutout: nil, insets: textInsets)) - if isExpiredStory || isStory { + if isExpiredStory || isStory || isPoll { contrainedTextSize.width -= 26.0 } @@ -713,7 +741,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { size.width = max(titleLayout.size.width + additionalTitleWidth - textInsets.left - textInsets.right, textLeftInset + textLayout.size.width - textInsets.left - textInsets.right - textCutoutWidth) + leftInset + 6.0 size.height = titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing size.height += 2.0 - if isExpiredStory || isStory { + if isExpiredStory || isStory || isPoll { size.width += 16.0 } @@ -786,7 +814,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: spacing - textInsets.top + 1.0), size: titleLayout.size) let textFrame = CGRect(origin: CGPoint(x: textLeftInset + leftInset - textInsets.left - 2.0 - textCutoutWidth, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top - 2.0), size: textLayout.size) - let effectiveTextFrame = textFrame.offsetBy(dx: (isExpiredStory || isStory) ? 18.0 : 0.0, dy: 0.0) + let effectiveTextFrame = textFrame.offsetBy(dx: (isExpiredStory || isStory || isPoll) ? 18.0 : 0.0, dy: 0.0) if textNode.textNode.bounds.isEmpty || !animation.isAnimated || textNode.textNode.bounds.height == effectiveTextFrame.height { textNode.textNode.frame = effectiveTextFrame @@ -809,7 +837,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } } - if isExpiredStory || isStory { + if isExpiredStory || isStory || isPoll { let expiredStoryIconView: UIImageView if let current = node.expiredStoryIconView { expiredStoryIconView = current @@ -827,7 +855,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { imageType = incoming ? .incoming : .outgoing } - if isExpiredStory { + if isPoll { + expiredStoryIconView.image = PresentationResourcesChat.chatReplyPollIndicatorIcon(arguments.presentationData.theme.theme, type: imageType) + } else if isExpiredStory { expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType) } else { expiredStoryIconView.image = PresentationResourcesChat.chatReplyStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType) @@ -921,7 +951,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { if let todoItemCompleted { let checkLayerFrame = CGRect(origin: CGPoint(x: textFrame.minX - 16.0, y: textFrame.minY + 5.0), size: CGSize(width: 12.0, height: 12.0)) - let checkTheme = CheckNodeTheme(backgroundColor: titleColor, strokeColor: .clear, borderColor: titleColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0) + let checkTheme = CheckNodeTheme(backgroundColor: iconColor, strokeColor: .clear, borderColor: iconColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0) let checkLayer: CheckLayer if let current = node.checkLayer { diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 4b1f0db3f2..c0964e6c29 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -327,6 +327,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public var canReadHistory: Bool = false public var summarizedMessageIds: Set = Set() public var focusedTextInputIsMedia: Bool = false + public var focusedPollAddOptionMessageId: MessageId? private var isOpeningMediaValue: Bool = false public var isOpeningMedia: Bool { diff --git a/submodules/TelegramUI/Components/ComposePollScreen/BUILD b/submodules/TelegramUI/Components/ComposePollScreen/BUILD index e215598c3c..ab1ce368b8 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/BUILD +++ b/submodules/TelegramUI/Components/ComposePollScreen/BUILD @@ -56,7 +56,8 @@ swift_library( "//submodules/Components/PagerComponent", "//submodules/FeaturedStickersScreen", "//submodules/TelegramNotices", - "//submodules/StickerPeekUI:StickerPeekUI", + "//submodules/StickerPeekUI", + "//submodules/CounterControllerTitleView", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift index 34d0d2d31f..7521659bf6 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift @@ -851,9 +851,26 @@ final class ComposePollScreenComponent: Component { availableButtons = [.gallery, .sticker, .emoji, .location] } - presentPollAttachmentScreen(context: component.context, updatedPresentationData: nil, availableButtons: availableButtons, inputMediaNodeData: self.inputMediaNodeDataPromise.get() |> map(Optional.init), present: { [weak self] c in - (self?.environment?.controller() as? ComposePollScreen)?.parentController()?.push(c) - }, completion: { [weak self] media in + let pollAttachmentSubject: PollAttachmentSubject + switch subject { + case .description: + pollAttachmentSubject = .description + case .quizAnswer: + pollAttachmentSubject = .quizAnswer + case .pollOption: + pollAttachmentSubject = .option + } + + presentPollAttachmentScreen( + context: component.context, + updatedPresentationData: nil, + subject: pollAttachmentSubject, + availableButtons: availableButtons, + inputMediaNodeData: self.inputMediaNodeDataPromise.get() |> map(Optional.init), + present: { [weak self] c in + (self?.environment?.controller() as? ComposePollScreen)?.parentController()?.push(c) + }, + completion: { [weak self] media in guard let self else { return } @@ -1729,7 +1746,13 @@ final class ComposePollScreenComponent: Component { maximumNumberOfLines: 0 )) } else { - let remainingCount = component.initialData.maxPollAnswersCount - self.pollOptions.count + var filledOptionsCount = 0 + for option in self.pollOptions { + if option.textInputState.hasText { + filledOptionsCount += 1 + } + } + let remainingCount = component.initialData.maxPollAnswersCount - filledOptionsCount let rawString = environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount)) var pollOptionsFooterItems: [AnimatedTextComponent.Item] = [] @@ -2813,7 +2836,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont public func prepareForReuse() { } - public func requestDismiss(completion: @escaping () -> Void) { + public func requestDismiss(completion: @escaping () -> Void) { completion() } diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift index b4be8247fe..e4a1a34b54 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift @@ -16,9 +16,16 @@ import LocationUI import AttachmentFileController import ChatEntityKeyboardInputNode +public enum PollAttachmentSubject { + case description + case quizAnswer + case option +} + public func presentPollAttachmentScreen( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, + subject: PollAttachmentSubject, availableButtons: [AttachmentButtonType], inputMediaNodeData: Signal = .single(nil), present: @escaping (ViewController) -> Void, @@ -36,16 +43,18 @@ public func presentPollAttachmentScreen( return nil } ) -// attachmentController.getSourceRect = { [weak self] in -// if let strongSelf = self { -// return strongSelf.chatDisplayNode.frameForAttachmentButton()?.offsetBy(dx: strongSelf.chatDisplayNode.supernode?.frame.minX ?? 0.0, dy: 0.0) -// } else { -// return nil -// } -// } attachmentController.requestController = { [weak attachmentController] type, controllerCompletion in switch type { case .gallery: + let mediaPickerPollSubject: MediaPickerScreenImpl.Subject.AssetsMode.PollMode + switch subject { + case .description: + mediaPickerPollSubject = .description + case .quizAnswer: + mediaPickerPollSubject = .quizAnswer + case .option: + mediaPickerPollSubject = .option + } let controller = MediaPickerScreenImpl( context: context, updatedPresentationData: updatedPresentationData, @@ -54,7 +63,7 @@ public func presentPollAttachmentScreen( threadTitle: nil, chatLocation: nil, enableMultiselection: false, - subject: .assets(nil, .poll(.option)) + subject: .assets(nil, .poll(mediaPickerPollSubject)) ) controller.getCaptionPanelView = { return nil @@ -71,24 +80,49 @@ public func presentPollAttachmentScreen( controllerCompletion(controller, controller.mediaPickerContext) return true case .file: - let controller = context.sharedContext.makeAttachmentFileController(context: context, updatedPresentationData: updatedPresentationData, audio: false, bannedSendMedia: nil, presentGallery: { [weak attachmentController] in - attachmentController?.dismiss(animated: true) - //self?.presentFileGallery() - }, presentFiles: { [weak attachmentController] in - attachmentController?.dismiss(animated: true) - //self?.presentICloudFileGallery() - }, presentDocumentScanner: { - //self?.presentDocumentScanner() - }, send: { mediaReferences in - completion(mediaReferences.first!) - }) - guard let controller = controller as? AttachmentFileControllerImpl else { - return false + let filePickerPollSubject: AttachmentFileControllerSource.PollMode + switch subject { + case .description: + filePickerPollSubject = .description + case .quizAnswer: + filePickerPollSubject = .quizAnswer + default: + filePickerPollSubject = .description } + let controller = makeAttachmentFileControllerImpl( + context: context, + updatedPresentationData: updatedPresentationData, + source: .poll(filePickerPollSubject), + bannedSendMedia: nil, + presentGallery: {}, + presentFiles: { [weak attachmentController] in + attachmentController?.dismiss(animated: true) + //TODO + }, + presentDocumentScanner: nil, + send: { mediaReferences in + completion(mediaReferences.first!) + } + ) as! AttachmentFileControllerImpl controllerCompletion(controller, controller.mediaPickerContext) return true case .location: - let controller = LocationPickerController(context: context, style: .glass, updatedPresentationData: updatedPresentationData, mode: .share(peer: nil, selfPeer: nil, hasLiveLocation: false), completion: { location, _, _, _, _ in + let locationPickerPollSubject: LocationPickerController.Source.PollMode + switch subject { + case .description: + locationPickerPollSubject = .description + case .quizAnswer: + locationPickerPollSubject = .quizAnswer + case .option: + locationPickerPollSubject = .option + } + let controller = LocationPickerController( + context: context, + style: .glass, + updatedPresentationData: updatedPresentationData, + mode: .share(peer: nil, selfPeer: nil, hasLiveLocation: false), + source: .poll(locationPickerPollSubject), + completion: { location, _, _, _, _ in completion(.standalone(media: location)) }) controllerCompletion(controller, controller.mediaPickerContext) @@ -100,9 +134,23 @@ public func presentPollAttachmentScreen( guard let content = content?.stickers else { return } - let controller = StickerAttachmentScreen(context: context, mode: .stickers(content), completion: { sticker in - completion(sticker) - }) + let stickerPickerPollSubject: StickerAttachmentScreen.Source.PollMode + switch subject { + case .description: + stickerPickerPollSubject = .description + case .quizAnswer: + stickerPickerPollSubject = .quizAnswer + case .option: + stickerPickerPollSubject = .option + } + let controller = StickerAttachmentScreen( + context: context, + mode: .stickers(content), + source: .poll(stickerPickerPollSubject), + completion: { sticker in + completion(sticker) + } + ) controllerCompletion(controller, controller.mediaPickerContext) }) return true @@ -113,9 +161,23 @@ public func presentPollAttachmentScreen( guard let content = content?.emoji else { return } - let controller = StickerAttachmentScreen(context: context, mode: .emoji(content), completion: { sticker in - completion(sticker) - }) + let stickerPickerPollSubject: StickerAttachmentScreen.Source.PollMode + switch subject { + case .description: + stickerPickerPollSubject = .description + case .quizAnswer: + stickerPickerPollSubject = .quizAnswer + case .option: + stickerPickerPollSubject = .option + } + let controller = StickerAttachmentScreen( + context: context, + mode: .emoji(content), + source: .poll(stickerPickerPollSubject), + completion: { sticker in + completion(sticker) + } + ) controllerCompletion(controller, controller.mediaPickerContext) }) return true diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/StickerAttachmentScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/StickerAttachmentScreen.swift index b94a6e02b5..634d8cf5c2 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/StickerAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/StickerAttachmentScreen.swift @@ -15,6 +15,10 @@ import ChatPresentationInterfaceState import PagerComponent import FeaturedStickersScreen import TelegramNotices +import CounterControllerTitleView +import GlassBackgroundComponent +import GlassBarButtonComponent +import BundleIconComponent final class StickerAttachmentScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -49,8 +53,10 @@ final class StickerAttachmentScreenComponent: Component { final class View: UIView, UIScrollViewDelegate { fileprivate let keyboardView: ComponentView private let keyboardClippingView: KeyboardClippingView + private let panelBackgroundView: GlassBackgroundView + private let panelClippingView: UIView private let panelHostView: PagerExternalTopPanelContainer - private let panelSeparatorView: UIView + private let cancelButton: ComponentView private var component: StickerAttachmentScreenComponent? private(set) weak var state: EmptyComponentState? @@ -109,14 +115,17 @@ final class StickerAttachmentScreenComponent: Component { override init(frame: CGRect) { self.keyboardView = ComponentView() self.keyboardClippingView = KeyboardClippingView() + self.panelBackgroundView = GlassBackgroundView() + self.panelClippingView = UIView() self.panelHostView = PagerExternalTopPanelContainer() - self.panelSeparatorView = UIView() + self.cancelButton = ComponentView() super.init(frame: frame) self.addSubview(self.keyboardClippingView) - self.addSubview(self.panelSeparatorView) - self.addSubview(self.panelHostView) + self.addSubview(self.panelBackgroundView) + self.panelBackgroundView.contentView.addSubview(self.panelClippingView) + self.panelClippingView.addSubview(self.panelHostView) self.interaction = ChatEntityKeyboardInputNode.Interaction( sendSticker: { [weak self] file, _, _, _, _, _, _, _, _ in @@ -200,10 +209,7 @@ final class StickerAttachmentScreenComponent: Component { (self.environment?.controller() as? StickerAttachmentScreen)?.dismiss(animated: true) } - func updateContent() { - guard let component = self.component else { - return - } + func updateContent(component: StickerAttachmentScreenComponent) { self.emojiContent?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( performItemAction: { [weak self] groupId, item, _, _, _, _ in guard let self, let component = self.component else { @@ -599,7 +605,7 @@ final class StickerAttachmentScreenComponent: Component { externalBackground: nil, externalExpansionView: nil, customContentView: nil, - useOpaqueTheme: false, + useOpaqueTheme: true, hideBackground: true, stateContext: nil, addImage: nil @@ -872,7 +878,7 @@ final class StickerAttachmentScreenComponent: Component { externalBackground: nil, externalExpansionView: nil, customContentView: nil, - useOpaqueTheme: false, + useOpaqueTheme: true, hideBackground: true, stateContext: nil, addImage: nil @@ -884,7 +890,6 @@ final class StickerAttachmentScreenComponent: Component { self.environment = environment self.backgroundColor = environment.theme.list.plainBackgroundColor - self.panelSeparatorView.backgroundColor = .clear if self.component == nil { let data = combineLatest( @@ -933,10 +938,7 @@ final class StickerAttachmentScreenComponent: Component { } self.stickerContent = stickerContent } - Queue.mainQueue().justDispatch { - self.updateContent() - self.state?.updated() - } + self.updateContent(component: component) })) } @@ -962,15 +964,15 @@ final class StickerAttachmentScreenComponent: Component { theme: environment.theme, strings: environment.strings, isContentInFocus: true, - containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0 + topInset, left: 0.0, bottom: 0.0, right: 0.0), - topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), + containerInsets: UIEdgeInsets(top: topPanelHeight + topInset - 11.0, left: 0.0, bottom: 0.0, right: 0.0), + topPanelInsets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0), emojiContent: self.emojiContent, stickerContent: self.stickerContent, maskContent: nil, gifContent: nil, hasRecentGifs: false, availableGifSearchEmojies: [], - defaultToEmojiTab: emojiContent != nil, + defaultToEmojiTab: self.emojiContent != nil, externalTopPanelContainer: self.panelHostView, externalBottomPanelContainer: nil, externalTintMaskContainer: nil, @@ -993,6 +995,16 @@ final class StickerAttachmentScreenComponent: Component { self.forceUpdate = true self.searchVisible = searchVisible self.state?.updated(transition: transition) + + let transition: ComponentTransition = .easeInOut(duration: 0.2) + if let controller = self.environment?.controller() as? StickerAttachmentScreen { + if let titleView = controller.navigationItem.titleView { + transition.setAlpha(view: titleView, alpha: searchVisible ? 0.0 : 1.0) + } + if searchVisible { + controller.requestAttachmentMenuExpansion() + } + } }, hideTopPanelUpdated: { _, _ in }, @@ -1053,28 +1065,57 @@ final class StickerAttachmentScreenComponent: Component { self.keyboardClippingView.addSubview(keyboardComponentView) } -// if panelBackgroundColor.alpha < 0.01 { -// self.keyboardClippingView.clipsToBounds = true -// } else { - self.keyboardClippingView.clipsToBounds = false -// } + self.keyboardClippingView.clipsToBounds = false transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight - topInset))) self.keyboardClippingView.hitEdgeInsets = UIEdgeInsets(top: -topPanelHeight - topInset, left: 0.0, bottom: 0.0, right: 0.0) + let panelBackgroundFrame = CGRect(origin: CGPoint(x: 12.0, y: topPanelHeight + topInset - 29.0), size: CGSize(width: availableSize.width - 24.0, height: 44.0)) + + self.panelClippingView.clipsToBounds = true + self.panelClippingView.layer.cornerRadius = panelBackgroundFrame.height * 0.5 + transition.setFrame(view: self.panelClippingView, frame: CGRect(origin: .zero, size: panelBackgroundFrame.size)) + + self.panelBackgroundView.update(size: panelBackgroundFrame.size, cornerRadius: panelBackgroundFrame.size.height * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, isVisible: !self.searchVisible, transition: transition) + transition.setFrame(view: self.panelBackgroundView, frame: panelBackgroundFrame) + transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight - topInset), size: keyboardSize)) - transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0))) - - let topPanelAlpha: CGFloat - if self.searchVisible || self.keyboardContentId == AnyHashable("gifs") { - topPanelAlpha = 0.0 - } else { - topPanelAlpha = max(0.0, min(1.0, (self.topPanelScrollingOffset / 20.0))) - } + transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: -12.0, y: 8.0 - UIScreenPixel), size: CGSize(width: keyboardSize.width, height: 0.0))) + } - transition.setAlpha(view: self.panelSeparatorView, alpha: topPanelAlpha) - - transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - UIScreenPixel), size: CGSize(width: keyboardSize.width, height: UIScreenPixel))) + let barButtonSize = CGSize(width: 44.0, height: 44.0) + let cancelButtonSize = self.cancelButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: nil, + isDark: environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) + )), + action: { [weak self] _ in + guard let self else { + return + } + (self.environment?.controller() as? StickerAttachmentScreen)?.dismiss(animated: true) + } + )), + environment: {}, + containerSize: barButtonSize + ) + let cancelButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelButtonSize) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setBounds(view: cancelButtonView, bounds: CGRect(origin: .zero, size: cancelButtonFrame.size)) + transition.setPosition(view: cancelButtonView, position: cancelButtonFrame.center) + transition.setAlpha(view: cancelButtonView, alpha: self.searchVisible ? 0.0 : 1.0) + transition.setScale(view: cancelButtonView, scale: self.searchVisible ? 0.001 : 1.0) } return availableSize @@ -1096,11 +1137,21 @@ final class StickerAttachmentScreen: ViewControllerComponentContainer, Attachmen case emoji(EmojiPagerContentComponent) } + enum Source: Equatable { + enum PollMode: Equatable { + case description + case quizAnswer + case option + } + + case poll(PollMode) + } + private let context: AccountContext private let mode: Mode private let completion: (AnyMediaReference) -> Void - init(context: AccountContext, mode: Mode, completion: @escaping (AnyMediaReference) -> Void) { + init(context: AccountContext, mode: Mode, source: Source, completion: @escaping (AnyMediaReference) -> Void) { self.context = context self.mode = mode self.completion = completion @@ -1109,9 +1160,43 @@ final class StickerAttachmentScreen: ViewControllerComponentContainer, Attachmen context: context, mode: mode, completion: completion - ), navigationBarAppearance: .default, theme: .default) + ), navigationBarAppearance: .transparent, theme: .default) self._hasGlassStyle = true + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + switch source { + case let .poll(pollMode): + //TODO:localize + let title: String + let subtitle: String + switch mode { + case .stickers: + title = "Sticker" + switch pollMode { + case .description: + subtitle = "Add sticker to the poll description" + case .quizAnswer: + subtitle = "Add sticker to the quiz explanation" + case .option: + subtitle = "Add sticker to this option" + } + case .emoji: + title = "Emoji" + switch pollMode { + case .description: + subtitle = "Add emoji to the poll description" + case .quizAnswer: + subtitle = "Add emoji to the quiz explanation" + case .option: + subtitle = "Add emoji to this option" + } + } + let titleView = CounterControllerTitleView(theme: presentationData.theme, verticalOffset: -2.0) + titleView.title = CounterControllerTitle(title: title, counter: subtitle) + self.navigationItem.titleView = titleView + } + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) } required init(coder: NSCoder) { diff --git a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift index 0bddee3634..2760ca964b 100644 --- a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift +++ b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -115,8 +115,8 @@ private class MediaHeaderItemNode: ASDisplayNode { let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0 - let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 4.0 + minimizedTitleOffset), size: titleLayout.size) - let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 20.0), size: subtitleLayout.size) + let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 5.0 + minimizedTitleOffset), size: titleLayout.size) + let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 21.0), size: subtitleLayout.size) transition.updateFrame(node: self.titleNode, frame: minimizedTitleFrame) transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame) diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 0635c8b4c7..5bb7677ff5 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -153,6 +153,7 @@ public final class TextFieldComponent: Component { public let hideKeyboard: Bool public let customInputView: UIView? public let placeholder: NSAttributedString? + public let placeholderVerticalOffset: CGFloat public let prefix: NSAttributedString? public let suffix: NSAttributedString? public let resetText: NSAttributedString? @@ -185,6 +186,7 @@ public final class TextFieldComponent: Component { hideKeyboard: Bool, customInputView: UIView?, placeholder: NSAttributedString? = nil, + placeholderVerticalOffset: CGFloat = 0.0, prefix: NSAttributedString? = nil, suffix: NSAttributedString? = nil, resetText: NSAttributedString?, @@ -216,6 +218,7 @@ public final class TextFieldComponent: Component { self.hideKeyboard = hideKeyboard self.customInputView = customInputView self.placeholder = placeholder + self.placeholderVerticalOffset = placeholderVerticalOffset self.prefix = prefix self.suffix = suffix self.resetText = resetText @@ -271,6 +274,9 @@ public final class TextFieldComponent: Component { if lhs.placeholder != rhs.placeholder { return false } + if lhs.placeholderVerticalOffset != rhs.placeholderVerticalOffset { + return false + } if lhs.prefix != rhs.prefix { return false } @@ -365,6 +371,7 @@ public final class TextFieldComponent: Component { self.textView.layer.isOpaque = false self.textView.indicatorStyle = .white self.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: 0.0) + self.textView.showsHorizontalScrollIndicator = false self.inputMenu = TextInputMenu(hasSpoilers: true, hasQuotes: true) @@ -1587,27 +1594,28 @@ public final class TextFieldComponent: Component { } let placeholderSize = placeholder.update( transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(placeholderValue) - )), + component: AnyComponent( + Text(attributedString: placeholderValue) + ), environment: {}, containerSize: textFrame.size ) - let placeholderFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((textFrame.height - placeholderSize.height) * 0.5) - 1.0), size: placeholderSize) + let placeholderFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((textFrame.height - placeholderSize.height) * 0.5) - 1.0 + component.placeholderVerticalOffset), size: placeholderSize) if let placeholderView = placeholder.view { if placeholderView.superview == nil { placeholderView.isUserInteractionEnabled = false - placeholderView.layer.anchorPoint = CGPoint() self.textView.insertSubview(placeholderView, at: 0) } - placeholderTransition.setPosition(view: placeholderView, position: placeholderFrame.origin) + placeholderTransition.setPosition(view: placeholderView, position: placeholderFrame.center) placeholderView.bounds = CGRect(origin: CGPoint(), size: placeholderFrame.size) placeholderView.isHidden = self.textView.textStorage.length != 0 } - } else if let placeholder = self.placeholder { - self.placeholder = nil - placeholder.view?.removeFromSuperview() + } else { + if let placeholder = self.placeholder { + self.placeholder = nil + placeholder.view?.removeFromSuperview() + } } self.updateEmojiSuggestion(transition: .immediate) diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/PollReplyIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/PollReplyIcon.imageset/Contents.json new file mode 100644 index 0000000000..b697925aba --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/PollReplyIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "replypoll.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/PollReplyIcon.imageset/replypoll.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/PollReplyIcon.imageset/replypoll.pdf new file mode 100644 index 0000000000..c73b3c7cfd Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/PollReplyIcon.imageset/replypoll.pdf differ diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index fc7321d581..7c84d1f1fb 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -3565,6 +3565,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.controller?.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams()), scrollPosition: .top(-18.0)) } + var focusedTextInputIsMedia = false + if case .media = chatPresentationInterfaceState.inputMode { + focusedTextInputIsMedia = true + } + self.controllerInteraction.focusedTextInputIsMedia = focusedTextInputIsMedia + self.controllerInteraction.focusedPollAddOptionMessageId = chatPresentationInterfaceState.focusedPollAddOptionMessageId + let updateInputTextState = self.chatPresentationInterfaceState.interfaceState.effectiveInputState != chatPresentationInterfaceState.interfaceState.effectiveInputState self.chatPresentationInterfaceState = chatPresentationInterfaceState diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index b3bc0ba70a..4d93ae830c 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -413,7 +413,7 @@ extension ChatControllerImpl { let controller = strongSelf.context.sharedContext.makeAttachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, audio: true, bannedSendMedia: bannedSendFiles, presentGallery: { }, presentFiles: { [weak self, weak attachmentController] in attachmentController?.dismiss(animated: true) - self?.presentICloudFileGallery() + self?.presentICloudFileGallery(documentTypes: ["public.mp3", "public.mpeg-4-audio", "public.aac-audio", "org.xiph.flac"]) }, presentDocumentScanner: nil, send: { [weak self] mediaReferences in guard let self else { return @@ -1154,7 +1154,7 @@ extension ChatControllerImpl { }) } - func presentICloudFileGallery(editingMessage: Bool = false) { + func presentICloudFileGallery(editingMessage: Bool = false, documentTypes: [String] = ["public.item"]) { let _ = (self.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId), TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), @@ -1167,7 +1167,7 @@ extension ChatControllerImpl { let (accountPeer, limits, premiumLimits) = result let isPremium = accountPeer?.isPremium ?? false - strongSelf.present(legacyICloudFilePicker(theme: strongSelf.presentationData.theme, completion: { [weak self] urls in + strongSelf.present(legacyICloudFilePicker(theme: strongSelf.presentationData.theme, documentTypes: documentTypes, completion: { [weak self] urls in if let strongSelf = self, !urls.isEmpty { var signals: [Signal] = [] for url in urls { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 05f452bd1b..8e6fb485bf 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2751,7 +2751,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference]) -> Void) -> AttachmentFileController { - return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: audio ? .audio : .recent, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, presentDocumentScanner: presentDocumentScanner, send: send) + return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: audio ? .audio(story: false) : .recent, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, presentDocumentScanner: presentDocumentScanner, send: send) } public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? {