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 hasPanel = false
// let previousHasButton = self.hasButton
let hasButton = self.panel.isButtonVisible && !self.isDismissing
self.hasButton = hasButton
if let controller = self.controller, controller.buttons.count > 1 || controller.hasTextInput || self.panel.hasMediaAccessoryPanel {
@ -1405,6 +1404,7 @@ public class AttachmentController: ViewController, MinimizableController {
let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting)
let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, transition: transition)
if hasPanel || hasButton {
containerInsets.bottom = panelHeight + panelOffset
}
@ -1526,7 +1526,7 @@ public class AttachmentController: ViewController, MinimizableController {
public var forceSourceRect = false
fileprivate var isStandalone: Bool {
return self.buttons.contains(.standalone)
return self.buttons.contains(.standalone) || self.buttons.count == 1
}
public func convertToStandalone() {

View file

@ -1018,6 +1018,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
private var elevateProgress: Bool = false
private var buttons: [AttachmentButtonType] = []
private var selectedIndex: Int = 0
private var selectionOverrideIndex: Int?
private(set) var isSelecting: Bool = false
private var selectionCount: Int = 0
@ -1025,12 +1026,30 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
var isButtonVisible: Bool {
return self.mainButtonState.isVisible || self.secondaryButtonState.isVisible
}
var hasMediaAccessoryPanel: Bool {
if case .glass = self.panelStyle {
return self.playlistStateAndType != nil
} else {
private var peerMessagesPlaylistLocation: PeerMessagesPlaylistLocation? {
return self.playlistLocation as? PeerMessagesPlaylistLocation
}
private var currentButtonType: AttachmentButtonType? {
let selectedIndex = self.selectionOverrideIndex ?? self.selectedIndex
guard selectedIndex >= 0 && selectedIndex < self.buttons.count else {
return nil
}
return self.buttons[selectedIndex]
}
private var shouldDisplayMediaAccessoryPanel: Bool {
guard case .glass = self.panelStyle, self.playlistStateAndType != nil else {
return false
}
guard let playlistLocation = self.peerMessagesPlaylistLocation, case .custom = playlistLocation else {
return false
}
guard let currentButtonType = self.currentButtonType, case .audio = currentButtonType else {
return false
}
return true
}
var hasMediaAccessoryPanel: Bool {
return self.shouldDisplayMediaAccessoryPanel
}
private var validLayout: ContainerViewLayout?
@ -1470,11 +1489,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
return
}
let hadMediaAccessoryPanel = strongSelf.hasMediaAccessoryPanel
let updatedPeerMessagesPlaylistLocation = playlistStateAndType?.1.playlistLocation as? PeerMessagesPlaylistLocation
if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) ||
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) ||
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) ||
strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order ||
strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 {
strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 ||
strongSelf.peerMessagesPlaylistLocation != updatedPeerMessagesPlaylistLocation {
if let playlistStateAndType {
strongSelf.playlistStateAndType = (
@ -1702,40 +1723,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
)
}
}
private func openCurrentMusicPlayer() {
guard let (state, _, _, order, type, account) = self.playlistStateAndType,
let id = state.id as? PeerMessagesMediaPlaylistItemId,
let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation else {
return
}
guard type == .music else {
self.context.sharedContext.navigateToChat(accountId: self.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId)
return
}
if case .custom = playlistLocation {
let controllerContext: AccountContext
if account.id == self.context.account.id {
controllerContext = self.context
} else {
controllerContext = self.context.sharedContext.makeTempAccountContext(account: account)
}
let controller = self.context.sharedContext.makeOverlayAudioPlayerController(
context: controllerContext,
chatLocation: .peer(id: id.messageId.peerId),
type: type,
initialMessageId: id.messageId,
initialOrder: order,
playlistLocation: playlistLocation,
parentNavigationController: self.controller?.navigationController as? NavigationController
)
self.view.window?.endEditing(true)
self.present(controller)
}
}
private func makeMediaAccessoryPanel() -> MediaNavigationAccessoryPanel {
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, presentationData: self.presentationData, displayBackground: false, customTintColor: self.presentationData.theme.rootController.tabBar.textColor)
mediaAccessoryPanel.getController = { [weak self] in
@ -1784,14 +1772,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
self.context.sharedContext.mediaManager.playlistControl(.previous, type: type)
}
}
mediaAccessoryPanel.tapAction = { [weak self] in
self?.openCurrentMusicPlayer()
mediaAccessoryPanel.tapAction = {
}
return mediaAccessoryPanel
}
private func updateMediaAccessoryPanel(frame: CGRect?, transition: ContainedViewLayoutTransition) {
guard case .glass = self.panelStyle,
self.hasMediaAccessoryPanel,
let frame,
let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType else {
self.dismissMediaAccessoryPanel(transition: transition)
@ -1869,8 +1857,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
func updateSelectedIndex(_ index: Int) {
let hadMediaAccessoryPanel = self.hasMediaAccessoryPanel
self.selectedIndex = index
self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
if hadMediaAccessoryPanel != self.hasMediaAccessoryPanel {
self.requestLayout(transition: .immediate)
}
}
var buttonSize: CGSize {
@ -1949,6 +1941,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
let button = self.buttons[index]
self.skipLensUpdate = true
self.selectionOverrideIndex = index
if self.selectionChanged(button) {
self.selectedIndex = index
if self.buttons.count > 5, let button = self.itemViews[button.key] {
@ -1972,6 +1965,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
self.updateItemContainers(contentOffset: newBounds.minX, transition: transition)
}
}
self.selectionOverrideIndex = nil
self.skipLensUpdate = false
}
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
@ -1986,8 +1980,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
return
}
var buttons = self.buttons
if buttons.count == 1 {
buttons.removeAll()
}
var width = layout.size.width
if self.buttons.count == 3 {
if buttons.count == 3 {
width = smallPanelWidth
}
@ -1999,11 +1998,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
panelSideInset = 3.0
}
var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, self.buttons.count - 1)))
if self.buttons.count == 3 {
distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - 32.0) / CGFloat(max(1, self.buttons.count - 1)))
var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, buttons.count - 1)))
if buttons.count == 3 {
distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - 32.0) / CGFloat(max(1, buttons.count - 1)))
}
let internalWidth = distanceBetweenNodes * CGFloat(self.buttons.count - 1)
let internalWidth = distanceBetweenNodes * CGFloat(max(0, buttons.count - 1))
var buttonWidth = self.buttonSize.width
var leftNodeOriginX: CGFloat
var maxButtonsToFit = 5
@ -2016,11 +2015,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
case .legacy:
leftNodeOriginX = (width - internalWidth) / 2.0
}
if self.buttons.count == 3 {
if buttons.count == 3 {
leftNodeOriginX = floor((layout.size.width - width) / 2.0) + 16.0
}
if self.buttons.count > maxButtonsToFit && layout.size.width < layout.size.height {
if buttons.count > maxButtonsToFit && layout.size.width < layout.size.height {
switch self.panelStyle {
case .glass:
buttonWidth = smallGlassButtonSize.width
@ -2036,12 +2035,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
var selectionFrame = CGRect()
var mostRightX = 0.0
for i in 0 ..< self.buttons.count {
for i in 0 ..< buttons.count {
let originX = floorToScreenPixels(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0)
let buttonFrame = CGRect(origin: CGPoint(x: originX, y: -3.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height))
mostRightX = buttonFrame.maxX
let type = self.buttons[i]
let type = buttons[i]
let _ = validIds.insert(type.key)
var buttonTransition = transition
@ -2096,20 +2095,22 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
context: self.context,
style: self.panelStyle == .glass ? .glass : .legacy,
type: type,
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
isFirstOrLast: i == 0 || i == buttons.count - 1,
isSelected: false,
strings: self.presentationData.strings,
theme: self.presentationData.theme,
action: { [weak self] in
if let strongSelf = self {
strongSelf.selectionOverrideIndex = i
if strongSelf.selectionChanged(type) {
strongSelf.selectedIndex = i
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
if strongSelf.buttons.count > 5, let button = strongSelf.itemViews[type.key] {
if buttons.count > 5, let button = strongSelf.itemViews[type.key] {
strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true)
}
}
strongSelf.selectionOverrideIndex = nil
}
}, longPressAction: { [weak self] in
if let strongSelf = self, i == strongSelf.selectedIndex {
@ -2128,7 +2129,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
context: self.context,
style: self.panelStyle == .glass ? .glass : .legacy,
type: type,
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
isFirstOrLast: i == 0 || i == buttons.count - 1,
isSelected: true,
strings: self.presentationData.strings,
theme: self.presentationData.theme,
@ -2433,7 +2434,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
let topAccessoryHeight: CGFloat
if case .glass = self.panelStyle, self.playlistStateAndType != nil {
if self.hasMediaAccessoryPanel {
topAccessoryHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
} else {
topAccessoryHeight = 0.0
@ -2474,7 +2475,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
self.updateViews(transition: .immediate)
let glassPanelHeight: CGFloat = 62.0
let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom + topAccessoryHeight))
var bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: topAccessoryHeight + self.buttonSize.height + insets.bottom))
if buttons.count == 1 && topAccessoryHeight > 0.0 {
bounds.size.height -= self.buttonSize.height
}
var mediaAccessoryPanelFrame: CGRect?
if case .glass = self.panelStyle {
let backgroundView: GlassBackgroundView
@ -2517,9 +2521,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
let basePanelHeight = isSelecting ? max(0.0, textPanelHeight - 11.0) : glassPanelHeight
let panelSize = CGSize(width: isSelecting ? textPanelWidth : buttonsPanelWidth, height: basePanelHeight + topAccessoryHeight)
var panelSize = CGSize(width: isSelecting ? textPanelWidth : buttonsPanelWidth, height: basePanelHeight + topAccessoryHeight)
if !isSelecting && buttons.count == 1 && topAccessoryHeight > 0.0 {
panelSize.height = topAccessoryHeight
}
let cornerRadius: CGFloat = isSelecting ? min(20.0, basePanelHeight * 0.5) : glassPanelHeight * 0.5
let cornerRadius: CGFloat
if isSelecting {
cornerRadius = min(20.0, basePanelHeight * 0.5)
} else if self.hasMediaAccessoryPanel {
cornerRadius = 18.5
} else {
cornerRadius = glassPanelHeight * 0.5
}
let backgroundOriginX: CGFloat = isSelecting ? panelSideInset : floorToScreenPixels((layout.size.width - panelSize.width) / 2.0)
backgroundView.update(size: panelSize, cornerRadius: cornerRadius, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: ComponentTransition(transition))
@ -2705,7 +2719,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel)))
if case .glass = self.panelStyle {
transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: topAccessoryHeight + (self.isSelecting ? -11.0 : 0.0)), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height)))
let scrollFrame = CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: topAccessoryHeight + (self.isSelecting ? -11.0 : 0.0)), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height))
transition.updatePosition(node: self.scrollNode, position: scrollFrame.center)
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: self.scrollNode.view.contentOffset.x, y: 0.0), size: scrollFrame.size))
}
if let progress = self.loadingProgress {

View file

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

View file

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

View file

@ -83,6 +83,18 @@ public final class Text: Component {
self.color = color
self.tintColor = tintColor
}
public convenience init(attributedString: NSAttributedString, tintColor: UIColor? = nil) {
let attributes = attributedString.attributes(at: 0, effectiveRange: nil)
let font = attributes[.font] as? UIFont ?? UIFont.systemFont(ofSize: UIFont.systemFontSize)
let color = attributes[.foregroundColor] as? UIColor ?? .black
self.init(
text: attributedString.string,
font: font,
color: color,
tintColor: tintColor
)
}
public static func ==(lhs: Text, rhs: Text) -> Bool {
if lhs.text != rhs.text {

View file

@ -37,6 +37,7 @@ public final class CounterControllerTitleView: UIView {
private var primaryTextColor: UIColor?
private var secondaryTextColor: UIColor?
private let verticalOffset: CGFloat
private var nextLayoutTransition: ContainedViewLayoutTransition?
@ -65,7 +66,7 @@ public final class CounterControllerTitleView: UIView {
let secondaryTextColor = self.secondaryTextColor ?? self.theme.rootController.navigationBar.secondaryTextColor
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: primaryTextColor)
let subtitleText = NSAttributedString(string: self.title.counter ?? "", font: Font.with(size: 13.0, traits: .monospacedNumbers), textColor: secondaryTextColor)
let subtitleText = NSAttributedString(string: self.title.counter ?? "", font: Font.with(size: 12.0, traits: .monospacedNumbers), textColor: secondaryTextColor)
if let previousSubtitleText = self.subtitleNode.attributedText, previousSubtitleText.string.isEmpty != subtitleText.string.isEmpty && subtitleText.string.isEmpty {
if let disappearingSubtitleNode = self.disappearingSubtitleNode {
self.disappearingSubtitleNode = nil
@ -94,8 +95,9 @@ public final class CounterControllerTitleView: UIView {
self.setNeedsLayout()
}
public init(theme: PresentationTheme) {
public init(theme: PresentationTheme, verticalOffset: CGFloat = 0.0) {
self.theme = theme
self.verticalOffset = verticalOffset
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
@ -126,7 +128,7 @@ public final class CounterControllerTitleView: UIView {
super.layoutSubviews()
let size = self.bounds.size
let spacing: CGFloat = 0.0
let spacing: CGFloat = 1.0
let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width), height: size.height))
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: max(1.0, size.width), height: size.height))
@ -146,11 +148,11 @@ public final class CounterControllerTitleView: UIView {
self.nextLayoutTransition = nil
}
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + self.verticalOffset), size: titleSize)
self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
transition.updatePosition(node: self.titleNode, position: titleFrame.center)
let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + spacing), size: subtitleSize)
let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + spacing + self.verticalOffset), size: subtitleSize)
self.subtitleNode.bounds = CGRect(origin: CGPoint(), size: subtitleFrame.size)
transition.updatePosition(node: self.subtitleNode, position: subtitleFrame.center)
transition.updateTransformScale(node: self.subtitleNode, scale: self.title.counter != nil ? 1.0 : 0.001)

