Various improvements

This commit is contained in:
Ilya Laktyushin 2026-03-22 13:44:04 +01:00
parent 9110dda4b4
commit 66d08c0e1b
35 changed files with 711 additions and 230 deletions

View file

@ -1384,7 +1384,6 @@ public class AttachmentController: ViewController, MinimizableController {
var containerInsets = containerLayout.intrinsicInsets var containerInsets = containerLayout.intrinsicInsets
var hasPanel = false var hasPanel = false
// let previousHasButton = self.hasButton
let hasButton = self.panel.isButtonVisible && !self.isDismissing let hasButton = self.panel.isButtonVisible && !self.isDismissing
self.hasButton = hasButton self.hasButton = hasButton
if let controller = self.controller, controller.buttons.count > 1 || controller.hasTextInput || self.panel.hasMediaAccessoryPanel { 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 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) 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 { if hasPanel || hasButton {
containerInsets.bottom = panelHeight + panelOffset containerInsets.bottom = panelHeight + panelOffset
} }
@ -1526,7 +1526,7 @@ public class AttachmentController: ViewController, MinimizableController {
public var forceSourceRect = false public var forceSourceRect = false
fileprivate var isStandalone: Bool { fileprivate var isStandalone: Bool {
return self.buttons.contains(.standalone) return self.buttons.contains(.standalone) || self.buttons.count == 1
} }
public func convertToStandalone() { public func convertToStandalone() {

View file

@ -1018,6 +1018,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
private var elevateProgress: Bool = false private var elevateProgress: Bool = false
private var buttons: [AttachmentButtonType] = [] private var buttons: [AttachmentButtonType] = []
private var selectedIndex: Int = 0 private var selectedIndex: Int = 0
private var selectionOverrideIndex: Int?
private(set) var isSelecting: Bool = false private(set) var isSelecting: Bool = false
private var selectionCount: Int = 0 private var selectionCount: Int = 0
@ -1025,12 +1026,30 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
var isButtonVisible: Bool { var isButtonVisible: Bool {
return self.mainButtonState.isVisible || self.secondaryButtonState.isVisible return self.mainButtonState.isVisible || self.secondaryButtonState.isVisible
} }
var hasMediaAccessoryPanel: Bool { private var peerMessagesPlaylistLocation: PeerMessagesPlaylistLocation? {
if case .glass = self.panelStyle { return self.playlistLocation as? PeerMessagesPlaylistLocation
return self.playlistStateAndType != nil }
} else { 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 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? private var validLayout: ContainerViewLayout?
@ -1470,11 +1489,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
return return
} }
let hadMediaAccessoryPanel = strongSelf.hasMediaAccessoryPanel let hadMediaAccessoryPanel = strongSelf.hasMediaAccessoryPanel
let updatedPeerMessagesPlaylistLocation = playlistStateAndType?.1.playlistLocation as? PeerMessagesPlaylistLocation
if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) || if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) ||
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) || !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) ||
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) || !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) ||
strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order || strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order ||
strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 { strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 ||
strongSelf.peerMessagesPlaylistLocation != updatedPeerMessagesPlaylistLocation {
if let playlistStateAndType { if let playlistStateAndType {
strongSelf.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 { private func makeMediaAccessoryPanel() -> MediaNavigationAccessoryPanel {
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, presentationData: self.presentationData, displayBackground: false, customTintColor: self.presentationData.theme.rootController.tabBar.textColor) let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, presentationData: self.presentationData, displayBackground: false, customTintColor: self.presentationData.theme.rootController.tabBar.textColor)
mediaAccessoryPanel.getController = { [weak self] in mediaAccessoryPanel.getController = { [weak self] in
@ -1784,14 +1772,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
self.context.sharedContext.mediaManager.playlistControl(.previous, type: type) self.context.sharedContext.mediaManager.playlistControl(.previous, type: type)
} }
} }
mediaAccessoryPanel.tapAction = { [weak self] in mediaAccessoryPanel.tapAction = {
self?.openCurrentMusicPlayer()
} }
return mediaAccessoryPanel return mediaAccessoryPanel
} }
private func updateMediaAccessoryPanel(frame: CGRect?, transition: ContainedViewLayoutTransition) { private func updateMediaAccessoryPanel(frame: CGRect?, transition: ContainedViewLayoutTransition) {
guard case .glass = self.panelStyle, guard case .glass = self.panelStyle,
self.hasMediaAccessoryPanel,
let frame, let frame,
let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType else { let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType else {
self.dismissMediaAccessoryPanel(transition: transition) self.dismissMediaAccessoryPanel(transition: transition)
@ -1869,8 +1857,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
} }
func updateSelectedIndex(_ index: Int) { func updateSelectedIndex(_ index: Int) {
let hadMediaAccessoryPanel = self.hasMediaAccessoryPanel
self.selectedIndex = index self.selectedIndex = index
self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
if hadMediaAccessoryPanel != self.hasMediaAccessoryPanel {
self.requestLayout(transition: .immediate)
}
} }
var buttonSize: CGSize { var buttonSize: CGSize {
@ -1949,6 +1941,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
} }
let button = self.buttons[index] let button = self.buttons[index]
self.skipLensUpdate = true self.skipLensUpdate = true
self.selectionOverrideIndex = index
if self.selectionChanged(button) { if self.selectionChanged(button) {
self.selectedIndex = index self.selectedIndex = index
if self.buttons.count > 5, let button = self.itemViews[button.key] { 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.updateItemContainers(contentOffset: newBounds.minX, transition: transition)
} }
} }
self.selectionOverrideIndex = nil
self.skipLensUpdate = false self.skipLensUpdate = false
} }
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
@ -1986,8 +1980,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
return return
} }
var buttons = self.buttons
if buttons.count == 1 {
buttons.removeAll()
}
var width = layout.size.width var width = layout.size.width
if self.buttons.count == 3 { if buttons.count == 3 {
width = smallPanelWidth width = smallPanelWidth
} }
@ -1999,11 +1998,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
panelSideInset = 3.0 panelSideInset = 3.0
} }
var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, self.buttons.count - 1))) var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, buttons.count - 1)))
if self.buttons.count == 3 { if buttons.count == 3 {
distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - 32.0) / CGFloat(max(1, self.buttons.count - 1))) 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 buttonWidth = self.buttonSize.width
var leftNodeOriginX: CGFloat var leftNodeOriginX: CGFloat
var maxButtonsToFit = 5 var maxButtonsToFit = 5
@ -2016,11 +2015,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
case .legacy: case .legacy:
leftNodeOriginX = (width - internalWidth) / 2.0 leftNodeOriginX = (width - internalWidth) / 2.0
} }
if self.buttons.count == 3 { if buttons.count == 3 {
leftNodeOriginX = floor((layout.size.width - width) / 2.0) + 16.0 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 { switch self.panelStyle {
case .glass: case .glass:
buttonWidth = smallGlassButtonSize.width buttonWidth = smallGlassButtonSize.width
@ -2036,12 +2035,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
var selectionFrame = CGRect() var selectionFrame = CGRect()
var mostRightX = 0.0 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 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)) let buttonFrame = CGRect(origin: CGPoint(x: originX, y: -3.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height))
mostRightX = buttonFrame.maxX mostRightX = buttonFrame.maxX
let type = self.buttons[i] let type = buttons[i]
let _ = validIds.insert(type.key) let _ = validIds.insert(type.key)
var buttonTransition = transition var buttonTransition = transition
@ -2096,20 +2095,22 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
context: self.context, context: self.context,
style: self.panelStyle == .glass ? .glass : .legacy, style: self.panelStyle == .glass ? .glass : .legacy,
type: type, type: type,
isFirstOrLast: i == 0 || i == self.buttons.count - 1, isFirstOrLast: i == 0 || i == buttons.count - 1,
isSelected: false, isSelected: false,
strings: self.presentationData.strings, strings: self.presentationData.strings,
theme: self.presentationData.theme, theme: self.presentationData.theme,
action: { [weak self] in action: { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.selectionOverrideIndex = i
if strongSelf.selectionChanged(type) { if strongSelf.selectionChanged(type) {
strongSelf.selectedIndex = i strongSelf.selectedIndex = i
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) 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.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true)
} }
} }
strongSelf.selectionOverrideIndex = nil
} }
}, longPressAction: { [weak self] in }, longPressAction: { [weak self] in
if let strongSelf = self, i == strongSelf.selectedIndex { if let strongSelf = self, i == strongSelf.selectedIndex {
@ -2128,7 +2129,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
context: self.context, context: self.context,
style: self.panelStyle == .glass ? .glass : .legacy, style: self.panelStyle == .glass ? .glass : .legacy,
type: type, type: type,
isFirstOrLast: i == 0 || i == self.buttons.count - 1, isFirstOrLast: i == 0 || i == buttons.count - 1,
isSelected: true, isSelected: true,
strings: self.presentationData.strings, strings: self.presentationData.strings,
theme: self.presentationData.theme, theme: self.presentationData.theme,
@ -2433,7 +2434,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
} }
let topAccessoryHeight: CGFloat let topAccessoryHeight: CGFloat
if case .glass = self.panelStyle, self.playlistStateAndType != nil { if self.hasMediaAccessoryPanel {
topAccessoryHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight topAccessoryHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
} else { } else {
topAccessoryHeight = 0.0 topAccessoryHeight = 0.0
@ -2474,7 +2475,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
self.updateViews(transition: .immediate) self.updateViews(transition: .immediate)
let glassPanelHeight: CGFloat = 62.0 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? var mediaAccessoryPanelFrame: CGRect?
if case .glass = self.panelStyle { if case .glass = self.panelStyle {
let backgroundView: GlassBackgroundView let backgroundView: GlassBackgroundView
@ -2517,9 +2521,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
} }
let basePanelHeight = isSelecting ? max(0.0, textPanelHeight - 11.0) : glassPanelHeight 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) 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)) 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))) containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel)))
if case .glass = self.panelStyle { 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 { if let progress = self.loadingProgress {

View file

@ -2380,6 +2380,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
var currentStoryIcon: UIImage? var currentStoryIcon: UIImage?
var currentGiftIcon: UIImage? var currentGiftIcon: UIImage?
var currentLocationIcon: UIImage? var currentLocationIcon: UIImage?
var currentPollIcon: UIImage?
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)? var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
@ -2555,6 +2556,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
var displayStoryReplyIcon = false var displayStoryReplyIcon = false
var displayGiftIcon = false var displayGiftIcon = false
var displayLocationIcon = false var displayLocationIcon = false
var displayPollIcon = false
var ignoreForwardedIcon = false var ignoreForwardedIcon = false
switch contentData { switch contentData {
@ -2867,7 +2869,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
displayStoryReplyIcon = true displayStoryReplyIcon = true
} else { } else {
for media in message.media { 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 displayLocationIcon = true
} else if let action = media as? TelegramMediaAction { } else if let action = media as? TelegramMediaAction {
switch action.action { switch action.action {
@ -3048,6 +3052,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
currentLocationIcon = PresentationResourcesChatList.locationIcon(item.presentationData.theme) currentLocationIcon = PresentationResourcesChatList.locationIcon(item.presentationData.theme)
} }
if displayPollIcon {
currentPollIcon = PresentationResourcesChatList.pollIcon(item.presentationData.theme)
}
if let currentForwardedIcon { if let currentForwardedIcon {
textLeftCutout += currentForwardedIcon.size.width textLeftCutout += currentForwardedIcon.size.width
if !contentImageSpecs.isEmpty { 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 { for i in 0 ..< contentImageSpecs.count {
if i != 0 { if i != 0 {
textLeftCutout += contentImageSpacing textLeftCutout += contentImageSpacing
@ -4745,6 +4762,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
} else if let currentLocationIcon { } else if let currentLocationIcon {
messageTypeIcon = currentLocationIcon messageTypeIcon = currentLocationIcon
messageTypeIconOffset.y -= 2.0 - UIScreenPixel messageTypeIconOffset.y -= 2.0 - UIScreenPixel
} else if let currentPollIcon {
messageTypeIcon = currentPollIcon
messageTypeIconOffset.y -= 2.0 - UIScreenPixel
} }
if let messageTypeIcon { if let messageTypeIcon {

View file

@ -395,15 +395,13 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
messageText = text messageText = text
} }
case let poll as TelegramMediaPoll: case let poll as TelegramMediaPoll:
let pollPrefix = "📊 " messageText = poll.text
let entityOffset = (pollPrefix as NSString).length
messageText = "\(pollPrefix)\(poll.text)"
for entity in poll.textEntities { for entity in poll.textEntities {
if case let .CustomEmoji(_, fileId) = entity.type { if case let .CustomEmoji(_, fileId) = entity.type {
if customEmojiRanges == nil { if customEmojiRanges == nil {
customEmojiRanges = [] 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) let attribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile)
customEmojiRanges?.append((range, attribute)) customEmojiRanges?.append((range, attribute))
} }

View file

@ -83,6 +83,18 @@ public final class Text: Component {
self.color = color self.color = color
self.tintColor = tintColor 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 { public static func ==(lhs: Text, rhs: Text) -> Bool {
if lhs.text != rhs.text { if lhs.text != rhs.text {

View file

@ -37,6 +37,7 @@ public final class CounterControllerTitleView: UIView {
private var primaryTextColor: UIColor? private var primaryTextColor: UIColor?
private var secondaryTextColor: UIColor? private var secondaryTextColor: UIColor?
private let verticalOffset: CGFloat
private var nextLayoutTransition: ContainedViewLayoutTransition? private var nextLayoutTransition: ContainedViewLayoutTransition?
@ -65,7 +66,7 @@ public final class CounterControllerTitleView: UIView {
let secondaryTextColor = self.secondaryTextColor ?? self.theme.rootController.navigationBar.secondaryTextColor let secondaryTextColor = self.secondaryTextColor ?? self.theme.rootController.navigationBar.secondaryTextColor
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: primaryTextColor) 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 previousSubtitleText = self.subtitleNode.attributedText, previousSubtitleText.string.isEmpty != subtitleText.string.isEmpty && subtitleText.string.isEmpty {
if let disappearingSubtitleNode = self.disappearingSubtitleNode { if let disappearingSubtitleNode = self.disappearingSubtitleNode {
self.disappearingSubtitleNode = nil self.disappearingSubtitleNode = nil
@ -94,8 +95,9 @@ public final class CounterControllerTitleView: UIView {
self.setNeedsLayout() self.setNeedsLayout()
} }
public init(theme: PresentationTheme) { public init(theme: PresentationTheme, verticalOffset: CGFloat = 0.0) {
self.theme = theme self.theme = theme
self.verticalOffset = verticalOffset
self.titleNode = ImmediateTextNode() self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false self.titleNode.displaysAsynchronously = false
@ -126,7 +128,7 @@ public final class CounterControllerTitleView: UIView {
super.layoutSubviews() super.layoutSubviews()
let size = self.bounds.size 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 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)) 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 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) self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
transition.updatePosition(node: self.titleNode, position: titleFrame.center) 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) self.subtitleNode.bounds = CGRect(origin: CGPoint(), size: subtitleFrame.size)
transition.updatePosition(node: self.subtitleNode, position: subtitleFrame.center) transition.updatePosition(node: self.subtitleNode, position: subtitleFrame.center)
transition.updateTransformScale(node: self.subtitleNode, scale: self.title.counter != nil ? 1.0 : 0.001) transition.updateTransformScale(node: self.subtitleNode, scale: self.title.counter != nil ? 1.0 : 0.001)

View file

@ -2149,7 +2149,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if isActive { if isActive {
self.setLivePhotoVideoVisible(true, animated: animated) self.setLivePhotoVideoVisible(true, animated: animated)
videoNode.seek(0.0) videoNode.seek(0.0)
videoNode.play() videoNode.playOnceWithSound(playAndRecord: false)
} else { } else {
videoNode.pause() videoNode.pause()
videoNode.seek(0.0) videoNode.seek(0.0)

View file

@ -699,14 +699,14 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl
self.titleNode.maximumNumberOfLines = 1 self.titleNode.maximumNumberOfLines = 1
self.titleNode.isOpaque = false 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 = ImmediateTextNode()
self.subtitleNode.displaysAsynchronously = false self.subtitleNode.displaysAsynchronously = false
self.subtitleNode.maximumNumberOfLines = 1 self.subtitleNode.maximumNumberOfLines = 1
self.subtitleNode.isOpaque = false 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()) super.init(frame: CGRect())
@ -719,8 +719,8 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl
} }
func updateTheme(theme: PresentationTheme) { func updateTheme(theme: PresentationTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor) 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(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(12.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
if let size = self.validLayout { if let size = self.validLayout {
let _ = self.updateLayout(availableSize: size, transition: .immediate) let _ = self.updateLayout(availableSize: size, transition: .immediate)
} }
@ -741,7 +741,7 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl
let titleSize = self.titleNode.updateLayout(size) let titleSize = self.titleNode.updateLayout(size)
let subtitleSize = self.subtitleNode.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 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 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) let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + spacing), size: subtitleSize)

View file

@ -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)? var dismissImpl: (() -> Void)?
let legacyController = LegacyICloudFileController(presentation: .modal(animateIn: true), theme: theme, completion: { urls in let legacyController = LegacyICloudFileController(presentation: .modal(animateIn: true), theme: theme, completion: { urls in
dismissImpl?() dismissImpl?()

View file

@ -933,7 +933,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
if statusUpdated && item.displayFileInfo { if statusUpdated && item.displayFileInfo {
if let file = selectedMedia as? TelegramMediaFile { 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<FileMediaResourceStatus, NoError> in |> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
if case .Fetching = value.fetchStatus, !item.isDownloadList { if case .Fetching = value.fetchStatus, !item.isDownloadList {
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue()) return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())

View file

@ -53,6 +53,7 @@ swift_library(
"//submodules/TelegramUI/Components/SearchInputPanelComponent", "//submodules/TelegramUI/Components/SearchInputPanelComponent",
"//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/CounterControllerTitleView",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -11,6 +11,7 @@ import CoreLocation
import PresentationDataUtils import PresentationDataUtils
import DeviceAccess import DeviceAccess
import AttachmentUI import AttachmentUI
import CounterControllerTitleView
public enum LocationPickerMode { public enum LocationPickerMode {
case share(peer: EnginePeer?, selfPeer: EnginePeer?, hasLiveLocation: Bool) case share(peer: EnginePeer?, selfPeer: EnginePeer?, hasLiveLocation: Bool)
@ -52,9 +53,16 @@ class LocationPickerInteraction {
} }
public final class LocationPickerController: ViewController, AttachmentContainable { 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 generic
case story case story
case poll(PollMode)
} }
private var controllerNode: LocationPickerControllerNode { private var controllerNode: LocationPickerControllerNode {
@ -63,7 +71,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab
private let context: AccountContext private let context: AccountContext
private let mode: LocationPickerMode private let mode: LocationPickerMode
private let source: Source let source: Source
let initialLocation: CLLocationCoordinate2D? let initialLocation: CLLocationCoordinate2D?
private let completion: (TelegramMediaMap, Int64?, String?, String?, String?) -> Void private let completion: (TelegramMediaMap, Int64?, String?, String?, String?) -> Void
private var presentationData: PresentationData private var presentationData: PresentationData
@ -115,7 +123,6 @@ public final class LocationPickerController: ViewController, AttachmentContainab
if case .glass = style { if case .glass = style {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
} else { } 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.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
} }
self.updateBarButtons() self.updateBarButtons()

View file

@ -354,6 +354,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
private let cancelButton = ComponentView<Empty>() private let cancelButton = ComponentView<Empty>()
private let searchButton = ComponentView<Empty>() private let searchButton = ComponentView<Empty>()
private let title = ComponentView<Empty>() private let title = ComponentView<Empty>()
private let subtitle = ComponentView<Empty>()
fileprivate let topEdgeEffectView: EdgeEffectView fileprivate let topEdgeEffectView: EdgeEffectView
fileprivate let bottomEdgeEffectView: EdgeEffectView fileprivate let bottomEdgeEffectView: EdgeEffectView
@ -670,15 +671,18 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
var coordinate = userLocation?.coordinate var coordinate = userLocation?.coordinate
switch strongSelf.mode { switch strongSelf.mode {
case .share: case .share:
if source == .story { switch source {
case .generic:
title = presentationData.strings.Map_SendMyCurrentLocation
case .story:
if let initialLocation = strongSelf.controller?.initialLocation { if let initialLocation = strongSelf.controller?.initialLocation {
title = presentationData.strings.Location_AddThisLocation title = presentationData.strings.Location_AddThisLocation
coordinate = initialLocation coordinate = initialLocation
} else { } else {
title = presentationData.strings.Location_AddMyLocation title = presentationData.strings.Location_AddMyLocation
} }
} else { case .poll:
title = presentationData.strings.Map_SendMyCurrentLocation title = presentationData.strings.Location_AddThisLocation
} }
case .pick: case .pick:
title = presentationData.strings.Map_SetThisLocation title = presentationData.strings.Map_SetThisLocation
@ -1358,6 +1362,27 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.view.addSubview(self.topEdgeEffectView) 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( let titleSize = self.title.update(
transition: ComponentTransition(transition), transition: ComponentTransition(transition),
component: AnyComponent( component: AnyComponent(
@ -1374,7 +1399,37 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
environment: {}, environment: {},
containerSize: CGSize(width: 200.0, height: 40.0) 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 let titleView = self.title.view {
if titleView.superview == nil { if titleView.superview == nil {
self.view.addSubview(titleView) self.view.addSubview(titleView)

View file

@ -412,8 +412,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if case .glass = controller.style { if case .glass = controller.style {
self.containerNode.view.addSubview(self.topEdgeEffectView) self.containerNode.view.addSubview(self.topEdgeEffectView)
if case let .assets(_, mode) = controller.subject, [.default, .story].contains(mode) { if case let .assets(_, mode) = controller.subject {
self.containerNode.view.addSubview(self.bottomEdgeEffectView) 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 self.titleView.title = presentationData.strings.MediaPicker_ChooseCover
case let .poll(pollMode): case let .poll(pollMode):
self.titleView.title = presentationData.strings.MediaPicker_Recents self.titleView.title = presentationData.strings.MediaPicker_Recents
//TODO:localize
switch pollMode { switch pollMode {
case .description: case .description:
self.titleView.subtitle = "Add media to the poll description" self.titleView.subtitle = "Add media to the poll description"
@ -2560,7 +2566,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
titleIsEnabled = false 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) self.cancelButtonNode.setState(isEnabled ? .cancel : .back, animated: true)
isBack = !isEnabled isBack = !isEnabled

View file

@ -130,6 +130,7 @@ public enum PresentationResourceKey: Int32 {
case chatListStoryReplyIcon case chatListStoryReplyIcon
case chatListGiftIcon case chatListGiftIcon
case chatListLocationIcon case chatListLocationIcon
case chatListPollIcon
case chatListGeneralTopicIcon case chatListGeneralTopicIcon
case chatListGeneralTopicTemplateIcon case chatListGeneralTopicTemplateIcon
@ -420,4 +421,5 @@ public enum PresentationResourceParameterKey: Hashable {
case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType) case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
case chatReplyStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType) case chatReplyStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
case chatReplyPollIndicatorIcon(type: ChatExpiredStoryIndicatorType)
} }

View file

@ -1277,9 +1277,9 @@ public struct PresentationResourcesChat {
let foregroundColor: UIColor let foregroundColor: UIColor
switch type { switch type {
case .incoming: case .incoming:
foregroundColor = theme.chat.message.incoming.mediaActiveControlColor foregroundColor = theme.chat.message.incoming.accentTextColor
case .outgoing: case .outgoing:
foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor foregroundColor = theme.chat.message.outgoing.accentTextColor
case .free: case .free:
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
} }
@ -1303,9 +1303,9 @@ public struct PresentationResourcesChat {
let foregroundColor: UIColor let foregroundColor: UIColor
switch type { switch type {
case .incoming: case .incoming:
foregroundColor = theme.chat.message.incoming.mediaActiveControlColor foregroundColor = theme.chat.message.incoming.accentTextColor
case .outgoing: case .outgoing:
foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor foregroundColor = theme.chat.message.outgoing.accentTextColor
case .free: case .free:
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText 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? { public static func storyViewListLikeIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.storyViewListLikeIcon.rawValue, { theme in return theme.image(PresentationResourceKey.storyViewListLikeIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Stories/InputLikeOn"), color: UIColor(rgb: 0xFF3B30)) return generateTintedImage(image: UIImage(bundleImageName: "Stories/InputLikeOn"), color: UIColor(rgb: 0xFF3B30))
@ -1417,8 +1443,8 @@ public struct PresentationResourcesChat {
context.setFillColor(UIColor.white.cgColor) context.setFillColor(UIColor.white.cgColor)
let lineHeight = 2.0 - UIScreenPixel 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: 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, y: 0.0, width: lineHeight, height: 15.0), 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() context.fillPath()
}) })
}) })

View file

@ -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? { public static func verifiedIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in
if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") { if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") {

View file

@ -466,7 +466,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
case .expiredVideoMessage: case .expiredVideoMessage:
return (NSAttributedString(string: strings.Message_VideoMessageExpired), true) return (NSAttributedString(string: strings.Message_VideoMessageExpired), true)
case let .poll(text): case let .poll(text):
return (NSAttributedString(string: "📊 \(text)"), false) return (NSAttributedString(string: text), false)
case let .todo(text): case let .todo(text):
return (NSAttributedString(string: "☑️ \(text)"), false) return (NSAttributedString(string: "☑️ \(text)"), false)
case let .restricted(text): case let .restricted(text):

View file

@ -26,6 +26,7 @@ import ContextUI
private final class AttachmentFileControllerArguments { private final class AttachmentFileControllerArguments {
let context: AccountContext let context: AccountContext
let isAudio: Bool let isAudio: Bool
let isAttach: Bool
let openGallery: () -> Void let openGallery: () -> Void
let openFiles: () -> Void let openFiles: () -> Void
let scanDocument: () -> Void let scanDocument: () -> Void
@ -38,9 +39,10 @@ private final class AttachmentFileControllerArguments {
let setMessageSelection: ([MessageId], Message?, Bool) -> Void let setMessageSelection: ([MessageId], Message?, Bool) -> Void
let openMessageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> 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.context = context
self.isAudio = isAudio self.isAudio = isAudio
self.isAttach = isAttach
self.openGallery = openGallery self.openGallery = openGallery
self.openFiles = openFiles self.openFiles = openFiles
self.scanDocument = scanDocument self.scanDocument = scanDocument
@ -250,7 +252,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat 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)) 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): case let .savedShowMore(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
arguments.expandSavedMusic() arguments.expandSavedMusic()
@ -275,8 +277,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat 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)) 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: arguments.isAttach, 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: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
case let .recentShowMore(theme, text): case let .recentShowMore(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
arguments.expandRecentMusic() arguments.expandRecentMusic()
@ -301,8 +302,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat 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)) 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: arguments.isAttach, 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: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
case let .globalShowMore(theme, text): case let .globalShowMore(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: { 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 caption: NSAttributedString?
fileprivate var selectionCount = ValuePromise<Int>(0) fileprivate var selectionCount = ValuePromise<Int>(0)
fileprivate var bottomEdgeColor: UIColor = .clear
fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?) -> Void)? fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?) -> Void)?
var delayDisappear = false var delayDisappear = false
@ -441,6 +443,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
var hasBottomEdgeEffect = true var hasBottomEdgeEffect = true
var resetForReuseImpl: () -> Void = {} var resetForReuseImpl: () -> Void = {}
var onDismissImpl: () -> Void = {}
public func resetForReuse() { public func resetForReuse() {
self.resetForReuseImpl() self.resetForReuseImpl()
self.scrollToTop?() self.scrollToTop?()
@ -451,12 +454,23 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset) self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset)
self.delayDisappear = false 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? { public var mediaPickerContext: AttachmentMediaPickerContext? {
return AttachmentFileContext(controller: self) return AttachmentFileContext(controller: self)
} }
private var topEdgeEffectView: EdgeEffectView?
private var bottomEdgeEffectView: EdgeEffectView? private var bottomEdgeEffectView: EdgeEffectView?
var isSearching: Bool = false { var isSearching: Bool = false {
@ -468,21 +482,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition) 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 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 { if self.hasBottomEdgeEffect {
let bottomEdgeEffectView: EdgeEffectView 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)) 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.updateFrame(view: bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
transition.updateAlpha(layer: bottomEdgeEffectView.layer, alpha: self.isSearching ? 0.0 : 1.0) 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 { } else if let bottomEdgeEffectView = self.bottomEdgeEffectView {
bottomEdgeEffectView.removeFromSuperview() bottomEdgeEffectView.removeFromSuperview()
} }
@ -521,13 +521,32 @@ private func messageSelectionState(state: AttachmentFileControllerState, message
public enum AttachmentFileControllerMode { public enum AttachmentFileControllerMode {
case recent 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( public func makeAttachmentFileControllerImpl(
context: AccountContext, context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
mode: AttachmentFileControllerMode = .recent, mode: AttachmentFileControllerMode = .recent,
source: AttachmentFileControllerSource = .generic,
bannedSendMedia: (Int32, Bool)?, bannedSendMedia: (Int32, Bool)?,
presentGallery: @escaping () -> Void, presentGallery: @escaping () -> Void,
presentFiles: @escaping () -> Void, presentFiles: @escaping () -> Void,
@ -549,10 +568,20 @@ public func makeAttachmentFileControllerImpl(
var updateIsSearchingImpl: ((Bool) -> Void)? var updateIsSearchingImpl: ((Bool) -> Void)?
var updateSelectionCountImpl: ((Int) -> Void)? var updateSelectionCountImpl: ((Int) -> Void)?
var presentInGlobalOverlayImpl: ((ViewController) -> 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( let arguments = AttachmentFileControllerArguments(
context: context, context: context,
isAudio: mode == .audio, isAudio: isAudio,
isAttach: isAttach,
openGallery: { openGallery: {
presentGallery() presentGallery()
}, },
@ -603,6 +632,8 @@ public func makeAttachmentFileControllerImpl(
} }
}, },
toggleMediaPlayback: { message in toggleMediaPlayback: { message in
didPreviewAudio = true
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil) 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)) 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<BarComponentHostNode?>(value: nil) let existingCloseButton = Atomic<BarComponentHostNode?>(value: nil)
let existingSearchButton = Atomic<BarComponentHostNode?>(value: nil) let existingSearchButton = Atomic<BarComponentHostNode?>(value: nil)
let previousRecentDocuments = Atomic<[Message]?>(value: nil) let previousRecentDocuments = Atomic<[Message]?>(value: nil)
let signal = combineLatest(queue: Queue.mainQueue(), let signal = combineLatest(queue: Queue.mainQueue(),
presentationData, presentationData,
@ -823,21 +854,32 @@ public func makeAttachmentFileControllerImpl(
} }
var rightNavigationButton: ItemListNavigationButton? 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: {}) } rightNavigationButton = searchButtonNode.flatMap { ItemListNavigationButton(content: .node($0), style: .regular, enabled: true, action: {}) }
} }
let title: String let title: String
var subtitle: String?
switch mode { switch mode {
case .recent: case .recent:
title = presentationData.strings.Attachment_File title = presentationData.strings.Attachment_File
case .audio: case .audio:
title = presentationData.strings.MediaEditor_Audio_Title 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( let controllerState = ItemListControllerState(
presentationData: ItemListPresentationData(presentationData), presentationData: ItemListPresentationData(presentationData),
title: .text(title), title: subtitle.flatMap { .textWithSubtitle(title, $0) } ?? .text(title),
leftNavigationButton: leftNavigationButton, leftNavigationButton: leftNavigationButton,
rightNavigationButton: rightNavigationButton, rightNavigationButton: rightNavigationButton,
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), 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) 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)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {
actionsDisposable.dispose() actionsDisposable.dispose()
@ -888,7 +932,6 @@ public func makeAttachmentFileControllerImpl(
} }
let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true) let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true)
controller.mulitpleCompletion = { _, _, _ in controller.mulitpleCompletion = { _, _, _ in
let _ = stateValue.with({ state in let _ = stateValue.with({ state in
if let selectedMessageIds = state.selectedMessageIds { if let selectedMessageIds = state.selectedMessageIds {
@ -928,12 +971,25 @@ public func makeAttachmentFileControllerImpl(
updatedState.selectedMessageIds = nil updatedState.selectedMessageIds = nil
return updatedState 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 updateIsSearchingImpl = { [weak controller] isSearching in
controller?.isSearching = isSearching controller?.isSearching = isSearching
} }
dismissImpl = { [weak controller] in dismissImpl = { [weak controller] in
controller?.dismiss(animated: true) controller?.dismiss(animated: true)
if didPreviewAudio {
context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause))
}
} }
dismissInputImpl = { [weak controller] in dismissInputImpl = { [weak controller] in
controller?.view.endEditing(true) controller?.view.endEditing(true)
@ -950,6 +1006,9 @@ public func makeAttachmentFileControllerImpl(
presentInGlobalOverlayImpl = { [weak controller] c in presentInGlobalOverlayImpl = { [weak controller] c in
controller?.presentInGlobalOverlay(c) controller?.presentInGlobalOverlay(c)
} }
updateBottomColorImpl = { [weak controller] color in
controller?.bottomEdgeColor = color
}
return controller return controller
} }
@ -962,15 +1021,16 @@ public func storyAudioPickerController(
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme) let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData)) let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (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 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() selectFromFiles()
dismissImpl?() dismissImpl?()
}, presentDocumentScanner: nil, send: { files in }, presentDocumentScanner: nil, send: { files in
completion(files.first!) completion(files.first!)
dismissImpl?() dismissImpl?()
}) as! AttachmentFileControllerImpl }) as! AttachmentFileControllerImpl
filePickerController.hasBottomEdgeEffect = false
present(filePickerController, filePickerController.mediaPickerContext) present(filePickerController, filePickerController.mediaPickerContext)
return true return true
} }

View file

@ -74,12 +74,11 @@ final class AttachmentFileSearchItem: ItemListControllerSearch {
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? {
switch self.mode { switch self.mode {
case .audio: case .audio:
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
if let current = current as? AttachmentFileSearchNavigationContentNode { if let current = current as? AttachmentFileSearchNavigationContentNode {
current.updateTheme(presentationData.theme) current.updateTheme(self.presentationData.theme)
return current return current
} else { } 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: default:
@ -164,7 +163,7 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode {
strings: self.presentationData.strings, strings: self.presentationData.strings,
metrics: layout.metrics, metrics: layout.metrics,
safeInsets: layout.safeInsets, 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 updated: { [weak self] query in
guard let self else { guard let self else {
return return
@ -329,9 +328,9 @@ private enum AttachmentFileSearchEntry: Comparable, Identifiable {
interaction.toggleMediaPlayback(message) interaction.toggleMediaPlayback(message)
}, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] }) }, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
let displayFileInfo = mode == .audio let displayFileInfo = mode.isAudio
let isStoryMusic = mode == .audio let isStoryMusic = mode.isAudio
let isDownloadList = mode == .audio 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) 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): case let .showMore(text, section):
@ -661,6 +660,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
} }
} }
} else { } else {
entries.append(.header(title: " ", section: 0))
var index: Int32 = 0 var index: Int32 = 0
for _ in 0 ..< 16 { for _ in 0 ..< 16 {
entries.append(.file(index: index, message: nil, section: 0)) entries.append(.file(index: index, message: nil, section: 0))
@ -1009,6 +1009,20 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont
func updateTheme(_ theme: PresentationTheme) { func updateTheme(_ theme: PresentationTheme) {
self.theme = theme 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 { if let params = self.params {
let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) 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 { override var nominalHeight: CGFloat {
return 60.0 return 68.0
} }
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {

View file

@ -815,6 +815,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if strongSelf.selectionNode != nil { if strongSelf.selectionNode != nil {
return .none 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 let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) {
if case let .action(action) = action, !action.contextMenuOnLongPress { if case let .action(action) = action, !action.contextMenuOnLongPress {
return .none return .none

View file

@ -1368,6 +1368,7 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
var attachPressed: (() -> Void)? var attachPressed: (() -> Void)?
var mediaPressed: (() -> Void)? var mediaPressed: (() -> Void)?
var modeSelectorPressed: (() -> Void)? var modeSelectorPressed: (() -> Void)?
var requestSave: (() -> Void)?
static let characterLimit = 100 static let characterLimit = 100
private static let leftInset: CGFloat = 50.0 private static let leftInset: CGFloat = 50.0
@ -1449,17 +1450,25 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
hideKeyboard: self.currentFocusedTextInputIsMedia, hideKeyboard: self.currentFocusedTextInputIsMedia,
customInputView: nil, customInputView: nil,
placeholder: NSAttributedString(string: strings.CreatePoll_AddOption, font: font, textColor: currentPlaceholderColor), placeholder: NSAttributedString(string: strings.CreatePoll_AddOption, font: font, textColor: currentPlaceholderColor),
placeholderVerticalOffset: 1.0 + UIScreenPixel,
resetText: nil, resetText: nil,
isOneLineWhenUnfocused: false, isOneLineWhenUnfocused: false,
characterLimit: ChatMessagePollAddOptionNode.characterLimit, characterLimit: ChatMessagePollAddOptionNode.characterLimit,
enableInlineAnimations: true, enableInlineAnimations: true,
emptyLineHandling: .allowed, emptyLineHandling: .notAllowed,
formatMenuAvailability: .none, formatMenuAvailability: .none,
returnKeyType: .done,
lockedFormatAction: { lockedFormatAction: {
}, },
present: { _ in present: { _ in
}, },
paste: { _ in paste: { _ in
},
returnKeyAction: { [weak self] in
self?.requestSave?()
},
backspaceKeyAction: {
} }
) )
} }
@ -2150,17 +2159,14 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
guard let item = self.item else { guard let item = self.item else {
return return
} }
item.controllerInteraction.updatePresentationState { [weak item] state in item.controllerInteraction.updatePresentationState { state in
var focusedTextInputIsMedia = false
let updatedState = state.updatedInputMode({ inputMode in let updatedState = state.updatedInputMode({ inputMode in
if case .media = inputMode { if case .media = inputMode {
return .text return .text
} else { } else {
focusedTextInputIsMedia = true
return .media(mode: .other, expanded: .none, focused: true) return .media(mode: .other, expanded: .none, focused: true)
} }
}) })
item?.controllerInteraction.focusedTextInputIsMedia = focusedTextInputIsMedia
return updatedState return updatedState
} }
@ -2185,6 +2191,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
presentPollAttachmentScreen( presentPollAttachmentScreen(
context: item.context, context: item.context,
updatedPresentationData: item.controllerInteraction.updatedPresentationData, updatedPresentationData: item.controllerInteraction.updatedPresentationData,
subject: .option,
availableButtons: [.gallery, .sticker, .emoji, .location], availableButtons: [.gallery, .sticker, .emoji, .location],
present: { [weak item] controller in present: { [weak item] controller in
item?.controllerInteraction.navigationController()?.pushViewController(controller) item?.controllerInteraction.navigationController()?.pushViewController(controller)
@ -2291,6 +2298,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let optionData = "\(poll.options.count)".data(using: .utf8)! let optionData = "\(poll.options.count)".data(using: .utf8)!
item.controllerInteraction.requestAddMessagePollOption(item.message.id, trimmedNewOptionText, entities, optionData, self.currentNewOptionMedia?.media) item.controllerInteraction.requestAddMessagePollOption(item.message.id, trimmedNewOptionText, entities, optionData, self.currentNewOptionMedia?.media)
return return
} else if self.newOptionIsFocused {
return
} }
var hasSelection = false var hasSelection = false
@ -2932,6 +2941,9 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
addOptionNode.modeSelectorPressed = { [weak self] in addOptionNode.modeSelectorPressed = { [weak self] in
self?.toggleNewOptionInputMode() self?.toggleNewOptionInputMode()
} }
addOptionNode.requestSave = { [weak self] in
self?.buttonPressed()
}
addOptionNode.focusUpdated = { [weak self] focused in addOptionNode.focusUpdated = { [weak self] focused in
guard let self else { guard let self else {
return return

View file

@ -203,6 +203,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
let isText: Bool let isText: Bool
var isExpiredStory: Bool = false var isExpiredStory: Bool = false
var isStory: Bool = false var isStory: Bool = false
var isPoll: Bool = false
let titleColor: UIColor let titleColor: UIColor
let mainColor: UIColor let mainColor: UIColor
@ -429,6 +430,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
} }
let textColor: UIColor let textColor: UIColor
let iconColor: UIColor
switch arguments.type { switch arguments.type {
case let .bubble(incoming): case let .bubble(incoming):
@ -439,8 +441,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
} else { } else {
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor 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: case .standalone:
textColor = titleColor textColor = titleColor
iconColor = titleColor
} }
var textLeftInset: CGFloat = 0.0 var textLeftInset: CGFloat = 0.0
@ -516,6 +520,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
} }
} else { } else {
messageText = NSAttributedString(string: textString.string, font: textFont, textColor: textColor) 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 var leftInset: CGFloat = 11.0
@ -562,6 +570,26 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
hasRoundImage = true hasRoundImage = true
} }
break 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] { } 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)) 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 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.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 = titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing
size.height += 2.0 size.height += 2.0
if isExpiredStory || isStory { if isExpiredStory || isStory || isPoll {
size.width += 16.0 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) 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 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 { if textNode.textNode.bounds.isEmpty || !animation.isAnimated || textNode.textNode.bounds.height == effectiveTextFrame.height {
textNode.textNode.frame = effectiveTextFrame textNode.textNode.frame = effectiveTextFrame
@ -809,7 +837,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
} }
} }
if isExpiredStory || isStory { if isExpiredStory || isStory || isPoll {
let expiredStoryIconView: UIImageView let expiredStoryIconView: UIImageView
if let current = node.expiredStoryIconView { if let current = node.expiredStoryIconView {
expiredStoryIconView = current expiredStoryIconView = current
@ -827,7 +855,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
imageType = incoming ? .incoming : .outgoing 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) expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
} else { } else {
expiredStoryIconView.image = PresentationResourcesChat.chatReplyStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType) expiredStoryIconView.image = PresentationResourcesChat.chatReplyStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
@ -921,7 +951,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
if let todoItemCompleted { 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 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 let checkLayer: CheckLayer
if let current = node.checkLayer { if let current = node.checkLayer {

View file

@ -327,6 +327,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public var canReadHistory: Bool = false public var canReadHistory: Bool = false
public var summarizedMessageIds: Set<MessageId> = Set() public var summarizedMessageIds: Set<MessageId> = Set()
public var focusedTextInputIsMedia: Bool = false public var focusedTextInputIsMedia: Bool = false
public var focusedPollAddOptionMessageId: MessageId?
private var isOpeningMediaValue: Bool = false private var isOpeningMediaValue: Bool = false
public var isOpeningMedia: Bool { public var isOpeningMedia: Bool {

View file

@ -56,7 +56,8 @@ swift_library(
"//submodules/Components/PagerComponent", "//submodules/Components/PagerComponent",
"//submodules/FeaturedStickersScreen", "//submodules/FeaturedStickersScreen",
"//submodules/TelegramNotices", "//submodules/TelegramNotices",
"//submodules/StickerPeekUI:StickerPeekUI", "//submodules/StickerPeekUI",
"//submodules/CounterControllerTitleView",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -851,9 +851,26 @@ final class ComposePollScreenComponent: Component {
availableButtons = [.gallery, .sticker, .emoji, .location] 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 let pollAttachmentSubject: PollAttachmentSubject
(self?.environment?.controller() as? ComposePollScreen)?.parentController()?.push(c) switch subject {
}, completion: { [weak self] media in 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 { guard let self else {
return return
} }
@ -1729,7 +1746,13 @@ final class ComposePollScreenComponent: Component {
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)) ))
} else { } 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)) let rawString = environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount))
var pollOptionsFooterItems: [AnimatedTextComponent.Item] = [] var pollOptionsFooterItems: [AnimatedTextComponent.Item] = []
@ -2813,7 +2836,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
public func prepareForReuse() { public func prepareForReuse() {
} }
public func requestDismiss(completion: @escaping () -> Void) { public func requestDismiss(completion: @escaping () -> Void) {
completion() completion()
} }

View file

@ -16,9 +16,16 @@ import LocationUI
import AttachmentFileController import AttachmentFileController
import ChatEntityKeyboardInputNode import ChatEntityKeyboardInputNode
public enum PollAttachmentSubject {
case description
case quizAnswer
case option
}
public func presentPollAttachmentScreen( public func presentPollAttachmentScreen(
context: AccountContext, context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
subject: PollAttachmentSubject,
availableButtons: [AttachmentButtonType], availableButtons: [AttachmentButtonType],
inputMediaNodeData: Signal<ChatEntityKeyboardInputNode.InputData?, NoError> = .single(nil), inputMediaNodeData: Signal<ChatEntityKeyboardInputNode.InputData?, NoError> = .single(nil),
present: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void,
@ -36,16 +43,18 @@ public func presentPollAttachmentScreen(
return nil 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 attachmentController.requestController = { [weak attachmentController] type, controllerCompletion in
switch type { switch type {
case .gallery: 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( let controller = MediaPickerScreenImpl(
context: context, context: context,
updatedPresentationData: updatedPresentationData, updatedPresentationData: updatedPresentationData,
@ -54,7 +63,7 @@ public func presentPollAttachmentScreen(
threadTitle: nil, threadTitle: nil,
chatLocation: nil, chatLocation: nil,
enableMultiselection: false, enableMultiselection: false,
subject: .assets(nil, .poll(.option)) subject: .assets(nil, .poll(mediaPickerPollSubject))
) )
controller.getCaptionPanelView = { controller.getCaptionPanelView = {
return nil return nil
@ -71,24 +80,49 @@ public func presentPollAttachmentScreen(
controllerCompletion(controller, controller.mediaPickerContext) controllerCompletion(controller, controller.mediaPickerContext)
return true return true
case .file: case .file:
let controller = context.sharedContext.makeAttachmentFileController(context: context, updatedPresentationData: updatedPresentationData, audio: false, bannedSendMedia: nil, presentGallery: { [weak attachmentController] in let filePickerPollSubject: AttachmentFileControllerSource.PollMode
attachmentController?.dismiss(animated: true) switch subject {
//self?.presentFileGallery() case .description:
}, presentFiles: { [weak attachmentController] in filePickerPollSubject = .description
attachmentController?.dismiss(animated: true) case .quizAnswer:
//self?.presentICloudFileGallery() filePickerPollSubject = .quizAnswer
}, presentDocumentScanner: { default:
//self?.presentDocumentScanner() filePickerPollSubject = .description
}, send: { mediaReferences in
completion(mediaReferences.first!)
})
guard let controller = controller as? AttachmentFileControllerImpl else {
return false
} }
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) controllerCompletion(controller, controller.mediaPickerContext)
return true return true
case .location: 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)) completion(.standalone(media: location))
}) })
controllerCompletion(controller, controller.mediaPickerContext) controllerCompletion(controller, controller.mediaPickerContext)
@ -100,9 +134,23 @@ public func presentPollAttachmentScreen(
guard let content = content?.stickers else { guard let content = content?.stickers else {
return return
} }
let controller = StickerAttachmentScreen(context: context, mode: .stickers(content), completion: { sticker in let stickerPickerPollSubject: StickerAttachmentScreen.Source.PollMode
completion(sticker) 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) controllerCompletion(controller, controller.mediaPickerContext)
}) })
return true return true
@ -113,9 +161,23 @@ public func presentPollAttachmentScreen(
guard let content = content?.emoji else { guard let content = content?.emoji else {
return return
} }
let controller = StickerAttachmentScreen(context: context, mode: .emoji(content), completion: { sticker in let stickerPickerPollSubject: StickerAttachmentScreen.Source.PollMode
completion(sticker) 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) controllerCompletion(controller, controller.mediaPickerContext)
}) })
return true return true

View file

@ -15,6 +15,10 @@ import ChatPresentationInterfaceState
import PagerComponent import PagerComponent
import FeaturedStickersScreen import FeaturedStickersScreen
import TelegramNotices import TelegramNotices
import CounterControllerTitleView
import GlassBackgroundComponent
import GlassBarButtonComponent
import BundleIconComponent
final class StickerAttachmentScreenComponent: Component { final class StickerAttachmentScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -49,8 +53,10 @@ final class StickerAttachmentScreenComponent: Component {
final class View: UIView, UIScrollViewDelegate { final class View: UIView, UIScrollViewDelegate {
fileprivate let keyboardView: ComponentView<Empty> fileprivate let keyboardView: ComponentView<Empty>
private let keyboardClippingView: KeyboardClippingView private let keyboardClippingView: KeyboardClippingView
private let panelBackgroundView: GlassBackgroundView
private let panelClippingView: UIView
private let panelHostView: PagerExternalTopPanelContainer private let panelHostView: PagerExternalTopPanelContainer
private let panelSeparatorView: UIView private let cancelButton: ComponentView<Empty>
private var component: StickerAttachmentScreenComponent? private var component: StickerAttachmentScreenComponent?
private(set) weak var state: EmptyComponentState? private(set) weak var state: EmptyComponentState?
@ -109,14 +115,17 @@ final class StickerAttachmentScreenComponent: Component {
override init(frame: CGRect) { override init(frame: CGRect) {
self.keyboardView = ComponentView<Empty>() self.keyboardView = ComponentView<Empty>()
self.keyboardClippingView = KeyboardClippingView() self.keyboardClippingView = KeyboardClippingView()
self.panelBackgroundView = GlassBackgroundView()
self.panelClippingView = UIView()
self.panelHostView = PagerExternalTopPanelContainer() self.panelHostView = PagerExternalTopPanelContainer()
self.panelSeparatorView = UIView() self.cancelButton = ComponentView<Empty>()
super.init(frame: frame) super.init(frame: frame)
self.addSubview(self.keyboardClippingView) self.addSubview(self.keyboardClippingView)
self.addSubview(self.panelSeparatorView) self.addSubview(self.panelBackgroundView)
self.addSubview(self.panelHostView) self.panelBackgroundView.contentView.addSubview(self.panelClippingView)
self.panelClippingView.addSubview(self.panelHostView)
self.interaction = ChatEntityKeyboardInputNode.Interaction( self.interaction = ChatEntityKeyboardInputNode.Interaction(
sendSticker: { [weak self] file, _, _, _, _, _, _, _, _ in sendSticker: { [weak self] file, _, _, _, _, _, _, _, _ in
@ -200,10 +209,7 @@ final class StickerAttachmentScreenComponent: Component {
(self.environment?.controller() as? StickerAttachmentScreen)?.dismiss(animated: true) (self.environment?.controller() as? StickerAttachmentScreen)?.dismiss(animated: true)
} }
func updateContent() { func updateContent(component: StickerAttachmentScreenComponent) {
guard let component = self.component else {
return
}
self.emojiContent?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( self.emojiContent?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self] groupId, item, _, _, _, _ in performItemAction: { [weak self] groupId, item, _, _, _, _ in
guard let self, let component = self.component else { guard let self, let component = self.component else {
@ -599,7 +605,7 @@ final class StickerAttachmentScreenComponent: Component {
externalBackground: nil, externalBackground: nil,
externalExpansionView: nil, externalExpansionView: nil,
customContentView: nil, customContentView: nil,
useOpaqueTheme: false, useOpaqueTheme: true,
hideBackground: true, hideBackground: true,
stateContext: nil, stateContext: nil,
addImage: nil addImage: nil
@ -872,7 +878,7 @@ final class StickerAttachmentScreenComponent: Component {
externalBackground: nil, externalBackground: nil,
externalExpansionView: nil, externalExpansionView: nil,
customContentView: nil, customContentView: nil,
useOpaqueTheme: false, useOpaqueTheme: true,
hideBackground: true, hideBackground: true,
stateContext: nil, stateContext: nil,
addImage: nil addImage: nil
@ -884,7 +890,6 @@ final class StickerAttachmentScreenComponent: Component {
self.environment = environment self.environment = environment
self.backgroundColor = environment.theme.list.plainBackgroundColor self.backgroundColor = environment.theme.list.plainBackgroundColor
self.panelSeparatorView.backgroundColor = .clear
if self.component == nil { if self.component == nil {
let data = combineLatest( let data = combineLatest(
@ -933,10 +938,7 @@ final class StickerAttachmentScreenComponent: Component {
} }
self.stickerContent = stickerContent self.stickerContent = stickerContent
} }
Queue.mainQueue().justDispatch { self.updateContent(component: component)
self.updateContent()
self.state?.updated()
}
})) }))
} }
@ -962,15 +964,15 @@ final class StickerAttachmentScreenComponent: Component {
theme: environment.theme, theme: environment.theme,
strings: environment.strings, strings: environment.strings,
isContentInFocus: true, isContentInFocus: true,
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0 + topInset, left: 0.0, bottom: 0.0, right: 0.0), containerInsets: UIEdgeInsets(top: topPanelHeight + topInset - 11.0, left: 0.0, bottom: 0.0, right: 0.0),
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), topPanelInsets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0),
emojiContent: self.emojiContent, emojiContent: self.emojiContent,
stickerContent: self.stickerContent, stickerContent: self.stickerContent,
maskContent: nil, maskContent: nil,
gifContent: nil, gifContent: nil,
hasRecentGifs: false, hasRecentGifs: false,
availableGifSearchEmojies: [], availableGifSearchEmojies: [],
defaultToEmojiTab: emojiContent != nil, defaultToEmojiTab: self.emojiContent != nil,
externalTopPanelContainer: self.panelHostView, externalTopPanelContainer: self.panelHostView,
externalBottomPanelContainer: nil, externalBottomPanelContainer: nil,
externalTintMaskContainer: nil, externalTintMaskContainer: nil,
@ -993,6 +995,16 @@ final class StickerAttachmentScreenComponent: Component {
self.forceUpdate = true self.forceUpdate = true
self.searchVisible = searchVisible self.searchVisible = searchVisible
self.state?.updated(transition: transition) 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 hideTopPanelUpdated: { _, _ in
}, },
@ -1053,28 +1065,57 @@ final class StickerAttachmentScreenComponent: Component {
self.keyboardClippingView.addSubview(keyboardComponentView) self.keyboardClippingView.addSubview(keyboardComponentView)
} }
// if panelBackgroundColor.alpha < 0.01 { self.keyboardClippingView.clipsToBounds = false
// self.keyboardClippingView.clipsToBounds = true
// } else {
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))) 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) 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: 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))) transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: -12.0, y: 8.0 - UIScreenPixel), 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.setAlpha(view: self.panelSeparatorView, alpha: topPanelAlpha) let barButtonSize = CGSize(width: 44.0, height: 44.0)
let cancelButtonSize = self.cancelButton.update(
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - UIScreenPixel), size: CGSize(width: keyboardSize.width, height: UIScreenPixel))) 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 return availableSize
@ -1096,11 +1137,21 @@ final class StickerAttachmentScreen: ViewControllerComponentContainer, Attachmen
case emoji(EmojiPagerContentComponent) case emoji(EmojiPagerContentComponent)
} }
enum Source: Equatable {
enum PollMode: Equatable {
case description
case quizAnswer
case option
}
case poll(PollMode)
}
private let context: AccountContext private let context: AccountContext
private let mode: Mode private let mode: Mode
private let completion: (AnyMediaReference) -> Void 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.context = context
self.mode = mode self.mode = mode
self.completion = completion self.completion = completion
@ -1109,9 +1160,43 @@ final class StickerAttachmentScreen: ViewControllerComponentContainer, Attachmen
context: context, context: context,
mode: mode, mode: mode,
completion: completion completion: completion
), navigationBarAppearance: .default, theme: .default) ), navigationBarAppearance: .transparent, theme: .default)
self._hasGlassStyle = true 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) { required init(coder: NSCoder) {

View file

@ -115,8 +115,8 @@ private class MediaHeaderItemNode: ASDisplayNode {
let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0 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 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: 20.0), size: subtitleLayout.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.titleNode, frame: minimizedTitleFrame)
transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame) transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame)

View file

@ -153,6 +153,7 @@ public final class TextFieldComponent: Component {
public let hideKeyboard: Bool public let hideKeyboard: Bool
public let customInputView: UIView? public let customInputView: UIView?
public let placeholder: NSAttributedString? public let placeholder: NSAttributedString?
public let placeholderVerticalOffset: CGFloat
public let prefix: NSAttributedString? public let prefix: NSAttributedString?
public let suffix: NSAttributedString? public let suffix: NSAttributedString?
public let resetText: NSAttributedString? public let resetText: NSAttributedString?
@ -185,6 +186,7 @@ public final class TextFieldComponent: Component {
hideKeyboard: Bool, hideKeyboard: Bool,
customInputView: UIView?, customInputView: UIView?,
placeholder: NSAttributedString? = nil, placeholder: NSAttributedString? = nil,
placeholderVerticalOffset: CGFloat = 0.0,
prefix: NSAttributedString? = nil, prefix: NSAttributedString? = nil,
suffix: NSAttributedString? = nil, suffix: NSAttributedString? = nil,
resetText: NSAttributedString?, resetText: NSAttributedString?,
@ -216,6 +218,7 @@ public final class TextFieldComponent: Component {
self.hideKeyboard = hideKeyboard self.hideKeyboard = hideKeyboard
self.customInputView = customInputView self.customInputView = customInputView
self.placeholder = placeholder self.placeholder = placeholder
self.placeholderVerticalOffset = placeholderVerticalOffset
self.prefix = prefix self.prefix = prefix
self.suffix = suffix self.suffix = suffix
self.resetText = resetText self.resetText = resetText
@ -271,6 +274,9 @@ public final class TextFieldComponent: Component {
if lhs.placeholder != rhs.placeholder { if lhs.placeholder != rhs.placeholder {
return false return false
} }
if lhs.placeholderVerticalOffset != rhs.placeholderVerticalOffset {
return false
}
if lhs.prefix != rhs.prefix { if lhs.prefix != rhs.prefix {
return false return false
} }
@ -365,6 +371,7 @@ public final class TextFieldComponent: Component {
self.textView.layer.isOpaque = false self.textView.layer.isOpaque = false
self.textView.indicatorStyle = .white self.textView.indicatorStyle = .white
self.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: 0.0) 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) self.inputMenu = TextInputMenu(hasSpoilers: true, hasQuotes: true)
@ -1587,27 +1594,28 @@ public final class TextFieldComponent: Component {
} }
let placeholderSize = placeholder.update( let placeholderSize = placeholder.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(
text: .plain(placeholderValue) Text(attributedString: placeholderValue)
)), ),
environment: {}, environment: {},
containerSize: textFrame.size 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 let placeholderView = placeholder.view {
if placeholderView.superview == nil { if placeholderView.superview == nil {
placeholderView.isUserInteractionEnabled = false placeholderView.isUserInteractionEnabled = false
placeholderView.layer.anchorPoint = CGPoint()
self.textView.insertSubview(placeholderView, at: 0) 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.bounds = CGRect(origin: CGPoint(), size: placeholderFrame.size)
placeholderView.isHidden = self.textView.textStorage.length != 0 placeholderView.isHidden = self.textView.textStorage.length != 0
} }
} else if let placeholder = self.placeholder { } else {
self.placeholder = nil if let placeholder = self.placeholder {
placeholder.view?.removeFromSuperview() self.placeholder = nil
placeholder.view?.removeFromSuperview()
}
} }
self.updateEmojiSuggestion(transition: .immediate) self.updateEmojiSuggestion(transition: .immediate)

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "replypoll.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -3565,6 +3565,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.controller?.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams()), scrollPosition: .top(-18.0)) 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 let updateInputTextState = self.chatPresentationInterfaceState.interfaceState.effectiveInputState != chatPresentationInterfaceState.interfaceState.effectiveInputState
self.chatPresentationInterfaceState = chatPresentationInterfaceState self.chatPresentationInterfaceState = chatPresentationInterfaceState

View file

@ -413,7 +413,7 @@ extension ChatControllerImpl {
let controller = strongSelf.context.sharedContext.makeAttachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, audio: true, bannedSendMedia: bannedSendFiles, presentGallery: { let controller = strongSelf.context.sharedContext.makeAttachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, audio: true, bannedSendMedia: bannedSendFiles, presentGallery: {
}, presentFiles: { [weak self, weak attachmentController] in }, presentFiles: { [weak self, weak attachmentController] in
attachmentController?.dismiss(animated: true) 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 }, presentDocumentScanner: nil, send: { [weak self] mediaReferences in
guard let self else { guard let self else {
return 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( let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId), TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
@ -1167,7 +1167,7 @@ extension ChatControllerImpl {
let (accountPeer, limits, premiumLimits) = result let (accountPeer, limits, premiumLimits) = result
let isPremium = accountPeer?.isPremium ?? false 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 { if let strongSelf = self, !urls.isEmpty {
var signals: [Signal<ICloudFileDescription?, NoError>] = [] var signals: [Signal<ICloudFileDescription?, NoError>] = []
for url in urls { for url in urls {

View file

@ -2751,7 +2751,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} }
public func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference]) -> Void) -> AttachmentFileController { public func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, 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? { public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? {