View file

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

View file

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

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

View file

@ -933,7 +933,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
if statusUpdated && item.displayFileInfo {
if let file = selectedMedia as? TelegramMediaFile {
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList, isSavedMusic: item.isSavedMusic, isAttachMusic: item.isAttachMusic)
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList, isSavedMusic: item.isSavedMusic, isAttachMusic: item.isAttachMusic || item.isStoryMusic)
|> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
if case .Fetching = value.fetchStatus, !item.isDownloadList {
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())

View file

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

View file

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

View file

@ -354,6 +354,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
private let cancelButton = ComponentView<Empty>()
private let searchButton = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private let subtitle = ComponentView<Empty>()
fileprivate let topEdgeEffectView: EdgeEffectView
fileprivate let bottomEdgeEffectView: EdgeEffectView
@ -670,15 +671,18 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
var coordinate = userLocation?.coordinate
switch strongSelf.mode {
case .share:
if source == .story {
switch source {
case .generic:
title = presentationData.strings.Map_SendMyCurrentLocation
case .story:
if let initialLocation = strongSelf.controller?.initialLocation {
title = presentationData.strings.Location_AddThisLocation
coordinate = initialLocation
} else {
title = presentationData.strings.Location_AddMyLocation
}
} else {
title = presentationData.strings.Map_SendMyCurrentLocation
case .poll:
title = presentationData.strings.Location_AddThisLocation
}
case .pick:
title = presentationData.strings.Map_SetThisLocation
@ -1358,6 +1362,27 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.view.addSubview(self.topEdgeEffectView)
}
let titleSpacing: CGFloat = 1.0
var combinedTitleHeight: CGFloat = 0.0
var subtitle: String = ""
if let source = self.controller?.source {
switch source {
case let .poll(pollMode):
//TODO:localize
switch pollMode {
case .description:
subtitle = "Add location to the poll description"
case .quizAnswer:
subtitle = "Add location to the quiz explanation"
case .option:
subtitle = "Add location to this option"
}
default:
break
}
}
let titleSize = self.title.update(
transition: ComponentTransition(transition),
component: AnyComponent(
@ -1374,7 +1399,37 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
environment: {},
containerSize: CGSize(width: 200.0, height: 40.0)
)
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize)
combinedTitleHeight += titleSize.height
if !subtitle.isEmpty {
let subtitleSize = self.subtitle.update(
transition: ComponentTransition(transition),
component: AnyComponent(
MultilineTextComponent(
text: .plain(
NSAttributedString(
string: subtitle,
font: Font.regular(12.0),
textColor: self.headerNode.mapNode.mapMode == .map ? self.presentationData.theme.rootController.navigationBar.primaryTextColor.withMultipliedAlpha(0.5) : .white.withAlphaComponent(0.5)
)
)
)
),
environment: {},
containerSize: CGSize(width: 200.0, height: 40.0)
)
combinedTitleHeight += subtitleSize.height + titleSpacing
let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - subtitleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight + combinedTitleHeight) / 2.0) + 3.0 - subtitleSize.height), size: subtitleSize)
if let subtitleView = self.subtitle.view {
if subtitleView.superview == nil {
self.view.addSubview(subtitleView)
}
transition.updateFrame(view: subtitleView, frame: subtitleFrame)
}
}
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight - combinedTitleHeight) / 2.0) + 3.0), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.view.addSubview(titleView)

View file

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

View file

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

View file

@ -1277,9 +1277,9 @@ public struct PresentationResourcesChat {
let foregroundColor: UIColor
switch type {
case .incoming:
foregroundColor = theme.chat.message.incoming.mediaActiveControlColor
foregroundColor = theme.chat.message.incoming.accentTextColor
case .outgoing:
foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor
foregroundColor = theme.chat.message.outgoing.accentTextColor
case .free:
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
}
@ -1303,9 +1303,9 @@ public struct PresentationResourcesChat {
let foregroundColor: UIColor
switch type {
case .incoming:
foregroundColor = theme.chat.message.incoming.mediaActiveControlColor
foregroundColor = theme.chat.message.incoming.accentTextColor
case .outgoing:
foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor
foregroundColor = theme.chat.message.outgoing.accentTextColor
case .free:
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
}
@ -1322,6 +1322,32 @@ public struct PresentationResourcesChat {
})
}
public static func chatReplyPollIndicatorIcon(_ theme: PresentationTheme, type: ChatExpiredStoryIndicatorType) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatReplyStoryIndicatorIcon(type: type), { theme in
return generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let foregroundColor: UIColor
switch type {
case .incoming:
foregroundColor = theme.chat.message.incoming.accentTextColor
case .outgoing:
foregroundColor = theme.chat.message.outgoing.accentTextColor
case .free:
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
}
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/PollReplyIcon"), color: foregroundColor) {
UIGraphicsPushContext(context)
let fittedSize = image.size
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - fittedSize.width) * 0.5), y: floor((size.height - fittedSize.height) * 0.5)), size: fittedSize), blendMode: .normal, alpha: 1.0)
UIGraphicsPopContext()
}
})
})
}
public static func storyViewListLikeIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.storyViewListLikeIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Stories/InputLikeOn"), color: UIColor(rgb: 0xFF3B30))
@ -1417,8 +1443,8 @@ public struct PresentationResourcesChat {
context.setFillColor(UIColor.white.cgColor)
let lineHeight = 2.0 - UIScreenPixel
context.addPath(CGPath(roundedRect: CGRect(x: 1.0, y: 7.0, width: 15.0, height: lineHeight), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.addPath(CGPath(roundedRect: CGRect(x: 8.0, y: 0.0, width: lineHeight, height: 15.0), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.addPath(CGPath(roundedRect: CGRect(x: 1.0, y: 7.0 - UIScreenPixel, width: 15.0, height: lineHeight), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.addPath(CGPath(roundedRect: CGRect(x: 8.0 - UIScreenPixel, y: 0.0, width: lineHeight, height: 15.0), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.fillPath()
})
})

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? {
return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in
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:
return (NSAttributedString(string: strings.Message_VideoMessageExpired), true)
case let .poll(text):
return (NSAttributedString(string: "📊 \(text)"), false)
return (NSAttributedString(string: text), false)
case let .todo(text):
return (NSAttributedString(string: "☑️ \(text)"), false)
case let .restricted(text):

View file

@ -26,6 +26,7 @@ import ContextUI
private final class AttachmentFileControllerArguments {
let context: AccountContext
let isAudio: Bool
let isAttach: Bool
let openGallery: () -> Void
let openFiles: () -> Void
let scanDocument: () -> Void
@ -38,9 +39,10 @@ private final class AttachmentFileControllerArguments {
let setMessageSelection: ([MessageId], Message?, Bool) -> Void
let openMessageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)
init(context: AccountContext, isAudio: Bool, openGallery: @escaping () -> Void, openFiles: @escaping () -> Void, scanDocument: @escaping () -> Void, expandSavedMusic: @escaping () -> Void, expandRecentMusic: @escaping () -> Void, send: @escaping (Message) -> Void, toggleMediaPlayback: @escaping (Message) -> Void, isSelectionActive: @escaping () -> Bool, toggleMessageSelection: @escaping (Message) -> Void, setMessageSelection: @escaping ([MessageId], Message?, Bool) -> Void, openMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)) {
init(context: AccountContext, isAudio: Bool, isAttach: Bool, openGallery: @escaping () -> Void, openFiles: @escaping () -> Void, scanDocument: @escaping () -> Void, expandSavedMusic: @escaping () -> Void, expandRecentMusic: @escaping () -> Void, send: @escaping (Message) -> Void, toggleMediaPlayback: @escaping (Message) -> Void, isSelectionActive: @escaping () -> Bool, toggleMessageSelection: @escaping (Message) -> Void, setMessageSelection: @escaping ([MessageId], Message?, Bool) -> Void, openMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)) {
self.context = context
self.isAudio = isAudio
self.isAttach = isAttach
self.openGallery = openGallery
self.openFiles = openFiles
self.scanDocument = scanDocument
@ -250,7 +252,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat
let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false))
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: arguments.context.account.peerId), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: arguments.context.account.peerId), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
case let .savedShowMore(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
arguments.expandSavedMusic()
@ -275,8 +277,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat
let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false))
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
case let .recentShowMore(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
arguments.expandRecentMusic()
@ -301,8 +302,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat
let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false))
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
case let .globalShowMore(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
@ -434,6 +434,8 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
fileprivate var caption: NSAttributedString?
fileprivate var selectionCount = ValuePromise<Int>(0)
fileprivate var bottomEdgeColor: UIColor = .clear
fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?) -> Void)?
var delayDisappear = false
@ -441,6 +443,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
var hasBottomEdgeEffect = true
var resetForReuseImpl: () -> Void = {}
var onDismissImpl: () -> Void = {}
public func resetForReuse() {
self.resetForReuseImpl()
self.scrollToTop?()
@ -451,12 +454,23 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset)
self.delayDisappear = false
}
public func requestDismiss(completion: @escaping () -> Void) {
self.onDismissImpl()
completion()
}
public func shouldDismissImmediately() -> Bool {
if self.hasBottomEdgeEffect {
self.onDismissImpl()
}
return true
}
public var mediaPickerContext: AttachmentMediaPickerContext? {
return AttachmentFileContext(controller: self)
}
private var topEdgeEffectView: EdgeEffectView?
private var bottomEdgeEffectView: EdgeEffectView?
var isSearching: Bool = false {
@ -468,21 +482,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
let topEdgeEffectView: EdgeEffectView
if let current = self.topEdgeEffectView {
topEdgeEffectView = current
} else {
topEdgeEffectView = EdgeEffectView()
if let navigationBar = self.navigationBar {
self.view.insertSubview(topEdgeEffectView, belowSubview: navigationBar.view)
}
self.topEdgeEffectView = topEdgeEffectView
}
let edgeEffectHeight: CGFloat = 88.0
let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
transition.updateFrame(view: topEdgeEffectView, frame: topEdgeEffectFrame)
topEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
if self.hasBottomEdgeEffect {
let bottomEdgeEffectView: EdgeEffectView
@ -497,7 +497,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
transition.updateFrame(view: bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
transition.updateAlpha(layer: bottomEdgeEffectView.layer, alpha: self.isSearching ? 0.0 : 1.0)
bottomEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition))
bottomEdgeEffectView.update(content: self.bottomEdgeColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition))
} else if let bottomEdgeEffectView = self.bottomEdgeEffectView {
bottomEdgeEffectView.removeFromSuperview()
}
@ -521,13 +521,32 @@ private func messageSelectionState(state: AttachmentFileControllerState, message
public enum AttachmentFileControllerMode {
case recent
case audio
case audio(story: Bool)
var isAudio: Bool {
if case .audio = self {
return true
} else {
return false
}
}
}
public enum AttachmentFileControllerSource: Equatable {
public enum PollMode: Equatable {
case description
case quizAnswer
}
case generic
case poll(PollMode)
}
public func makeAttachmentFileControllerImpl(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
mode: AttachmentFileControllerMode = .recent,
source: AttachmentFileControllerSource = .generic,
bannedSendMedia: (Int32, Bool)?,
presentGallery: @escaping () -> Void,
presentFiles: @escaping () -> Void,
@ -549,10 +568,20 @@ public func makeAttachmentFileControllerImpl(
var updateIsSearchingImpl: ((Bool) -> Void)?
var updateSelectionCountImpl: ((Int) -> Void)?
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
var updateBottomColorImpl: ((UIColor) -> Void)?
var isAudio = false
var isAttach = true
if case let .audio(isStory) = mode {
isAudio = true
isAttach = !isStory
}
var didPreviewAudio = false
let arguments = AttachmentFileControllerArguments(
context: context,
isAudio: mode == .audio,
isAudio: isAudio,
isAttach: isAttach,
openGallery: {
presentGallery()
},
@ -603,6 +632,8 @@ public func makeAttachmentFileControllerImpl(
}
},
toggleMediaPlayback: { message in
didPreviewAudio = true
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil)
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause))
},
@ -727,7 +758,7 @@ public func makeAttachmentFileControllerImpl(
let existingCloseButton = Atomic<BarComponentHostNode?>(value: nil)
let existingSearchButton = Atomic<BarComponentHostNode?>(value: nil)
let previousRecentDocuments = Atomic<[Message]?>(value: nil)
let signal = combineLatest(queue: Queue.mainQueue(),
presentationData,
@ -823,21 +854,32 @@ public func makeAttachmentFileControllerImpl(
}
var rightNavigationButton: ItemListNavigationButton?
if bannedSendMedia == nil && (recentDocuments == nil || (recentDocuments?.count ?? 0) > 10 || (mode == .audio && hasAudioSearch)) {
if bannedSendMedia == nil && (recentDocuments == nil || (recentDocuments?.count ?? 0) > 10 || (mode.isAudio && hasAudioSearch)) {
rightNavigationButton = searchButtonNode.flatMap { ItemListNavigationButton(content: .node($0), style: .regular, enabled: true, action: {}) }
}
let title: String
var subtitle: String?
switch mode {
case .recent:
title = presentationData.strings.Attachment_File
case .audio:
title = presentationData.strings.MediaEditor_Audio_Title
}
if case let .poll(pollMode) = source {
//TODO:localize
switch pollMode {
case .description:
subtitle = "Add file to the poll description"
case .quizAnswer:
subtitle = "Add file to the quiz explanation"
}
}
let controllerState = ItemListControllerState(
presentationData: ItemListPresentationData(presentationData),
title: .text(title),
title: subtitle.flatMap { .textWithSubtitle(title, $0) } ?? .text(title),
leftNavigationButton: leftNavigationButton,
rightNavigationButton: rightNavigationButton,
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
@ -881,6 +923,8 @@ public func makeAttachmentFileControllerImpl(
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: attachmentFileControllerEntries(presentationData: presentationData, mode: mode, state: state, savedMusic: savedMusic, recentDocuments: recentDocuments, hasScan: presentDocumentScanner != nil, empty: bannedSendMedia != nil), style: .blocks, emptyStateItem: emptyItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: animateChanges)
updateBottomColorImpl?(presentationData.theme.list.blocksBackgroundColor)
return (controllerState, (listState, arguments))
} |> afterDisposed {
actionsDisposable.dispose()
@ -888,7 +932,6 @@ public func makeAttachmentFileControllerImpl(
}
let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true)
controller.mulitpleCompletion = { _, _, _ in
let _ = stateValue.with({ state in
if let selectedMessageIds = state.selectedMessageIds {
@ -928,12 +971,25 @@ public func makeAttachmentFileControllerImpl(
updatedState.selectedMessageIds = nil
return updatedState
}
if didPreviewAudio {
context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause))
}
}
controller.onDismissImpl = {
if didPreviewAudio {
context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause))
}
}
updateIsSearchingImpl = { [weak controller] isSearching in
controller?.isSearching = isSearching
}
dismissImpl = { [weak controller] in
controller?.dismiss(animated: true)
if didPreviewAudio {
context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause))
}
}
dismissInputImpl = { [weak controller] in
controller?.view.endEditing(true)
@ -950,6 +1006,9 @@ public func makeAttachmentFileControllerImpl(
presentInGlobalOverlayImpl = { [weak controller] c in
controller?.presentInGlobalOverlay(c)
}
updateBottomColorImpl = { [weak controller] color in
controller?.bottomEdgeColor = color
}
return controller
}
@ -962,15 +1021,16 @@ public func storyAudioPickerController(
var dismissImpl: (() -> Void)?
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
let updatedPresentationData: (PresentationData, Signal<PresentationData, 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
let filePickerController = makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: .audio, bannedSendMedia: nil, presentGallery: {}, presentFiles: {
let filePickerController = makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: .audio(story: true), bannedSendMedia: nil, presentGallery: {}, presentFiles: {
selectFromFiles()
dismissImpl?()
}, presentDocumentScanner: nil, send: { files in
completion(files.first!)
dismissImpl?()
}) as! AttachmentFileControllerImpl
filePickerController.hasBottomEdgeEffect = false
present(filePickerController, filePickerController.mediaPickerContext)
return true
}

View file

@ -74,12 +74,11 @@ final class AttachmentFileSearchItem: ItemListControllerSearch {
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? {
switch self.mode {
case .audio:
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
if let current = current as? AttachmentFileSearchNavigationContentNode {
current.updateTheme(presentationData.theme)
current.updateTheme(self.presentationData.theme)
return current
} else {
return AttachmentFileSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, cancel: self.cancel, focus: self.focus, updateActivity: { _ in
return AttachmentFileSearchNavigationContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, cancel: self.cancel, focus: self.focus, updateActivity: { _ in
})
}
default:
@ -164,7 +163,7 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode {
strings: self.presentationData.strings,
metrics: layout.metrics,
safeInsets: layout.safeInsets,
placeholder: self.mode == .audio ? self.presentationData.strings.Attachment_FilesSearchPlaceholder : self.presentationData.strings.Attachment_FilesSearchPlaceholder,
placeholder: self.presentationData.strings.Attachment_FilesSearchPlaceholder,
updated: { [weak self] query in
guard let self else {
return
@ -329,9 +328,9 @@ private enum AttachmentFileSearchEntry: Comparable, Identifiable {
interaction.toggleMediaPlayback(message)
}, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
let displayFileInfo = mode == .audio
let isStoryMusic = mode == .audio
let isDownloadList = mode == .audio
let displayFileInfo = mode.isAudio
let isStoryMusic = mode.isAudio
let isDownloadList = mode.isAudio
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), systemStyle: .glass, context: interaction.context, chatLocation: .peer(id: PeerId(0)), interaction: itemInteraction, message: message, selection: .none, displayHeader: false, isDownloadList: isDownloadList, isStoryMusic: isStoryMusic, isAttachMusic: true, displayFileInfo: displayFileInfo, displayBackground: true, style: .blocks, sectionId: section)
case let .showMore(text, section):
@ -661,6 +660,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
}
}
} else {
entries.append(.header(title: " ", section: 0))
var index: Int32 = 0
for _ in 0 ..< 16 {
entries.append(.file(index: index, message: nil, section: 0))
@ -1009,6 +1009,20 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
let searchBarTheme = SearchBarNodeTheme(
background: .clear,
separator: .clear,
inputFill: .clear,
primaryText: theme.chat.inputPanel.panelControlColor,
placeholder: theme.chat.inputPanel.inputPlaceholderColor,
inputIcon: theme.chat.inputPanel.inputControlColor,
inputClear: theme.chat.inputPanel.panelControlColor,
accent: theme.chat.inputPanel.panelControlAccentColor,
keyboard: theme.rootController.keyboardColor
)
self.searchBar.updateThemeAndStrings(theme: searchBarTheme, presentationTheme: theme, strings: self.strings)
if let params = self.params {
let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate)
}
@ -1022,7 +1036,7 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont
}
override var nominalHeight: CGFloat {
return 60.0
return 68.0
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {

View file

@ -815,6 +815,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if strongSelf.selectionNode != nil {
return .none
}
if let item = strongSelf.item, item.controllerInteraction.focusedPollAddOptionMessageId != nil {
return .none
}
if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) {
if case let .action(action) = action, !action.contextMenuOnLongPress {
return .none

View file

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

View file

@ -203,6 +203,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
let isText: Bool
var isExpiredStory: Bool = false
var isStory: Bool = false
var isPoll: Bool = false
let titleColor: UIColor
let mainColor: UIColor
@ -429,6 +430,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
}
let textColor: UIColor
let iconColor: UIColor
switch arguments.type {
case let .bubble(incoming):
@ -439,8 +441,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
} else {
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
}
iconColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
case .standalone:
textColor = titleColor
iconColor = titleColor
}
var textLeftInset: CGFloat = 0.0
@ -516,6 +520,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
}
} else {
messageText = NSAttributedString(string: textString.string, font: textFont, textColor: textColor)
if let _ = arguments.message?.media.first(where: { $0 is TelegramMediaPoll }) as? TelegramMediaPoll {
isPoll = true
}
}
var leftInset: CGFloat = 11.0
@ -562,6 +570,26 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
hasRoundImage = true
}
break
} else if let poll = media as? TelegramMediaPoll, let media = poll.attachedMedia {
if let image = media as? TelegramMediaImage {
updatedMediaReference = .message(message: MessageReference(message), media: image)
if let representation = largestRepresentationForPhoto(image) {
imageDimensions = representation.dimensions.cgSize
}
break
} else if let file = media as? TelegramMediaFile, !file.isVideoSticker {
updatedMediaReference = .message(message: MessageReference(message), media: file)
if let dimensions = file.dimensions {
imageDimensions = dimensions.cgSize
} else if let representation = largestImageRepresentation(file.previewRepresentations), !file.isSticker {
imageDimensions = representation.dimensions.cgSize
}
if file.isInstantVideo {
hasRoundImage = true
}
break
}
}
}
} else if let story = arguments.story, let storyPeer = arguments.parentMessage.peers[story.peerId], let storyItem = arguments.parentMessage.associatedStories[story] {
@ -646,7 +674,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
}
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: maxTitleNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: contrainedTextSize.width - additionalTitleWidth, height: contrainedTextSize.height), alignment: .natural, cutout: nil, insets: textInsets))
if isExpiredStory || isStory {
if isExpiredStory || isStory || isPoll {
contrainedTextSize.width -= 26.0
}
@ -713,7 +741,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
size.width = max(titleLayout.size.width + additionalTitleWidth - textInsets.left - textInsets.right, textLeftInset + textLayout.size.width - textInsets.left - textInsets.right - textCutoutWidth) + leftInset + 6.0
size.height = titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing
size.height += 2.0
if isExpiredStory || isStory {
if isExpiredStory || isStory || isPoll {
size.width += 16.0
}
@ -786,7 +814,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: spacing - textInsets.top + 1.0), size: titleLayout.size)
let textFrame = CGRect(origin: CGPoint(x: textLeftInset + leftInset - textInsets.left - 2.0 - textCutoutWidth, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top - 2.0), size: textLayout.size)
let effectiveTextFrame = textFrame.offsetBy(dx: (isExpiredStory || isStory) ? 18.0 : 0.0, dy: 0.0)
let effectiveTextFrame = textFrame.offsetBy(dx: (isExpiredStory || isStory || isPoll) ? 18.0 : 0.0, dy: 0.0)
if textNode.textNode.bounds.isEmpty || !animation.isAnimated || textNode.textNode.bounds.height == effectiveTextFrame.height {
textNode.textNode.frame = effectiveTextFrame
@ -809,7 +837,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
}
}
if isExpiredStory || isStory {
if isExpiredStory || isStory || isPoll {
let expiredStoryIconView: UIImageView
if let current = node.expiredStoryIconView {
expiredStoryIconView = current
@ -827,7 +855,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
imageType = incoming ? .incoming : .outgoing
}
if isExpiredStory {
if isPoll {
expiredStoryIconView.image = PresentationResourcesChat.chatReplyPollIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
} else if isExpiredStory {
expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
} else {
expiredStoryIconView.image = PresentationResourcesChat.chatReplyStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
@ -921,7 +951,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
if let todoItemCompleted {
let checkLayerFrame = CGRect(origin: CGPoint(x: textFrame.minX - 16.0, y: textFrame.minY + 5.0), size: CGSize(width: 12.0, height: 12.0))
let checkTheme = CheckNodeTheme(backgroundColor: titleColor, strokeColor: .clear, borderColor: titleColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0)
let checkTheme = CheckNodeTheme(backgroundColor: iconColor, strokeColor: .clear, borderColor: iconColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0)
let checkLayer: CheckLayer
if let current = node.checkLayer {

View file

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

View file

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

View file

@ -851,9 +851,26 @@ final class ComposePollScreenComponent: Component {
availableButtons = [.gallery, .sticker, .emoji, .location]
}
presentPollAttachmentScreen(context: component.context, updatedPresentationData: nil, availableButtons: availableButtons, inputMediaNodeData: self.inputMediaNodeDataPromise.get() |> map(Optional.init), present: { [weak self] c in
(self?.environment?.controller() as? ComposePollScreen)?.parentController()?.push(c)
}, completion: { [weak self] media in
let pollAttachmentSubject: PollAttachmentSubject
switch subject {
case .description:
pollAttachmentSubject = .description
case .quizAnswer:
pollAttachmentSubject = .quizAnswer
case .pollOption:
pollAttachmentSubject = .option
}
presentPollAttachmentScreen(
context: component.context,
updatedPresentationData: nil,
subject: pollAttachmentSubject,
availableButtons: availableButtons,
inputMediaNodeData: self.inputMediaNodeDataPromise.get() |> map(Optional.init),
present: { [weak self] c in
(self?.environment?.controller() as? ComposePollScreen)?.parentController()?.push(c)
},
completion: { [weak self] media in
guard let self else {
return
}
@ -1729,7 +1746,13 @@ final class ComposePollScreenComponent: Component {
maximumNumberOfLines: 0
))
} else {
let remainingCount = component.initialData.maxPollAnswersCount - self.pollOptions.count
var filledOptionsCount = 0
for option in self.pollOptions {
if option.textInputState.hasText {
filledOptionsCount += 1
}
}
let remainingCount = component.initialData.maxPollAnswersCount - filledOptionsCount
let rawString = environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount))
var pollOptionsFooterItems: [AnimatedTextComponent.Item] = []
@ -2813,7 +2836,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
public func prepareForReuse() {
}
public func requestDismiss(completion: @escaping () -> Void) {
public func requestDismiss(completion: @escaping () -> Void) {
completion()
}

View file

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

View file

@ -15,6 +15,10 @@ import ChatPresentationInterfaceState
import PagerComponent
import FeaturedStickersScreen
import TelegramNotices
import CounterControllerTitleView
import GlassBackgroundComponent
import GlassBarButtonComponent
import BundleIconComponent
final class StickerAttachmentScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -49,8 +53,10 @@ final class StickerAttachmentScreenComponent: Component {
final class View: UIView, UIScrollViewDelegate {
fileprivate let keyboardView: ComponentView<Empty>
private let keyboardClippingView: KeyboardClippingView
private let panelBackgroundView: GlassBackgroundView
private let panelClippingView: UIView
private let panelHostView: PagerExternalTopPanelContainer
private let panelSeparatorView: UIView
private let cancelButton: ComponentView<Empty>
private var component: StickerAttachmentScreenComponent?
private(set) weak var state: EmptyComponentState?
@ -109,14 +115,17 @@ final class StickerAttachmentScreenComponent: Component {
override init(frame: CGRect) {
self.keyboardView = ComponentView<Empty>()
self.keyboardClippingView = KeyboardClippingView()
self.panelBackgroundView = GlassBackgroundView()
self.panelClippingView = UIView()
self.panelHostView = PagerExternalTopPanelContainer()
self.panelSeparatorView = UIView()
self.cancelButton = ComponentView<Empty>()
super.init(frame: frame)
self.addSubview(self.keyboardClippingView)
self.addSubview(self.panelSeparatorView)
self.addSubview(self.panelHostView)
self.addSubview(self.panelBackgroundView)
self.panelBackgroundView.contentView.addSubview(self.panelClippingView)
self.panelClippingView.addSubview(self.panelHostView)
self.interaction = ChatEntityKeyboardInputNode.Interaction(
sendSticker: { [weak self] file, _, _, _, _, _, _, _, _ in
@ -200,10 +209,7 @@ final class StickerAttachmentScreenComponent: Component {
(self.environment?.controller() as? StickerAttachmentScreen)?.dismiss(animated: true)
}
func updateContent() {
guard let component = self.component else {
return
}
func updateContent(component: StickerAttachmentScreenComponent) {
self.emojiContent?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self] groupId, item, _, _, _, _ in
guard let self, let component = self.component else {
@ -599,7 +605,7 @@ final class StickerAttachmentScreenComponent: Component {
externalBackground: nil,
externalExpansionView: nil,
customContentView: nil,
useOpaqueTheme: false,
useOpaqueTheme: true,
hideBackground: true,
stateContext: nil,
addImage: nil
@ -872,7 +878,7 @@ final class StickerAttachmentScreenComponent: Component {
externalBackground: nil,
externalExpansionView: nil,
customContentView: nil,
useOpaqueTheme: false,
useOpaqueTheme: true,
hideBackground: true,
stateContext: nil,
addImage: nil
@ -884,7 +890,6 @@ final class StickerAttachmentScreenComponent: Component {
self.environment = environment
self.backgroundColor = environment.theme.list.plainBackgroundColor
self.panelSeparatorView.backgroundColor = .clear
if self.component == nil {
let data = combineLatest(
@ -933,10 +938,7 @@ final class StickerAttachmentScreenComponent: Component {
}
self.stickerContent = stickerContent
}
Queue.mainQueue().justDispatch {
self.updateContent()
self.state?.updated()
}
self.updateContent(component: component)
}))
}
@ -962,15 +964,15 @@ final class StickerAttachmentScreenComponent: Component {
theme: environment.theme,
strings: environment.strings,
isContentInFocus: true,
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0 + topInset, left: 0.0, bottom: 0.0, right: 0.0),
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
containerInsets: UIEdgeInsets(top: topPanelHeight + topInset - 11.0, left: 0.0, bottom: 0.0, right: 0.0),
topPanelInsets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0),
emojiContent: self.emojiContent,
stickerContent: self.stickerContent,
maskContent: nil,
gifContent: nil,
hasRecentGifs: false,
availableGifSearchEmojies: [],
defaultToEmojiTab: emojiContent != nil,
defaultToEmojiTab: self.emojiContent != nil,
externalTopPanelContainer: self.panelHostView,
externalBottomPanelContainer: nil,
externalTintMaskContainer: nil,
@ -993,6 +995,16 @@ final class StickerAttachmentScreenComponent: Component {
self.forceUpdate = true
self.searchVisible = searchVisible
self.state?.updated(transition: transition)
let transition: ComponentTransition = .easeInOut(duration: 0.2)
if let controller = self.environment?.controller() as? StickerAttachmentScreen {
if let titleView = controller.navigationItem.titleView {
transition.setAlpha(view: titleView, alpha: searchVisible ? 0.0 : 1.0)
}
if searchVisible {
controller.requestAttachmentMenuExpansion()
}
}
},
hideTopPanelUpdated: { _, _ in
},
@ -1053,28 +1065,57 @@ final class StickerAttachmentScreenComponent: Component {
self.keyboardClippingView.addSubview(keyboardComponentView)
}
// if panelBackgroundColor.alpha < 0.01 {
// self.keyboardClippingView.clipsToBounds = true
// } else {
self.keyboardClippingView.clipsToBounds = false
// }
self.keyboardClippingView.clipsToBounds = false
transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight - topInset)))
self.keyboardClippingView.hitEdgeInsets = UIEdgeInsets(top: -topPanelHeight - topInset, left: 0.0, bottom: 0.0, right: 0.0)
let panelBackgroundFrame = CGRect(origin: CGPoint(x: 12.0, y: topPanelHeight + topInset - 29.0), size: CGSize(width: availableSize.width - 24.0, height: 44.0))
self.panelClippingView.clipsToBounds = true
self.panelClippingView.layer.cornerRadius = panelBackgroundFrame.height * 0.5
transition.setFrame(view: self.panelClippingView, frame: CGRect(origin: .zero, size: panelBackgroundFrame.size))
self.panelBackgroundView.update(size: panelBackgroundFrame.size, cornerRadius: panelBackgroundFrame.size.height * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, isVisible: !self.searchVisible, transition: transition)
transition.setFrame(view: self.panelBackgroundView, frame: panelBackgroundFrame)
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight - topInset), size: keyboardSize))
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
let topPanelAlpha: CGFloat
if self.searchVisible || self.keyboardContentId == AnyHashable("gifs") {
topPanelAlpha = 0.0
} else {
topPanelAlpha = max(0.0, min(1.0, (self.topPanelScrollingOffset / 20.0)))
}
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: -12.0, y: 8.0 - UIScreenPixel), size: CGSize(width: keyboardSize.width, height: 0.0)))
}
transition.setAlpha(view: self.panelSeparatorView, alpha: topPanelAlpha)
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - UIScreenPixel), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
let barButtonSize = CGSize(width: 44.0, height: 44.0)
let cancelButtonSize = self.cancelButton.update(
transition: transition,
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: nil,
isDark: environment.theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: environment.theme.chat.inputPanel.panelControlColor
)
)),
action: { [weak self] _ in
guard let self else {
return
}
(self.environment?.controller() as? StickerAttachmentScreen)?.dismiss(animated: true)
}
)),
environment: {},
containerSize: barButtonSize
)
let cancelButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelButtonSize)
if let cancelButtonView = self.cancelButton.view {
if cancelButtonView.superview == nil {
self.addSubview(cancelButtonView)
}
transition.setBounds(view: cancelButtonView, bounds: CGRect(origin: .zero, size: cancelButtonFrame.size))
transition.setPosition(view: cancelButtonView, position: cancelButtonFrame.center)
transition.setAlpha(view: cancelButtonView, alpha: self.searchVisible ? 0.0 : 1.0)
transition.setScale(view: cancelButtonView, scale: self.searchVisible ? 0.001 : 1.0)
}
return availableSize
@ -1096,11 +1137,21 @@ final class StickerAttachmentScreen: ViewControllerComponentContainer, Attachmen
case emoji(EmojiPagerContentComponent)
}
enum Source: Equatable {
enum PollMode: Equatable {
case description
case quizAnswer
case option
}
case poll(PollMode)
}
private let context: AccountContext
private let mode: Mode
private let completion: (AnyMediaReference) -> Void
init(context: AccountContext, mode: Mode, completion: @escaping (AnyMediaReference) -> Void) {
init(context: AccountContext, mode: Mode, source: Source, completion: @escaping (AnyMediaReference) -> Void) {
self.context = context
self.mode = mode
self.completion = completion
@ -1109,9 +1160,43 @@ final class StickerAttachmentScreen: ViewControllerComponentContainer, Attachmen
context: context,
mode: mode,
completion: completion
), navigationBarAppearance: .default, theme: .default)
), navigationBarAppearance: .transparent, theme: .default)
self._hasGlassStyle = true
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
switch source {
case let .poll(pollMode):
//TODO:localize
let title: String
let subtitle: String
switch mode {
case .stickers:
title = "Sticker"
switch pollMode {
case .description:
subtitle = "Add sticker to the poll description"
case .quizAnswer:
subtitle = "Add sticker to the quiz explanation"
case .option:
subtitle = "Add sticker to this option"
}
case .emoji:
title = "Emoji"
switch pollMode {
case .description:
subtitle = "Add emoji to the poll description"
case .quizAnswer:
subtitle = "Add emoji to the quiz explanation"
case .option:
subtitle = "Add emoji to this option"
}
}
let titleView = CounterControllerTitleView(theme: presentationData.theme, verticalOffset: -2.0)
titleView.title = CounterControllerTitle(title: title, counter: subtitle)
self.navigationItem.titleView = titleView
}
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
}
required init(coder: NSCoder) {

View file

@ -115,8 +115,8 @@ private class MediaHeaderItemNode: ASDisplayNode {
let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0
let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 4.0 + minimizedTitleOffset), size: titleLayout.size)
let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 20.0), size: subtitleLayout.size)
let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 5.0 + minimizedTitleOffset), size: titleLayout.size)
let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 21.0), size: subtitleLayout.size)
transition.updateFrame(node: self.titleNode, frame: minimizedTitleFrame)
transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame)

View file

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

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))
}
var focusedTextInputIsMedia = false
if case .media = chatPresentationInterfaceState.inputMode {
focusedTextInputIsMedia = true
}
self.controllerInteraction.focusedTextInputIsMedia = focusedTextInputIsMedia
self.controllerInteraction.focusedPollAddOptionMessageId = chatPresentationInterfaceState.focusedPollAddOptionMessageId
let updateInputTextState = self.chatPresentationInterfaceState.interfaceState.effectiveInputState != chatPresentationInterfaceState.interfaceState.effectiveInputState
self.chatPresentationInterfaceState = chatPresentationInterfaceState

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: {
}, presentFiles: { [weak self, weak attachmentController] in
attachmentController?.dismiss(animated: true)
self?.presentICloudFileGallery()
self?.presentICloudFileGallery(documentTypes: ["public.mp3", "public.mpeg-4-audio", "public.aac-audio", "org.xiph.flac"])
}, presentDocumentScanner: nil, send: { [weak self] mediaReferences in
guard let self else {
return
@ -1154,7 +1154,7 @@ extension ChatControllerImpl {
})
}
func presentICloudFileGallery(editingMessage: Bool = false) {
func presentICloudFileGallery(editingMessage: Bool = false, documentTypes: [String] = ["public.item"]) {
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
@ -1167,7 +1167,7 @@ extension ChatControllerImpl {
let (accountPeer, limits, premiumLimits) = result
let isPremium = accountPeer?.isPremium ?? false
strongSelf.present(legacyICloudFilePicker(theme: strongSelf.presentationData.theme, completion: { [weak self] urls in
strongSelf.present(legacyICloudFilePicker(theme: strongSelf.presentationData.theme, documentTypes: documentTypes, completion: { [weak self] urls in
if let strongSelf = self, !urls.isEmpty {
var signals: [Signal<ICloudFileDescription?, NoError>] = []
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 {
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? {