[WIP] Polls
This commit is contained in:
parent
ce1685be40
commit
cbc5a49507
42 changed files with 4437 additions and 2537 deletions
|
|
@ -3001,6 +3001,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||||
} else {
|
} else {
|
||||||
match = false
|
match = false
|
||||||
}
|
}
|
||||||
|
case .createBot:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -2328,6 +2328,8 @@ public final class ChatListNode: ListViewImpl {
|
||||||
} else {
|
} else {
|
||||||
match = false
|
match = false
|
||||||
}
|
}
|
||||||
|
case .createBot:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -56,19 +56,21 @@ public extension CheckNodeTheme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CheckNodeContent {
|
public enum CheckNodeContent: Equatable {
|
||||||
case check
|
case check(isRectangle: Bool)
|
||||||
case counter(Int)
|
case counter(Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class CheckNodeParameters: NSObject {
|
private final class CheckNodeParameters: NSObject {
|
||||||
|
let isRectangle: Bool
|
||||||
let theme: CheckNodeTheme
|
let theme: CheckNodeTheme
|
||||||
let content: CheckNodeContent
|
let content: CheckNodeContent
|
||||||
let animationProgress: CGFloat
|
let animationProgress: CGFloat
|
||||||
let selected: Bool
|
let selected: Bool
|
||||||
let animatingOut: Bool
|
let animatingOut: Bool
|
||||||
|
|
||||||
init(theme: CheckNodeTheme, content: CheckNodeContent, animationProgress: CGFloat, selected: Bool, animatingOut: Bool) {
|
init(isRectangle: Bool, theme: CheckNodeTheme, content: CheckNodeContent, animationProgress: CGFloat, selected: Bool, animatingOut: Bool) {
|
||||||
|
self.isRectangle = isRectangle
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.content = content
|
self.content = content
|
||||||
self.animationProgress = animationProgress
|
self.animationProgress = animationProgress
|
||||||
|
|
@ -86,7 +88,7 @@ public class CheckNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(theme: CheckNodeTheme, content: CheckNodeContent = .check) {
|
public init(theme: CheckNodeTheme, content: CheckNodeContent = .check(isRectangle: false)) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.content = content
|
self.content = content
|
||||||
|
|
||||||
|
|
@ -161,7 +163,7 @@ public class CheckNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||||
return CheckNodeParameters(theme: self.theme, content: self.content, animationProgress: self.animationProgress, selected: self.selected, animatingOut: self.animatingOut)
|
return CheckNodeParameters(isRectangle: self.content == .check(isRectangle: true), theme: self.theme, content: self.content, animationProgress: self.animationProgress, selected: self.selected, animatingOut: self.animatingOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||||
|
|
@ -200,7 +202,7 @@ public class InteractiveCheckNode: CheckNode {
|
||||||
|
|
||||||
public var valueChanged: ((Bool) -> Void)?
|
public var valueChanged: ((Bool) -> Void)?
|
||||||
|
|
||||||
override public init(theme: CheckNodeTheme, content: CheckNodeContent = .check) {
|
override public init(theme: CheckNodeTheme, content: CheckNodeContent = .check(isRectangle: false)) {
|
||||||
self.buttonNode = HighlightTrackingButtonNode()
|
self.buttonNode = HighlightTrackingButtonNode()
|
||||||
|
|
||||||
super.init(theme: theme, content: content)
|
super.init(theme: theme, content: content)
|
||||||
|
|
@ -250,7 +252,7 @@ public class CheckLayer: CALayer {
|
||||||
|
|
||||||
public override init() {
|
public override init() {
|
||||||
self.theme = CheckNodeTheme(backgroundColor: .white, strokeColor: .blue, borderColor: .white, overlayBorder: false, hasInset: false, hasShadow: false)
|
self.theme = CheckNodeTheme(backgroundColor: .white, strokeColor: .blue, borderColor: .white, overlayBorder: false, hasInset: false, hasShadow: false)
|
||||||
self.content = .check
|
self.content = .check(isRectangle: false)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
|
@ -270,7 +272,7 @@ public class CheckLayer: CALayer {
|
||||||
self.isOpaque = false
|
self.isOpaque = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(theme: CheckNodeTheme, content: CheckNodeContent = .check) {
|
public init(theme: CheckNodeTheme, content: CheckNodeContent = .check(isRectangle: false)) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.content = content
|
self.content = content
|
||||||
|
|
||||||
|
|
@ -364,7 +366,7 @@ public class CheckLayer: CALayer {
|
||||||
CheckLayer.drawContents(
|
CheckLayer.drawContents(
|
||||||
context: context,
|
context: context,
|
||||||
size: size,
|
size: size,
|
||||||
parameters: CheckNodeParameters(theme: self.theme, content: self.content, animationProgress: self.animationProgress, selected: self.selected, animatingOut: self.animatingOut)
|
parameters: CheckNodeParameters(isRectangle: self.content == .check(isRectangle: true), theme: self.theme, content: self.content, animationProgress: self.animationProgress, selected: self.selected, animatingOut: self.animatingOut)
|
||||||
)
|
)
|
||||||
})?.cgImage
|
})?.cgImage
|
||||||
}
|
}
|
||||||
|
|
@ -414,13 +416,24 @@ public class CheckLayer: CALayer {
|
||||||
let fillProgress: CGFloat = parameters.animationProgress
|
let fillProgress: CGFloat = parameters.animationProgress
|
||||||
|
|
||||||
context.setFillColor(parameters.theme.backgroundColor.mixedWith(parameters.theme.borderColor, alpha: 1.0 - fillProgress).cgColor)
|
context.setFillColor(parameters.theme.backgroundColor.mixedWith(parameters.theme.borderColor, alpha: 1.0 - fillProgress).cgColor)
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
if parameters.isRectangle {
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 7.0).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
} else {
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
}
|
||||||
|
|
||||||
let innerDiameter: CGFloat = (fillProgress * 0.0) + (1.0 - fillProgress) * (size.width - borderWidth * 2.0)
|
let innerDiameter: CGFloat = (fillProgress * 0.0) + (1.0 - fillProgress) * (size.width - borderWidth * 2.0)
|
||||||
|
|
||||||
context.setBlendMode(.copy)
|
context.setBlendMode(.copy)
|
||||||
context.setFillColor(UIColor.clear.cgColor)
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: (size.width - innerDiameter) * 0.5, y: (size.height - innerDiameter) * 0.5), size: CGSize(width: innerDiameter, height: innerDiameter)))
|
if parameters.isRectangle {
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: (size.width - innerDiameter) * 0.5, y: (size.height - innerDiameter) * 0.5), size: CGSize(width: innerDiameter, height: innerDiameter)), cornerRadius: 6.0).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
} else {
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: (size.width - innerDiameter) * 0.5, y: (size.height - innerDiameter) * 0.5), size: CGSize(width: innerDiameter, height: innerDiameter)))
|
||||||
|
}
|
||||||
context.setBlendMode(.normal)
|
context.setBlendMode(.normal)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ public final class GridMessageSelectionLayer: CALayer {
|
||||||
public let checkLayer: CheckLayer
|
public let checkLayer: CheckLayer
|
||||||
|
|
||||||
public init(theme: CheckNodeTheme) {
|
public init(theme: CheckNodeTheme) {
|
||||||
self.checkLayer = CheckLayer(theme: theme, content: .check)
|
self.checkLayer = CheckLayer(theme: theme, content: .check(isRectangle: false))
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,12 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AssetsMode: Equatable {
|
public enum AssetsMode: Equatable {
|
||||||
|
public enum PollMode: Equatable {
|
||||||
|
case description
|
||||||
|
case quizAnswer
|
||||||
|
case option
|
||||||
|
}
|
||||||
|
|
||||||
case `default`
|
case `default`
|
||||||
case wallpaper
|
case wallpaper
|
||||||
case story
|
case story
|
||||||
|
|
@ -174,6 +180,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||||
case cover
|
case cover
|
||||||
case createSticker
|
case createSticker
|
||||||
case createAvatar
|
case createAvatar
|
||||||
|
case poll(PollMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
case assets(PHAssetCollection?, AssetsMode)
|
case assets(PHAssetCollection?, AssetsMode)
|
||||||
|
|
@ -2037,6 +2044,17 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||||
self.titleView.title = presentationData.strings.MediaPicker_AddImage
|
self.titleView.title = presentationData.strings.MediaPicker_AddImage
|
||||||
case .cover:
|
case .cover:
|
||||||
self.titleView.title = presentationData.strings.MediaPicker_ChooseCover
|
self.titleView.title = presentationData.strings.MediaPicker_ChooseCover
|
||||||
|
case let .poll(pollMode):
|
||||||
|
self.titleView.title = presentationData.strings.MediaPicker_Recents
|
||||||
|
switch pollMode {
|
||||||
|
case .description:
|
||||||
|
self.titleView.subtitle = "Add media to the poll description"
|
||||||
|
case .quizAnswer:
|
||||||
|
self.titleView.subtitle = "Add media to the quiz explanation"
|
||||||
|
case .option:
|
||||||
|
self.titleView.subtitle = "Add media to this option"
|
||||||
|
}
|
||||||
|
self.titleView.isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -139,7 +139,7 @@ public final class ShareStartAtTimestampNode: HighlightTrackingButtonNode {
|
||||||
self.titleTextColor = titleTextColor
|
self.titleTextColor = titleTextColor
|
||||||
self.checkNodeTheme = checkNodeTheme
|
self.checkNodeTheme = checkNodeTheme
|
||||||
|
|
||||||
self.checkNode = CheckNode(theme: checkNodeTheme, content: .check)
|
self.checkNode = CheckNode(theme: checkNodeTheme, content: .check(isRectangle: false))
|
||||||
self.checkNode.isUserInteractionEnabled = false
|
self.checkNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.titleTextNode = TextNode()
|
self.titleTextNode = TextNode()
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
|
||||||
let diminsionsSize = dimensions.cgSizeValue
|
let diminsionsSize = dimensions.cgSizeValue
|
||||||
return .single(.preparing(false))
|
return .single(.preparing(false))
|
||||||
|> then(
|
|> then(
|
||||||
standaloneUploadedImage(postbox: postbox, network: network, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(diminsionsSize.width), height: Int32(diminsionsSize.height)))
|
standaloneUploadedImage(postbox: postbox, network: network, peerId: peerId, text: "", source: .data(imageData), dimensions: PixelDimensions(width: Int32(diminsionsSize.width), height: Int32(diminsionsSize.height)))
|
||||||
|> mapError { _ -> PreparedShareItemError in
|
|> mapError { _ -> PreparedShareItemError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
|
||||||
if let scaledImage = scalePhotoImage(image, dimensions: dimensions), let imageData = scaledImage.jpegData(compressionQuality: 0.52) {
|
if let scaledImage = scalePhotoImage(image, dimensions: dimensions), let imageData = scaledImage.jpegData(compressionQuality: 0.52) {
|
||||||
return .single(.preparing(false))
|
return .single(.preparing(false))
|
||||||
|> then(
|
|> then(
|
||||||
standaloneUploadedImage(postbox: postbox, network: network, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)))
|
standaloneUploadedImage(postbox: postbox, network: network, peerId: peerId, text: "", source: .data(imageData), dimensions: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)))
|
||||||
|> mapError { _ -> PreparedShareItemError in
|
|> mapError { _ -> PreparedShareItemError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|
|
@ -265,7 +265,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
|
||||||
let imageData = scaledImage.jpegData(compressionQuality: 0.54)!
|
let imageData = scaledImage.jpegData(compressionQuality: 0.54)!
|
||||||
return .single(.preparing(false))
|
return .single(.preparing(false))
|
||||||
|> then(
|
|> then(
|
||||||
standaloneUploadedImage(postbox: postbox, network: network, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(scaledImage.size.width), height: Int32(scaledImage.size.height)))
|
standaloneUploadedImage(postbox: postbox, network: network, peerId: peerId, text: "", source: .data(imageData), dimensions: PixelDimensions(width: Int32(scaledImage.size.width), height: Int32(scaledImage.size.height)))
|
||||||
|> mapError { _ -> PreparedShareItemError in
|
|> mapError { _ -> PreparedShareItemError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ private func uploadedThumbnail(network: Network, postbox: Postbox, data: Data) -
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func standaloneUploadedImage(postbox: Postbox, network: Network, peerId: PeerId, text: String, data: Data, thumbnailData: Data? = nil, dimensions: PixelDimensions) -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> {
|
public func standaloneUploadedImage(postbox: Postbox, network: Network, peerId: PeerId, text: String, source: MultipartUploadSource, thumbnailData: Data? = nil, dimensions: PixelDimensions) -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> {
|
||||||
return multipartUpload(network: network, postbox: postbox, source: .data(data), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|
return multipartUpload(network: network, postbox: postbox, source: source, encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|
||||||
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|
||||||
|> mapToSignal { next -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
|
|> mapToSignal { next -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
|
||||||
switch next {
|
switch next {
|
||||||
|
|
@ -103,7 +103,7 @@ public func standaloneUploadedImage(postbox: Postbox, network: Network, peerId:
|
||||||
switch result {
|
switch result {
|
||||||
case let .encryptedFile(encryptedFileData):
|
case let .encryptedFile(encryptedFileData):
|
||||||
let (id, accessHash, size, dcId) = (encryptedFileData.id, encryptedFileData.accessHash, encryptedFileData.size, encryptedFileData.dcId)
|
let (id, accessHash, size, dcId) = (encryptedFileData.id, encryptedFileData.accessHash, encryptedFileData.size, encryptedFileData.dcId)
|
||||||
return .single(.result(.media(.standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)), representations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: SecretFileMediaResource(fileId: id, accessHash: accessHash, containerSize: size, decryptedSize: Int64(data.count), datacenterId: Int(dcId), key: key), progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])))))
|
return .single(.result(.media(.standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)), representations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: SecretFileMediaResource(fileId: id, accessHash: accessHash, containerSize: size, decryptedSize: size, datacenterId: Int(dcId), key: key), progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])))))
|
||||||
case .encryptedFileEmpty:
|
case .encryptedFileEmpty:
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,13 @@ public enum TelegramMediaPollKind: Equatable, PostboxCoding {
|
||||||
encoder.encodeInt32(multipleAnswers ? 1 : 0, forKey: "m")
|
encoder.encodeInt32(multipleAnswers ? 1 : 0, forKey: "m")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var multipleAnswers: Bool {
|
||||||
|
switch self {
|
||||||
|
case let .poll(multipleAnswers), let .quiz(multipleAnswers):
|
||||||
|
return multipleAnswers
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class TelegramMediaPoll: Media, Equatable {
|
public final class TelegramMediaPoll: Media, Equatable {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ public enum PresentationResourceKey: Int32 {
|
||||||
case itemListDeleteIcon
|
case itemListDeleteIcon
|
||||||
case itemListDeleteIndicatorIcon
|
case itemListDeleteIndicatorIcon
|
||||||
case itemListReorderIndicatorIcon
|
case itemListReorderIndicatorIcon
|
||||||
|
case itemListAddIndicatorIcon
|
||||||
case itemListLinkIcon
|
case itemListLinkIcon
|
||||||
case itemListAddPersonIcon
|
case itemListAddPersonIcon
|
||||||
case itemListCreateGroupIcon
|
case itemListCreateGroupIcon
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,20 @@ public struct PresentationResourcesItemList {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func itemListAddIndicatorIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
|
return theme.image(PresentationResourceKey.itemListAddIndicatorIcon.rawValue, { theme in
|
||||||
|
return generateImage(CGSize(width: 17.0, height: 15.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(theme.list.itemBlocksSeparatorColor.cgColor)
|
||||||
|
|
||||||
|
let lineHeight = 1.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.fillPath()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public static func linkIcon(_ theme: PresentationTheme) -> UIImage? {
|
public static func linkIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.itemListLinkIcon.rawValue, { theme in
|
return theme.image(PresentationResourceKey.itemListLinkIcon.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: theme.list.itemAccentColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: theme.list.itemAccentColor)
|
||||||
|
|
|
||||||
|
|
@ -1780,6 +1780,8 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||||
} else {
|
} else {
|
||||||
attributedString = NSAttributedString(string: strings.Notification_CopyProtection_Request(peerName).string, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: strings.Notification_CopyProtection_Request(peerName).string, font: titleFont, textColor: primaryTextColor)
|
||||||
}
|
}
|
||||||
|
case .managedBotCreated:
|
||||||
|
attributedString = nil
|
||||||
case .unknown:
|
case .unknown:
|
||||||
attributedString = nil
|
attributedString = nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ swift_library(
|
||||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||||
"//submodules/ActionSheetPeerItem:ActionSheetPeerItem",
|
"//submodules/ActionSheetPeerItem:ActionSheetPeerItem",
|
||||||
"//submodules/ComposePollUI:ComposePollUI",
|
"//submodules/TelegramUI/Components/ComposePollScreen",
|
||||||
"//submodules/AlertUI:AlertUI",
|
"//submodules/AlertUI:AlertUI",
|
||||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
"//submodules/TouchDownGesture:TouchDownGesture",
|
"//submodules/TouchDownGesture:TouchDownGesture",
|
||||||
|
|
|
||||||
|
|
@ -524,48 +524,53 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
||||||
return messages
|
return messages
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
globalMusic = .single(nil)
|
|
||||||
|> then(
|
if let data = context.currentAppConfiguration.with({ $0 }).data, let searchBot = data["music_search_username"] as? String, !searchBot.isEmpty {
|
||||||
context.engine.peers.resolvePeerByName(name: "lybot", referrer: nil)
|
globalMusic = .single(nil)
|
||||||
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
|
|> then(
|
||||||
guard case let .result(result) = result else {
|
context.engine.peers.resolvePeerByName(name: searchBot, referrer: nil)
|
||||||
return .complete()
|
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
|
||||||
|
guard case let .result(result) = result else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
return .single(result)
|
||||||
}
|
}
|
||||||
return .single(result)
|
|> mapToSignal { peer -> Signal<ChatContextResultCollection?, NoError> in
|
||||||
}
|
guard let peer = peer else {
|
||||||
|> mapToSignal { peer -> Signal<ChatContextResultCollection?, NoError> in
|
return .single(nil)
|
||||||
guard let peer = peer else {
|
}
|
||||||
return .single(nil)
|
return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: query, offset: "")
|
||||||
}
|
|> map { results -> ChatContextResultCollection? in
|
||||||
return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: query, offset: "")
|
return results?.results
|
||||||
|> map { results -> ChatContextResultCollection? in
|
}
|
||||||
return results?.results
|
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
|
||||||
}
|
return .single(nil)
|
||||||
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
|
|
||||||
return .single(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|> map { contextResult in
|
|
||||||
guard let results = contextResult?.results else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
let peerId = context.account.peerId
|
|
||||||
var messages: [Message] = []
|
|
||||||
let peers = SimpleDictionary<PeerId, Peer>()
|
|
||||||
for result in results {
|
|
||||||
switch result {
|
|
||||||
case let .internalReference(internalReference):
|
|
||||||
if let file = internalReference.file {
|
|
||||||
let stableId = UInt32(clamping: file.fileId.id % Int64(Int32.max))
|
|
||||||
messages.append(Message(stableId: stableId, stableVersion: 0, id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [.music], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return messages
|
|> map { contextResult in
|
||||||
}
|
guard let results = contextResult?.results else {
|
||||||
)
|
return []
|
||||||
|
}
|
||||||
|
let peerId = context.account.peerId
|
||||||
|
var messages: [Message] = []
|
||||||
|
let peers = SimpleDictionary<PeerId, Peer>()
|
||||||
|
for result in results {
|
||||||
|
switch result {
|
||||||
|
case let .internalReference(internalReference):
|
||||||
|
if let file = internalReference.file {
|
||||||
|
let stableId = UInt32(clamping: file.fileId.id % Int64(Int32.max))
|
||||||
|
messages.append(Message(stableId: stableId, stableVersion: 0, id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [.music], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
globalMusic = .single(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateActivity(true)
|
updateActivity(true)
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||||
var hasSeparateCommentsButton = false
|
var hasSeparateCommentsButton = false
|
||||||
|
|
||||||
var addedPriceInfo = false
|
var addedPriceInfo = false
|
||||||
|
var addedPollMedia = false
|
||||||
|
|
||||||
outer: for (message, itemAttributes) in item.content {
|
outer: for (message, itemAttributes) in item.content {
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
|
|
@ -283,7 +284,22 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||||
result.removeAll()
|
result.removeAll()
|
||||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
return (result, false, true)
|
return (result, false, true)
|
||||||
} else if let _ = media as? TelegramMediaPoll {
|
} else if let poll = media as? TelegramMediaPoll {
|
||||||
|
if let attachedMedia = poll.attachedMedia {
|
||||||
|
if let _ = attachedMedia as? TelegramMediaImage {
|
||||||
|
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
||||||
|
} else if let file = attachedMedia as? TelegramMediaFile {
|
||||||
|
let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil)
|
||||||
|
if isVideo {
|
||||||
|
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
||||||
|
} else {
|
||||||
|
result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
|
}
|
||||||
|
} else if let _ = attachedMedia as? TelegramMediaMap {
|
||||||
|
result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
||||||
|
}
|
||||||
|
addedPollMedia = true
|
||||||
|
}
|
||||||
result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
needReactions = false
|
needReactions = false
|
||||||
} else if let _ = media as? TelegramMediaTodo {
|
} else if let _ = media as? TelegramMediaTodo {
|
||||||
|
|
@ -318,10 +334,12 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||||
isMediaInverted = updatingMedia.invertMediaAttribute != nil
|
isMediaInverted = updatingMedia.invertMediaAttribute != nil
|
||||||
} else if let _ = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) {
|
} else if let _ = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) {
|
||||||
isMediaInverted = true
|
isMediaInverted = true
|
||||||
|
} else if let _ = message.media.first(where: { $0 is TelegramMediaPoll }) {
|
||||||
|
isMediaInverted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if isMediaInverted {
|
if isMediaInverted {
|
||||||
result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: addedPriceInfo ? 1 : 0)
|
result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: addedPriceInfo || addedPollMedia ? 1 : 0)
|
||||||
} else {
|
} else {
|
||||||
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
|
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
|
||||||
needReactions = false
|
needReactions = false
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,8 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
for media in item.message.media {
|
for media in item.message.media {
|
||||||
if let telegramFile = media as? TelegramMediaFile {
|
if let telegramFile = media as? TelegramMediaFile {
|
||||||
selectedFile = telegramFile
|
selectedFile = telegramFile
|
||||||
|
} else if let poll = media as? TelegramMediaPoll, let telegramFile = poll.attachedMedia as? TelegramMediaFile {
|
||||||
|
selectedFile = telegramFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let updatingMedia = item.attributes.updatingMedia, case let .update(media) = updatingMedia.media, let file = media.media as? TelegramMediaFile {
|
if let updatingMedia = item.attributes.updatingMedia, case let .update(media) = updatingMedia.media, let file = media.media as? TelegramMediaFile {
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
if item.presentationData.isPreview {
|
if item.presentationData.isPreview {
|
||||||
automaticDownload = .full
|
automaticDownload = .full
|
||||||
}
|
}
|
||||||
|
} else if let poll = media as? TelegramMediaPoll, let image = poll.attachedMedia as? TelegramMediaImage {
|
||||||
|
selectedMedia = image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -526,7 +526,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||||
|
|
||||||
return { context, presentationData, message, poll, option, translation, optionResult, constrainedWidth in
|
return { context, presentationData, message, poll, option, translation, optionResult, constrainedWidth in
|
||||||
let leftInset: CGFloat = 50.0
|
let leftInset: CGFloat = 50.0
|
||||||
let rightInset: CGFloat = 12.0
|
let rightInset: CGFloat = 10.0
|
||||||
|
|
||||||
let incoming = message.effectivelyIncoming(context.account.peerId)
|
let incoming = message.effectivelyIncoming(context.account.peerId)
|
||||||
|
|
||||||
|
|
@ -559,7 +559,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||||
|
|
||||||
let shouldHaveRadioNode = optionResult == nil
|
let shouldHaveRadioNode = optionResult == nil
|
||||||
let isSelectable: Bool
|
let isSelectable: Bool
|
||||||
if shouldHaveRadioNode, case .poll(multipleAnswers: true) = poll.kind, !Namespaces.Message.allNonRegular.contains(message.id.namespace) {
|
if shouldHaveRadioNode, poll.kind.multipleAnswers, !Namespaces.Message.allNonRegular.contains(message.id.namespace) {
|
||||||
isSelectable = true
|
isSelectable = true
|
||||||
} else {
|
} else {
|
||||||
isSelectable = false
|
isSelectable = false
|
||||||
|
|
@ -617,7 +617,13 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||||
fillColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.bar : presentationData.theme.theme.chat.message.outgoing.polls.bar
|
fillColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.bar : presentationData.theme.theme.chat.message.outgoing.polls.bar
|
||||||
}
|
}
|
||||||
context.setFillColor(fillColor.cgColor)
|
context.setFillColor(fillColor.cgColor)
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
if poll.kind.multipleAnswers {
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 4.0).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
} else {
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
}
|
||||||
|
|
||||||
let strokeColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.barIconForeground : presentationData.theme.theme.chat.message.outgoing.polls.barIconForeground
|
let strokeColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.barIconForeground : presentationData.theme.theme.chat.message.outgoing.polls.barIconForeground
|
||||||
if strokeColor.alpha.isZero {
|
if strokeColor.alpha.isZero {
|
||||||
|
|
@ -689,9 +695,9 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||||
))
|
))
|
||||||
let titleNodeFrame: CGRect
|
let titleNodeFrame: CGRect
|
||||||
if titleLayout.hasRTL {
|
if titleLayout.hasRTL {
|
||||||
titleNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - titleLayout.size.width, y: 11.0), size: titleLayout.size)
|
titleNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - titleLayout.size.width, y: 12.0), size: titleLayout.size)
|
||||||
} else {
|
} else {
|
||||||
titleNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size)
|
titleNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: titleLayout.size)
|
||||||
}
|
}
|
||||||
if node.titleNode !== titleNode {
|
if node.titleNode !== titleNode {
|
||||||
node.titleNode = titleNode
|
node.titleNode = titleNode
|
||||||
|
|
@ -718,7 +724,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
let radioSize: CGFloat = 22.0
|
let radioSize: CGFloat = 22.0
|
||||||
radioNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: CGSize(width: radioSize, height: radioSize))
|
radioNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: CGSize(width: radioSize, height: radioSize))
|
||||||
radioNode.update(isRectangle: poll.kind == .poll(multipleAnswers: true), staticColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioButton : presentationData.theme.theme.chat.message.outgoing.polls.radioButton, animatedColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioProgress : presentationData.theme.theme.chat.message.outgoing.polls.radioProgress, fillColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.bar : presentationData.theme.theme.chat.message.outgoing.polls.bar, foregroundColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.barIconForeground : presentationData.theme.theme.chat.message.outgoing.polls.barIconForeground, isSelectable: isSelectable, isAnimating: inProgress)
|
radioNode.update(isRectangle: poll.kind.multipleAnswers, staticColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioButton : presentationData.theme.theme.chat.message.outgoing.polls.radioButton, animatedColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioProgress : presentationData.theme.theme.chat.message.outgoing.polls.radioProgress, fillColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.bar : presentationData.theme.theme.chat.message.outgoing.polls.bar, foregroundColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.barIconForeground : presentationData.theme.theme.chat.message.outgoing.polls.barIconForeground, isSelectable: isSelectable, isAnimating: inProgress)
|
||||||
} else if let radioNode = node.radioNode {
|
} else if let radioNode = node.radioNode {
|
||||||
node.radioNode = nil
|
node.radioNode = nil
|
||||||
if animated {
|
if animated {
|
||||||
|
|
@ -754,7 +760,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||||
node.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: contentHeight + UIScreenPixel))
|
node.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: contentHeight + UIScreenPixel))
|
||||||
}
|
}
|
||||||
node.separatorNode.backgroundColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.separator : presentationData.theme.theme.chat.message.outgoing.polls.separator
|
node.separatorNode.backgroundColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.separator : presentationData.theme.theme.chat.message.outgoing.polls.separator
|
||||||
node.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentHeight - UIScreenPixel), size: CGSize(width: width - leftInset, height: UIScreenPixel))
|
node.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentHeight - UIScreenPixel), size: CGSize(width: width - leftInset - rightInset, height: UIScreenPixel))
|
||||||
|
|
||||||
if node.resultBarNode.image == nil || updatedResultIcon {
|
if node.resultBarNode.image == nil || updatedResultIcon {
|
||||||
var isQuiz = false
|
var isQuiz = false
|
||||||
|
|
@ -1270,6 +1276,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
}
|
}
|
||||||
let (votersLayout, votersApply) = makeVotersLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: votersString ?? "", font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
let (votersLayout, votersApply) = makeVotersLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: votersString ?? "", font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let (buttonSubmitInactiveTextLayout, buttonSubmitInactiveTextApply) = makeSubmitInactiveTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.MessagePoll_SubmitVote, font: Font.regular(17.0), textColor: messageTheme.accentControlDisabledColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
let (buttonSubmitInactiveTextLayout, buttonSubmitInactiveTextApply) = makeSubmitInactiveTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.MessagePoll_SubmitVote, font: Font.regular(17.0), textColor: messageTheme.accentControlDisabledColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
||||||
let (buttonSubmitActiveTextLayout, buttonSubmitActiveTextApply) = makeSubmitActiveTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.MessagePoll_SubmitVote, font: Font.regular(17.0), textColor: messageTheme.polls.bar), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
let (buttonSubmitActiveTextLayout, buttonSubmitActiveTextApply) = makeSubmitActiveTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.MessagePoll_SubmitVote, font: Font.regular(17.0), textColor: messageTheme.polls.bar), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
||||||
let (buttonViewResultsTextLayout, buttonViewResultsTextApply) = makeViewResultsTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.MessagePoll_ViewResults, font: Font.regular(17.0), textColor: messageTheme.polls.bar), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
let (buttonViewResultsTextLayout, buttonViewResultsTextApply) = makeViewResultsTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.MessagePoll_ViewResults, font: Font.regular(17.0), textColor: messageTheme.polls.bar), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
||||||
|
|
|
||||||
|
|
@ -225,6 +225,9 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
|
||||||
title = strings.Conversation_SetReminder_Title
|
title = strings.Conversation_SetReminder_Title
|
||||||
case .format:
|
case .format:
|
||||||
title = strings.Conversation_FormatDate_Title
|
title = strings.Conversation_FormatDate_Title
|
||||||
|
case .poll:
|
||||||
|
//TODO:localize
|
||||||
|
title = "Deadline"
|
||||||
}
|
}
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
|
|
@ -384,6 +387,8 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
|
||||||
var repeatValueFrame = CGRect()
|
var repeatValueFrame = CGRect()
|
||||||
if case .format = component.mode {
|
if case .format = component.mode {
|
||||||
contentHeight += 8.0
|
contentHeight += 8.0
|
||||||
|
} else if case .poll = component.mode {
|
||||||
|
contentHeight += 8.0
|
||||||
} else {
|
} else {
|
||||||
transition.setFrame(layer: self.bottomSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel)))
|
transition.setFrame(layer: self.bottomSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel)))
|
||||||
self.bottomSeparator.backgroundColor = environment.theme.list.itemBlocksSeparatorColor.cgColor
|
self.bottomSeparator.backgroundColor = environment.theme.list.itemBlocksSeparatorColor.cgColor
|
||||||
|
|
@ -491,6 +496,9 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
|
||||||
}
|
}
|
||||||
case .format:
|
case .format:
|
||||||
buttonTitle = component.currentTime != nil ? strings.Conversation_FormatDate_EditDate : strings.Conversation_FormatDate_AddDate
|
buttonTitle = component.currentTime != nil ? strings.Conversation_FormatDate_EditDate : strings.Conversation_FormatDate_AddDate
|
||||||
|
case .poll:
|
||||||
|
//TODO:localize
|
||||||
|
buttonTitle = "Set Deadline"
|
||||||
}
|
}
|
||||||
|
|
||||||
let buttonSideInset: CGFloat = 30.0
|
let buttonSideInset: CGFloat = 30.0
|
||||||
|
|
@ -930,6 +938,7 @@ public class ChatScheduleTimeScreen: ViewControllerComponentContainer {
|
||||||
case scheduledMessages(sendWhenOnlineAvailable: Bool)
|
case scheduledMessages(sendWhenOnlineAvailable: Bool)
|
||||||
case reminders
|
case reminders
|
||||||
case format
|
case format
|
||||||
|
case poll
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Result {
|
public struct Result {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
swift_library(
|
swift_library(
|
||||||
name = "ComposePollUI",
|
name = "ComposePollScreen",
|
||||||
module_name = "ComposePollUI",
|
module_name = "ComposePollScreen",
|
||||||
srcs = glob([
|
srcs = glob([
|
||||||
"Sources/**/*.swift",
|
"Sources/**/*.swift",
|
||||||
]),
|
]),
|
||||||
|
|
@ -21,7 +21,6 @@ swift_library(
|
||||||
"//submodules/PresentationDataUtils",
|
"//submodules/PresentationDataUtils",
|
||||||
"//submodules/TextFormat",
|
"//submodules/TextFormat",
|
||||||
"//submodules/ObjCRuntimeUtils",
|
"//submodules/ObjCRuntimeUtils",
|
||||||
"//submodules/AttachmentUI",
|
|
||||||
"//submodules/TextInputMenu",
|
"//submodules/TextInputMenu",
|
||||||
"//submodules/ComponentFlow",
|
"//submodules/ComponentFlow",
|
||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
|
@ -43,6 +42,17 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
|
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
|
||||||
"//submodules/TelegramUI/Components/ListComposePollOptionComponent",
|
"//submodules/TelegramUI/Components/ListComposePollOptionComponent",
|
||||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||||
|
"//submodules/LegacyComponents",
|
||||||
|
"//submodules/LegacyUI",
|
||||||
|
"//submodules/AttachmentUI",
|
||||||
|
"//submodules/MediaPickerUI",
|
||||||
|
"//submodules/TelegramUI/Components/LegacyCamera",
|
||||||
|
"//submodules/LegacyMediaPickerUI",
|
||||||
|
"//submodules/LocationUI",
|
||||||
|
"//submodules/TelegramUI/Components/AttachmentFileController",
|
||||||
|
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
||||||
|
"//submodules/TelegramUI/Components/ChatScheduleTimeController",
|
||||||
|
"//submodules/ContextUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,101 @@
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import AccountContext
|
||||||
|
import TelegramCore
|
||||||
|
import Postbox
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramPresentationData
|
||||||
|
import LegacyComponents
|
||||||
|
import LegacyUI
|
||||||
|
import AttachmentUI
|
||||||
|
import MediaPickerUI
|
||||||
|
import LegacyCamera
|
||||||
|
import LegacyMediaPickerUI
|
||||||
|
import LocationUI
|
||||||
|
import AttachmentFileController
|
||||||
|
import ChatEntityKeyboardInputNode
|
||||||
|
|
||||||
|
public func presentPollAttachmentScreen(
|
||||||
|
context: AccountContext,
|
||||||
|
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||||
|
availableButtons: [AttachmentButtonType],
|
||||||
|
present: @escaping (ViewController) -> Void,
|
||||||
|
completion: @escaping (AnyMediaReference) -> Void
|
||||||
|
) {
|
||||||
|
let attachmentController = AttachmentController(
|
||||||
|
context: context,
|
||||||
|
updatedPresentationData: updatedPresentationData,
|
||||||
|
style: .glass,
|
||||||
|
chatLocation: nil,
|
||||||
|
isScheduledMessages: false,
|
||||||
|
buttons: availableButtons,
|
||||||
|
initialButton: .gallery,
|
||||||
|
makeEntityInputView: {
|
||||||
|
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 controller = MediaPickerScreenImpl(
|
||||||
|
context: context,
|
||||||
|
updatedPresentationData: updatedPresentationData,
|
||||||
|
style: .glass,
|
||||||
|
peer: nil,
|
||||||
|
threadTitle: nil,
|
||||||
|
chatLocation: nil,
|
||||||
|
enableMultiselection: false,
|
||||||
|
subject: .assets(nil, .poll(.option))
|
||||||
|
)
|
||||||
|
controller.getCaptionPanelView = {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
controller.legacyCompletion = { fromGallery, signals, silently, scheduleTime, parameters, getAnimatedTransitionSource, sendCompletion in
|
||||||
|
let _ = (legacyAssetPickerEnqueueMessages(context: context, account: context.account, signals: signals)
|
||||||
|
|> deliverOnMainQueue).start(next: { items in
|
||||||
|
if let item = items.first, case let .message(_, _, _, mediaReference, _, _, _, _, _, _) = item.message, let mediaReference {
|
||||||
|
completion(mediaReference)
|
||||||
|
sendCompletion()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
controllerCompletion(controller, controller.mediaPickerContext)
|
||||||
|
return true
|
||||||
|
case .file:
|
||||||
|
let controller = context.sharedContext.makeAttachmentFileController(context: context, updatedPresentationData: updatedPresentationData, 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: { mediaReference in
|
||||||
|
completion(mediaReference)
|
||||||
|
})
|
||||||
|
guard let controller = controller as? AttachmentFileControllerImpl else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
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
|
||||||
|
completion(.standalone(media: location))
|
||||||
|
})
|
||||||
|
controllerCompletion(controller, controller.mediaPickerContext)
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attachmentController.navigationPresentation = .flatModal
|
||||||
|
present(attachmentController)
|
||||||
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ swift_library(
|
||||||
"//submodules/ChatPresentationInterfaceState",
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
|
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
|
||||||
"//submodules/TelegramUI/Components/ListComposePollOptionComponent",
|
"//submodules/TelegramUI/Components/ListComposePollOptionComponent",
|
||||||
"//submodules/ComposePollUI",
|
"//submodules/TelegramUI/Components/ComposePollScreen",
|
||||||
"//submodules/Markdown",
|
"//submodules/Markdown",
|
||||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1623,8 +1623,8 @@ final class ComposeTodoScreenComponent: Component {
|
||||||
}
|
}
|
||||||
if let input = self.validatedInput() {
|
if let input = self.validatedInput() {
|
||||||
controller.completion(input)
|
controller.completion(input)
|
||||||
|
controller.dismiss()
|
||||||
}
|
}
|
||||||
controller.dismiss()
|
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ public final class GlassBarButtonComponent: Component {
|
||||||
|
|
||||||
transition.animateAlpha(view: glassBackgroundView, from: 0.0, to: 1.0)
|
transition.animateAlpha(view: glassBackgroundView, from: 0.0, to: 1.0)
|
||||||
}
|
}
|
||||||
glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom(style: .default, color: backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)) : .panel), isInteractive: true, isVisible: component.isVisible, transition: glassBackgroundTransition)
|
glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom(style: .default, color: backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)) : .panel), isInteractive: component.isEnabled, isVisible: component.isVisible, transition: glassBackgroundTransition)
|
||||||
glassBackgroundTransition.setFrame(view: glassBackgroundView, frame: bounds)
|
glassBackgroundTransition.setFrame(view: glassBackgroundView, frame: bounds)
|
||||||
} else if case .glass = component.state {
|
} else if case .glass = component.state {
|
||||||
let glassBackgroundView: GlassBackgroundView
|
let glassBackgroundView: GlassBackgroundView
|
||||||
|
|
@ -283,7 +283,7 @@ public final class GlassBarButtonComponent: Component {
|
||||||
|
|
||||||
transition.animateAlpha(view: glassBackgroundView, from: 0.0, to: 1.0)
|
transition.animateAlpha(view: glassBackgroundView, from: 0.0, to: 1.0)
|
||||||
}
|
}
|
||||||
glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .panel), isInteractive: true, isVisible: component.isVisible, transition: glassBackgroundTransition)
|
glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .panel), isInteractive: component.isEnabled, isVisible: component.isVisible, transition: glassBackgroundTransition)
|
||||||
glassBackgroundTransition.setFrame(view: glassBackgroundView, frame: bounds)
|
glassBackgroundTransition.setFrame(view: glassBackgroundView, frame: bounds)
|
||||||
} else if let glassBackgroundView = self.glassBackgroundView {
|
} else if let glassBackgroundView = self.glassBackgroundView {
|
||||||
self.glassBackgroundView = nil
|
self.glassBackgroundView = nil
|
||||||
|
|
|
||||||
|
|
@ -323,7 +323,7 @@ public final class ListActionItemComponent: Component {
|
||||||
if let current = self.checkLayer {
|
if let current = self.checkLayer {
|
||||||
checkLayer = current
|
checkLayer = current
|
||||||
} else {
|
} else {
|
||||||
checkLayer = CheckLayer(theme: CheckNodeTheme(theme: theme, style: .plain), content: .check)
|
checkLayer = CheckLayer(theme: CheckNodeTheme(theme: theme, style: .plain), content: .check(isRectangle: false))
|
||||||
self.checkLayer = checkLayer
|
self.checkLayer = checkLayer
|
||||||
self.layer.addSublayer(checkLayer)
|
self.layer.addSublayer(checkLayer)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,15 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/TextFieldComponent",
|
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||||
"//submodules/TelegramUI/Components/LottieComponent",
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
"//submodules/CheckNode",
|
"//submodules/CheckNode",
|
||||||
"//submodules/AccountContext",
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/TextFormat",
|
||||||
"//submodules/PresentationDataUtils",
|
"//submodules/PresentationDataUtils",
|
||||||
|
"//submodules/PhotoResources",
|
||||||
|
"//submodules/LocationResources",
|
||||||
|
"//submodules/SemanticStatusNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import UIKit
|
||||||
import Display
|
import Display
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import TelegramCore
|
||||||
import CheckNode
|
import CheckNode
|
||||||
import ListSectionComponent
|
import ListSectionComponent
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
|
@ -12,7 +13,13 @@ import MultilineTextComponent
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import LottieComponent
|
import LottieComponent
|
||||||
import PlainButtonComponent
|
import PlainButtonComponent
|
||||||
|
import BundleIconComponent
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
import PhotoResources
|
||||||
|
import LocationResources
|
||||||
|
import SemanticStatusNode
|
||||||
|
import EmojiTextAttachmentView
|
||||||
|
import TextFormat
|
||||||
|
|
||||||
public final class ListComposePollOptionComponent: Component {
|
public final class ListComposePollOptionComponent: Component {
|
||||||
public enum Style {
|
public enum Style {
|
||||||
|
|
@ -34,10 +41,12 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
|
|
||||||
public final class Selection: Equatable {
|
public final class Selection: Equatable {
|
||||||
public let isSelected: Bool
|
public let isSelected: Bool
|
||||||
|
public let isMultiSelection: Bool
|
||||||
public let toggle: () -> Void
|
public let toggle: () -> Void
|
||||||
|
|
||||||
public init(isSelected: Bool, toggle: @escaping () -> Void) {
|
public init(isSelected: Bool, isMultiSelection: Bool = false, toggle: @escaping () -> Void) {
|
||||||
self.isSelected = isSelected
|
self.isSelected = isSelected
|
||||||
|
self.isMultiSelection = isMultiSelection
|
||||||
self.toggle = toggle
|
self.toggle = toggle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +54,34 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
if lhs.isSelected != rhs.isSelected {
|
if lhs.isSelected != rhs.isSelected {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isMultiSelection != rhs.isMultiSelection {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class Attachment: Equatable {
|
||||||
|
public let media: AnyMediaReference?
|
||||||
|
public let progress: CGFloat?
|
||||||
|
public let alwaysDisplayAttachButton: Bool
|
||||||
|
|
||||||
|
public init(media: AnyMediaReference?, progress: CGFloat?, alwaysDisplayAttachButton: Bool) {
|
||||||
|
self.media = media
|
||||||
|
self.progress = progress
|
||||||
|
self.alwaysDisplayAttachButton = alwaysDisplayAttachButton
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Attachment, rhs: Attachment) -> Bool {
|
||||||
|
if lhs.media != rhs.media {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.progress != rhs.progress {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.alwaysDisplayAttachButton != rhs.alwaysDisplayAttachButton {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -83,8 +120,11 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
public let resetText: ResetText?
|
public let resetText: ResetText?
|
||||||
public let assumeIsEditing: Bool
|
public let assumeIsEditing: Bool
|
||||||
public let characterLimit: Int?
|
public let characterLimit: Int?
|
||||||
|
public let hasLeftInset: Bool
|
||||||
public let enableInlineAnimations: Bool
|
public let enableInlineAnimations: Bool
|
||||||
public let canReorder: Bool
|
public let canReorder: Bool
|
||||||
|
public let canAdd: Bool
|
||||||
|
public let attachment: Attachment?
|
||||||
public let emptyLineHandling: TextFieldComponent.EmptyLineHandling
|
public let emptyLineHandling: TextFieldComponent.EmptyLineHandling
|
||||||
public let returnKeyAction: (() -> Void)?
|
public let returnKeyAction: (() -> Void)?
|
||||||
public let backspaceKeyAction: (() -> Void)?
|
public let backspaceKeyAction: (() -> Void)?
|
||||||
|
|
@ -92,6 +132,7 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
public let inputMode: InputMode?
|
public let inputMode: InputMode?
|
||||||
public let alwaysDisplayInputModeSelector: Bool
|
public let alwaysDisplayInputModeSelector: Bool
|
||||||
public let toggleInputMode: (() -> Void)?
|
public let toggleInputMode: (() -> Void)?
|
||||||
|
public let attachAction: (() -> Void)?
|
||||||
public let deleteAction: (() -> Void)?
|
public let deleteAction: (() -> Void)?
|
||||||
public let paste: ((TextFieldComponent.PasteData) -> Void)?
|
public let paste: ((TextFieldComponent.PasteData) -> Void)?
|
||||||
public let tag: AnyObject?
|
public let tag: AnyObject?
|
||||||
|
|
@ -108,7 +149,10 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
assumeIsEditing: Bool = false,
|
assumeIsEditing: Bool = false,
|
||||||
characterLimit: Int,
|
characterLimit: Int,
|
||||||
enableInlineAnimations: Bool = true,
|
enableInlineAnimations: Bool = true,
|
||||||
|
hasLeftInset: Bool = false,
|
||||||
canReorder: Bool = false,
|
canReorder: Bool = false,
|
||||||
|
canAdd: Bool = false,
|
||||||
|
attachment: Attachment? = nil,
|
||||||
emptyLineHandling: TextFieldComponent.EmptyLineHandling,
|
emptyLineHandling: TextFieldComponent.EmptyLineHandling,
|
||||||
returnKeyAction: (() -> Void)?,
|
returnKeyAction: (() -> Void)?,
|
||||||
backspaceKeyAction: (() -> Void)?,
|
backspaceKeyAction: (() -> Void)?,
|
||||||
|
|
@ -116,6 +160,7 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
inputMode: InputMode?,
|
inputMode: InputMode?,
|
||||||
alwaysDisplayInputModeSelector: Bool = false,
|
alwaysDisplayInputModeSelector: Bool = false,
|
||||||
toggleInputMode: (() -> Void)?,
|
toggleInputMode: (() -> Void)?,
|
||||||
|
attachAction: (() -> Void)? = nil,
|
||||||
deleteAction: (() -> Void)? = nil,
|
deleteAction: (() -> Void)? = nil,
|
||||||
paste: ((TextFieldComponent.PasteData) -> Void)? = nil,
|
paste: ((TextFieldComponent.PasteData) -> Void)? = nil,
|
||||||
tag: AnyObject? = nil
|
tag: AnyObject? = nil
|
||||||
|
|
@ -131,7 +176,10 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
self.assumeIsEditing = assumeIsEditing
|
self.assumeIsEditing = assumeIsEditing
|
||||||
self.characterLimit = characterLimit
|
self.characterLimit = characterLimit
|
||||||
self.enableInlineAnimations = enableInlineAnimations
|
self.enableInlineAnimations = enableInlineAnimations
|
||||||
|
self.hasLeftInset = hasLeftInset
|
||||||
self.canReorder = canReorder
|
self.canReorder = canReorder
|
||||||
|
self.canAdd = canAdd
|
||||||
|
self.attachment = attachment
|
||||||
self.emptyLineHandling = emptyLineHandling
|
self.emptyLineHandling = emptyLineHandling
|
||||||
self.returnKeyAction = returnKeyAction
|
self.returnKeyAction = returnKeyAction
|
||||||
self.backspaceKeyAction = backspaceKeyAction
|
self.backspaceKeyAction = backspaceKeyAction
|
||||||
|
|
@ -139,6 +187,7 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
self.inputMode = inputMode
|
self.inputMode = inputMode
|
||||||
self.alwaysDisplayInputModeSelector = alwaysDisplayInputModeSelector
|
self.alwaysDisplayInputModeSelector = alwaysDisplayInputModeSelector
|
||||||
self.toggleInputMode = toggleInputMode
|
self.toggleInputMode = toggleInputMode
|
||||||
|
self.attachAction = attachAction
|
||||||
self.deleteAction = deleteAction
|
self.deleteAction = deleteAction
|
||||||
self.paste = paste
|
self.paste = paste
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
|
|
@ -178,9 +227,18 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
if lhs.enableInlineAnimations != rhs.enableInlineAnimations {
|
if lhs.enableInlineAnimations != rhs.enableInlineAnimations {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.hasLeftInset != rhs.hasLeftInset {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.canReorder != rhs.canReorder {
|
if lhs.canReorder != rhs.canReorder {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.canAdd != rhs.canAdd {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.attachment != rhs.attachment {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.emptyLineHandling != rhs.emptyLineHandling {
|
if lhs.emptyLineHandling != rhs.emptyLineHandling {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -202,6 +260,7 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
private final class CheckView: HighlightTrackingButton {
|
private final class CheckView: HighlightTrackingButton {
|
||||||
private var checkLayer: CheckLayer?
|
private var checkLayer: CheckLayer?
|
||||||
private var theme: PresentationTheme?
|
private var theme: PresentationTheme?
|
||||||
|
private var isRectangle = false
|
||||||
|
|
||||||
var action: (() -> Void)?
|
var action: (() -> Void)?
|
||||||
|
|
||||||
|
|
@ -251,22 +310,27 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
self.action?()
|
self.action?()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, theme: PresentationTheme, isSelected: Bool, transition: ComponentTransition) {
|
func update(size: CGSize, isRectangle: Bool, theme: PresentationTheme, isSelected: Bool, transition: ComponentTransition) {
|
||||||
let checkLayer: CheckLayer
|
let checkLayer: CheckLayer
|
||||||
if let current = self.checkLayer {
|
if let current = self.checkLayer {
|
||||||
checkLayer = current
|
checkLayer = current
|
||||||
} else {
|
} else {
|
||||||
checkLayer = CheckLayer(theme: CheckNodeTheme(theme: theme, style: .plain), content: .check)
|
checkLayer = CheckLayer(theme: CheckNodeTheme(theme: theme, style: .plain), content: .check(isRectangle: isRectangle))
|
||||||
self.checkLayer = checkLayer
|
self.checkLayer = checkLayer
|
||||||
self.layer.addSublayer(checkLayer)
|
self.layer.addSublayer(checkLayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.theme !== theme {
|
if self.theme !== theme {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
checkLayer.theme = CheckNodeTheme(theme: theme, style: .plain)
|
checkLayer.theme = CheckNodeTheme(theme: theme, style: .plain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.isRectangle != isRectangle {
|
||||||
|
self.isRectangle = isRectangle
|
||||||
|
checkLayer.content = .check(isRectangle: isRectangle)
|
||||||
|
}
|
||||||
|
|
||||||
checkLayer.frame = CGRect(origin: CGPoint(), size: size)
|
checkLayer.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
checkLayer.setSelected(isSelected, animated: !transition.animation.isImmediate)
|
checkLayer.setSelected(isSelected, animated: !transition.animation.isImmediate)
|
||||||
}
|
}
|
||||||
|
|
@ -385,6 +449,13 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
|
|
||||||
private var modeSelector: ComponentView<Empty>?
|
private var modeSelector: ComponentView<Empty>?
|
||||||
private var reorderIconView: UIImageView?
|
private var reorderIconView: UIImageView?
|
||||||
|
private var addIconView: UIImageView?
|
||||||
|
|
||||||
|
private var attachButton: ComponentView<Empty>?
|
||||||
|
private var imageNode: TransformImageNode?
|
||||||
|
private var statusNode: SemanticStatusNode?
|
||||||
|
private var animationLayer: InlineStickerItemLayer?
|
||||||
|
private let imageButton = HighlightTrackingButton()
|
||||||
|
|
||||||
private var checkView: CheckView?
|
private var checkView: CheckView?
|
||||||
|
|
||||||
|
|
@ -396,6 +467,8 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
|
|
||||||
private var customPlaceholder: ComponentView<Empty>?
|
private var customPlaceholder: ComponentView<Empty>?
|
||||||
|
|
||||||
|
private var appliedMedia: AnyMediaReference?
|
||||||
|
|
||||||
private var component: ListComposePollOptionComponent?
|
private var component: ListComposePollOptionComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
private var isUpdating: Bool = false
|
private var isUpdating: Bool = false
|
||||||
|
|
@ -430,6 +503,25 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
|
|
||||||
public override init(frame: CGRect) {
|
public override init(frame: CGRect) {
|
||||||
super.init(frame: CGRect())
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
self.imageButton.highligthedChanged = { [weak self] highlighted in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if highlighted {
|
||||||
|
self.imageNode?.layer.removeAnimation(forKey: "opacity")
|
||||||
|
self.imageNode?.alpha = 0.4
|
||||||
|
|
||||||
|
self.animationLayer?.removeAnimation(forKey: "opacity")
|
||||||
|
self.animationLayer?.opacity = 0.4
|
||||||
|
} else {
|
||||||
|
self.imageNode?.alpha = 1.0
|
||||||
|
self.imageNode?.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
|
|
||||||
|
self.animationLayer?.opacity = 1.0
|
||||||
|
self.animationLayer?.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init?(coder: NSCoder) {
|
required public init?(coder: NSCoder) {
|
||||||
|
|
@ -551,6 +643,13 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
self.component?.deleteAction?()
|
self.component?.deleteAction?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func imageButtonPressed() {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.attachAction?()
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: ListComposePollOptionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: ListComposePollOptionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
|
|
@ -570,6 +669,10 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
var rightInset: CGFloat = 16.0
|
var rightInset: CGFloat = 16.0
|
||||||
let modeSelectorSize = CGSize(width: 32.0, height: 32.0)
|
let modeSelectorSize = CGSize(width: 32.0, height: 32.0)
|
||||||
|
|
||||||
|
if component.hasLeftInset {
|
||||||
|
leftInset += 46.0
|
||||||
|
}
|
||||||
|
|
||||||
if component.selection != nil {
|
if component.selection != nil {
|
||||||
leftInset += 34.0
|
leftInset += 34.0
|
||||||
}
|
}
|
||||||
|
|
@ -578,8 +681,8 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
rightInset += 34.0
|
rightInset += 34.0
|
||||||
}
|
}
|
||||||
|
|
||||||
if component.canReorder {
|
if component.attachment != nil {
|
||||||
rightInset += 16.0
|
rightInset += 28.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let textFieldSize = self.textField.update(
|
let textFieldSize = self.textField.update(
|
||||||
|
|
@ -641,8 +744,89 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
)
|
)
|
||||||
|
|
||||||
let size = CGSize(width: availableSize.width, height: textFieldSize.height - 1.0)
|
let size = CGSize(width: availableSize.width, height: textFieldSize.height - 1.0)
|
||||||
let textFieldFrame = CGRect(origin: CGPoint(x: leftInset - 16.0 + self.revealOffset, y: 0.0), size: textFieldSize)
|
|
||||||
|
|
||||||
|
var hasReorderIcon = false
|
||||||
|
if component.canReorder, let externalState = component.externalState, externalState.hasText {
|
||||||
|
var reorderIconTransition = transition
|
||||||
|
let reorderIconView: UIImageView
|
||||||
|
if let current = self.reorderIconView {
|
||||||
|
reorderIconView = current
|
||||||
|
} else {
|
||||||
|
reorderIconTransition = reorderIconTransition.withAnimation(.none)
|
||||||
|
reorderIconView = UIImageView()
|
||||||
|
self.reorderIconView = reorderIconView
|
||||||
|
self.addSubview(reorderIconView)
|
||||||
|
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
transition.animateAlpha(view: reorderIconView, from: 0.0, to: 1.0)
|
||||||
|
transition.animateScale(view: reorderIconView, from: 0.001, to: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reorderIconView.image = PresentationResourcesItemList.itemListReorderIndicatorIcon(component.theme)
|
||||||
|
|
||||||
|
var reorderIconSize = CGSize()
|
||||||
|
if let icon = reorderIconView.image {
|
||||||
|
reorderIconSize = icon.size
|
||||||
|
}
|
||||||
|
|
||||||
|
let reorderIconFrame = CGRect(origin: CGPoint(x: 22.0 + self.revealOffset, y: floor((size.height - reorderIconSize.height) * 0.5)), size: reorderIconSize)
|
||||||
|
reorderIconTransition.setPosition(view: reorderIconView, position: reorderIconFrame.center)
|
||||||
|
reorderIconTransition.setBounds(view: reorderIconView, bounds: CGRect(origin: CGPoint(), size: reorderIconFrame.size))
|
||||||
|
|
||||||
|
hasReorderIcon = true
|
||||||
|
} else if let reorderIconView = self.reorderIconView {
|
||||||
|
self.reorderIconView = nil
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
alphaTransition.setAlpha(view: reorderIconView, alpha: 0.0, completion: { [weak reorderIconView] _ in
|
||||||
|
reorderIconView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
alphaTransition.setScale(view: reorderIconView, scale: 0.001)
|
||||||
|
} else {
|
||||||
|
reorderIconView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if component.canAdd, !hasReorderIcon {
|
||||||
|
var addIconTransition = transition
|
||||||
|
let addIconView: UIImageView
|
||||||
|
if let current = self.addIconView {
|
||||||
|
addIconView = current
|
||||||
|
} else {
|
||||||
|
addIconTransition = addIconTransition.withAnimation(.none)
|
||||||
|
addIconView = UIImageView()
|
||||||
|
self.addIconView = addIconView
|
||||||
|
self.addSubview(addIconView)
|
||||||
|
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
transition.animateAlpha(view: addIconView, from: 0.0, to: 1.0)
|
||||||
|
transition.animateScale(view: addIconView, from: 0.001, to: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addIconView.image = PresentationResourcesItemList.itemListAddIndicatorIcon(component.theme)
|
||||||
|
|
||||||
|
var addIconSize = CGSize()
|
||||||
|
if let icon = addIconView.image {
|
||||||
|
addIconSize = icon.size
|
||||||
|
}
|
||||||
|
|
||||||
|
let addIconFrame = CGRect(origin: CGPoint(x: 22.0 + self.revealOffset, y: floor((size.height - addIconSize.height) * 0.5)), size: addIconSize)
|
||||||
|
addIconTransition.setPosition(view: addIconView, position: addIconFrame.center)
|
||||||
|
addIconTransition.setBounds(view: addIconView, bounds: CGRect(origin: CGPoint(), size: addIconFrame.size))
|
||||||
|
} else if let addIconView = self.addIconView {
|
||||||
|
self.addIconView = nil
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
alphaTransition.setAlpha(view: addIconView, alpha: 0.0, completion: { [weak addIconView] _ in
|
||||||
|
addIconView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
alphaTransition.setScale(view: addIconView, scale: 0.001)
|
||||||
|
} else {
|
||||||
|
addIconView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let textFieldFrame = CGRect(origin: CGPoint(x: leftInset - 16.0 + self.revealOffset, y: 0.0), size: textFieldSize)
|
||||||
if let textFieldView = self.textField.view {
|
if let textFieldView = self.textField.view {
|
||||||
if textFieldView.superview == nil {
|
if textFieldView.superview == nil {
|
||||||
self.addSubview(textFieldView)
|
self.addSubview(textFieldView)
|
||||||
|
|
@ -673,17 +857,17 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let checkSize = CGSize(width: 22.0, height: 22.0)
|
let checkSize = CGSize(width: 22.0, height: 22.0)
|
||||||
let checkFrame = CGRect(origin: CGPoint(x: floor((leftInset - checkSize.width) * 0.5) + self.revealOffset, y: floor((size.height - checkSize.height) * 0.5)), size: checkSize)
|
let checkFrame = CGRect(origin: CGPoint(x: leftInset - checkSize.width - 20.0 + self.revealOffset, y: floor((size.height - checkSize.height) * 0.5)), size: checkSize)
|
||||||
|
|
||||||
if animateIn {
|
if animateIn {
|
||||||
checkView.frame = CGRect(origin: CGPoint(x: -checkSize.width, y: self.bounds.height == 0.0 ? checkFrame.minY : floor((self.bounds.height - checkSize.height) * 0.5)), size: checkFrame.size)
|
checkView.frame = CGRect(origin: CGPoint(x: -checkSize.width, y: self.bounds.height == 0.0 ? checkFrame.minY : floor((self.bounds.height - checkSize.height) * 0.5)), size: checkFrame.size)
|
||||||
transition.setPosition(view: checkView, position: checkFrame.center)
|
transition.setPosition(view: checkView, position: checkFrame.center)
|
||||||
transition.setBounds(view: checkView, bounds: CGRect(origin: CGPoint(), size: checkFrame.size))
|
transition.setBounds(view: checkView, bounds: CGRect(origin: CGPoint(), size: checkFrame.size))
|
||||||
checkView.update(size: checkFrame.size, theme: component.theme, isSelected: selection.isSelected, transition: .immediate)
|
checkView.update(size: checkFrame.size, isRectangle: selection.isMultiSelection, theme: component.theme, isSelected: selection.isSelected, transition: .immediate)
|
||||||
} else {
|
} else {
|
||||||
transition.setPosition(view: checkView, position: checkFrame.center)
|
transition.setPosition(view: checkView, position: checkFrame.center)
|
||||||
transition.setBounds(view: checkView, bounds: CGRect(origin: CGPoint(), size: checkFrame.size))
|
transition.setBounds(view: checkView, bounds: CGRect(origin: CGPoint(), size: checkFrame.size))
|
||||||
checkView.update(size: checkFrame.size, theme: component.theme, isSelected: selection.isSelected, transition: transition)
|
checkView.update(size: checkFrame.size, isRectangle: selection.isMultiSelection, theme: component.theme, isSelected: selection.isSelected, transition: transition)
|
||||||
}
|
}
|
||||||
} else if let checkView = self.checkView {
|
} else if let checkView = self.checkView {
|
||||||
self.checkView = nil
|
self.checkView = nil
|
||||||
|
|
@ -692,40 +876,237 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var rightIconsInset: CGFloat = 0.0
|
var rightIconsInset: CGFloat = 16.0
|
||||||
if component.canReorder, let externalState = component.externalState, externalState.hasText {
|
let minHeight: CGFloat = 52.0
|
||||||
var reorderIconTransition = transition
|
|
||||||
let reorderIconView: UIImageView
|
if let attachment = component.attachment, attachment.alwaysDisplayAttachButton || component.externalState?.hasText == true {
|
||||||
if let current = self.reorderIconView {
|
var attachButtonTransition = transition
|
||||||
reorderIconView = current
|
let attachButton: ComponentView<Empty>
|
||||||
|
if let current = self.attachButton {
|
||||||
|
attachButton = current
|
||||||
} else {
|
} else {
|
||||||
reorderIconTransition = reorderIconTransition.withAnimation(.none)
|
attachButtonTransition = attachButtonTransition.withAnimation(.none)
|
||||||
reorderIconView = UIImageView()
|
attachButton = ComponentView()
|
||||||
self.reorderIconView = reorderIconView
|
self.attachButton = attachButton
|
||||||
self.addSubview(reorderIconView)
|
|
||||||
}
|
}
|
||||||
reorderIconView.image = PresentationResourcesItemList.itemListReorderIndicatorIcon(component.theme)
|
|
||||||
|
let attachButtonSize = attachButton.update(
|
||||||
var reorderIconSize = CGSize()
|
transition: attachButtonTransition,
|
||||||
if let icon = reorderIconView.image {
|
component: AnyComponent(PlainButtonComponent(
|
||||||
reorderIconSize = icon.size
|
content: AnyComponent(BundleIconComponent(
|
||||||
|
name: "Chat/Input/Text/IconAttachment",
|
||||||
|
tintColor: component.theme.chat.inputPanel.inputControlColor.blitOver(component.theme.list.itemBlocksBackgroundColor, alpha: 1.0),
|
||||||
|
maxSize: CGSize(width: 25.0, height: 25.0)
|
||||||
|
)),
|
||||||
|
effectAlignment: .center,
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.attachAction?()
|
||||||
|
},
|
||||||
|
animateScale: false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
|
)
|
||||||
|
let attachButtonFrame = CGRect(origin: CGPoint(x: size.width - rightIconsInset - 7.0 - attachButtonSize.width + self.revealOffset, y: size.height - minHeight + floor((minHeight - attachButtonSize.height) * 0.5)), size: attachButtonSize)
|
||||||
|
if let attachButtonView = attachButton.view as? PlainButtonComponent.View {
|
||||||
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
|
||||||
|
if attachButtonView.superview == nil {
|
||||||
|
self.addSubview(attachButtonView)
|
||||||
|
ComponentTransition.immediate.setAlpha(view: attachButtonView, alpha: 0.0)
|
||||||
|
ComponentTransition.immediate.setScale(view: attachButtonView, scale: 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachButtonTransition.setPosition(view: attachButtonView, position: attachButtonFrame.center)
|
||||||
|
attachButtonTransition.setBounds(view: attachButtonView, bounds: CGRect(origin: CGPoint(), size: attachButtonFrame.size))
|
||||||
|
|
||||||
|
let displaySelector = attachment.media == nil
|
||||||
|
alphaTransition.setAlpha(view: attachButtonView, alpha: displaySelector ? 1.0 : 0.0)
|
||||||
|
alphaTransition.setScale(view: attachButtonView, scale: displaySelector ? 1.0 : 0.001)
|
||||||
}
|
}
|
||||||
|
|
||||||
let reorderIconFrame = CGRect(origin: CGPoint(x: size.width - 14.0 - reorderIconSize.width + self.revealOffset, y: floor((size.height - reorderIconSize.height) * 0.5)), size: reorderIconSize)
|
rightIconsInset += 42.0
|
||||||
reorderIconTransition.setPosition(view: reorderIconView, position: reorderIconFrame.center)
|
} else if let attachButton = self.attachButton {
|
||||||
reorderIconTransition.setBounds(view: reorderIconView, bounds: CGRect(origin: CGPoint(), size: reorderIconFrame.size))
|
self.attachButton = nil
|
||||||
|
if let attachButtonView = attachButton.view {
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
alphaTransition.setAlpha(view: attachButtonView, alpha: 0.0, completion: { [weak attachButtonView] _ in
|
||||||
|
attachButtonView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
alphaTransition.setScale(view: attachButtonView, scale: 0.001)
|
||||||
|
} else {
|
||||||
|
attachButtonView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageNodeSize = CGSize(width: 40.0, height: 40.0)
|
||||||
|
let imageNodeFrame = CGRect(origin: CGPoint(x: size.width - 16.0 - imageNodeSize.width + self.revealOffset, y: size.height - minHeight + floor((minHeight - imageNodeSize.height) * 0.5)), size: imageNodeSize)
|
||||||
|
|
||||||
|
var isSticker = false
|
||||||
|
if let attachment = component.attachment, let file = attachment.media?.media as? TelegramMediaFile, file.isSticker {
|
||||||
|
isSticker = true
|
||||||
|
|
||||||
rightIconsInset += 36.0
|
let animationSize = CGSize(width: 40.0, height: 40.0)
|
||||||
} else if let reorderIconView = self.reorderIconView {
|
let animationLayer: InlineStickerItemLayer
|
||||||
self.reorderIconView = nil
|
if let current = self.animationLayer {
|
||||||
|
animationLayer = current
|
||||||
|
} else {
|
||||||
|
if let animationLayer = self.animationLayer {
|
||||||
|
self.animationLayer = nil
|
||||||
|
animationLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
animationLayer = InlineStickerItemLayer(
|
||||||
|
context: component.context,
|
||||||
|
userLocation: .other,
|
||||||
|
attemptSynchronousLoad: true,
|
||||||
|
emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file, custom: nil, enableAnimation: true),
|
||||||
|
file: file,
|
||||||
|
cache: component.context.animationCache,
|
||||||
|
renderer: component.context.animationRenderer,
|
||||||
|
unique: false,
|
||||||
|
placeholderColor: component.theme.list.mediaPlaceholderColor,
|
||||||
|
pointSize: CGSize(width: animationSize.width * 2.0, height: animationSize.height * 2.0),
|
||||||
|
dynamicColor: nil,
|
||||||
|
loopCount: 2
|
||||||
|
)
|
||||||
|
self.animationLayer = animationLayer
|
||||||
|
self.layer.addSublayer(animationLayer)
|
||||||
|
}
|
||||||
|
animationLayer.frame = imageNodeFrame
|
||||||
|
|
||||||
|
if self.imageButton.superview == nil {
|
||||||
|
self.imageButton.addTarget(self, action: #selector(self.imageButtonPressed), for: .touchUpInside)
|
||||||
|
self.addSubview(self.imageButton)
|
||||||
|
}
|
||||||
|
self.imageButton.frame = imageNodeFrame
|
||||||
|
} else if let animationLayer = self.animationLayer {
|
||||||
|
self.imageNode = nil
|
||||||
if !transition.animation.isImmediate {
|
if !transition.animation.isImmediate {
|
||||||
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
alphaTransition.setAlpha(view: reorderIconView, alpha: 0.0, completion: { [weak reorderIconView] _ in
|
alphaTransition.setAlpha(layer: animationLayer, alpha: 0.0, completion: { [weak animationLayer] _ in
|
||||||
reorderIconView?.removeFromSuperview()
|
animationLayer?.removeFromSuperlayer()
|
||||||
})
|
})
|
||||||
alphaTransition.setScale(view: reorderIconView, scale: 0.001)
|
alphaTransition.setScale(layer: animationLayer, scale: 0.001)
|
||||||
} else {
|
} else {
|
||||||
reorderIconView.removeFromSuperview()
|
animationLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
self.imageButton.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let attachment = component.attachment, let media = attachment.media, !isSticker {
|
||||||
|
var imageNodeTransition = transition
|
||||||
|
let imageNode: TransformImageNode
|
||||||
|
if let current = self.imageNode {
|
||||||
|
imageNode = current
|
||||||
|
} else {
|
||||||
|
imageNodeTransition = imageNodeTransition.withAnimation(.none)
|
||||||
|
imageNode = TransformImageNode()
|
||||||
|
imageNode.isUserInteractionEnabled = false
|
||||||
|
self.imageNode = imageNode
|
||||||
|
self.addSubview(imageNode.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageNodeTransition.setPosition(view: imageNode.view, position: imageNodeFrame.center)
|
||||||
|
imageNodeTransition.setBounds(view: imageNode.view, bounds: CGRect(origin: CGPoint(), size: imageNodeFrame.size))
|
||||||
|
|
||||||
|
var imageSize = imageNodeSize
|
||||||
|
var updateMedia = false
|
||||||
|
if self.appliedMedia != media {
|
||||||
|
self.appliedMedia = media
|
||||||
|
updateMedia = true
|
||||||
|
}
|
||||||
|
if let image = media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations), let photoReference = media.concrete(TelegramMediaImage.self) {
|
||||||
|
imageSize = largest.dimensions.cgSize.aspectFilled(imageNodeSize)
|
||||||
|
|
||||||
|
if updateMedia {
|
||||||
|
imageNode.setSignal(chatMessagePhoto(postbox: component.context.account.postbox, userLocation: .other, photoReference: photoReference))
|
||||||
|
}
|
||||||
|
} else if let file = media.media as? TelegramMediaFile, let fileReference = media.concrete(TelegramMediaFile.self) {
|
||||||
|
if let dimensions = file.dimensions {
|
||||||
|
imageSize = dimensions.cgSize.aspectFilled(imageNodeSize)
|
||||||
|
}
|
||||||
|
if file.mimeType.hasPrefix("image/") {
|
||||||
|
if updateMedia {
|
||||||
|
imageNode.setSignal(instantPageImageFile(account: component.context.account, userLocation: .other, fileReference: fileReference, fetched: true))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if updateMedia {
|
||||||
|
imageNode.setSignal(chatMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: fileReference))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let map = media.media as? TelegramMediaMap {
|
||||||
|
imageSize = CGSize(width: 40.0, height: 40.0)
|
||||||
|
if updateMedia {
|
||||||
|
let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(imageSize.width), height: Int32(imageSize.height))
|
||||||
|
imageNode.setSignal(chatMapSnapshotImage(engine: component.context.engine, resource: resource))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cornerRadius: CGFloat = 10.0
|
||||||
|
let makeLayout = imageNode.asyncLayout()
|
||||||
|
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: cornerRadius), imageSize: imageSize, boundingSize: imageNodeSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))
|
||||||
|
apply()
|
||||||
|
|
||||||
|
if self.imageButton.superview == nil {
|
||||||
|
self.imageButton.addTarget(self, action: #selector(self.imageButtonPressed), for: .touchUpInside)
|
||||||
|
self.addSubview(self.imageButton)
|
||||||
|
}
|
||||||
|
self.imageButton.frame = imageNodeFrame
|
||||||
|
|
||||||
|
if let progress = attachment.progress {
|
||||||
|
let statusNode: SemanticStatusNode
|
||||||
|
if let current = self.statusNode {
|
||||||
|
statusNode = current
|
||||||
|
} else {
|
||||||
|
statusNode = SemanticStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.5), foregroundNodeColor: .white)
|
||||||
|
self.statusNode = statusNode
|
||||||
|
self.addSubview(statusNode.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressFrame = imageNodeFrame.insetBy(dx: 6.0, dy: 6.0)
|
||||||
|
statusNode.frame = progressFrame
|
||||||
|
statusNode.transitionToState(.progress(value: max(0.027, min(1.0, progress)), cancelEnabled: true, appearance: SemanticStatusNodeState.ProgressAppearance(inset: 1.0, lineWidth: 1.0 + UIScreenPixel), animateRotation: false), updateCutout: false)
|
||||||
|
} else if let statusNode = self.statusNode {
|
||||||
|
self.statusNode = nil
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
alphaTransition.setAlpha(view: statusNode.view, alpha: 0.0, completion: { [weak statusNode] _ in
|
||||||
|
statusNode?.view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
alphaTransition.setScale(view: statusNode.view, scale: 0.001)
|
||||||
|
} else {
|
||||||
|
statusNode.view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let imageNode = self.imageNode {
|
||||||
|
self.imageNode = nil
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
alphaTransition.setAlpha(view: imageNode.view, alpha: 0.0, completion: { [weak imageNode] _ in
|
||||||
|
imageNode?.view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
alphaTransition.setScale(view: imageNode.view, scale: 0.001)
|
||||||
|
} else {
|
||||||
|
imageNode.view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
self.imageButton.removeFromSuperview()
|
||||||
|
|
||||||
|
if let statusNode = self.statusNode {
|
||||||
|
self.statusNode = nil
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
alphaTransition.setAlpha(view: statusNode.view, alpha: 0.0, completion: { [weak statusNode] _ in
|
||||||
|
statusNode?.view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
alphaTransition.setScale(view: statusNode.view, scale: 0.001)
|
||||||
|
} else {
|
||||||
|
statusNode.view.removeFromSuperview()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -775,7 +1156,7 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: modeSelectorSize
|
containerSize: modeSelectorSize
|
||||||
)
|
)
|
||||||
let modeSelectorFrame = CGRect(origin: CGPoint(x: size.width - rightIconsInset - 4.0 - modeSelectorSize.width + self.revealOffset, y: floor((size.height - modeSelectorSize.height) * 0.5)), size: modeSelectorSize)
|
let modeSelectorFrame = CGRect(origin: CGPoint(x: size.width - rightIconsInset - 4.0 - modeSelectorSize.width + self.revealOffset, y: size.height - minHeight + floor((minHeight - modeSelectorSize.height) * 0.5)), size: modeSelectorSize)
|
||||||
if let modeSelectorView = modeSelector.view as? PlainButtonComponent.View {
|
if let modeSelectorView = modeSelector.view as? PlainButtonComponent.View {
|
||||||
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
|
||||||
|
|
@ -881,6 +1262,10 @@ public final class ListComposePollOptionComponent: Component {
|
||||||
var leftInset: CGFloat = 16.0
|
var leftInset: CGFloat = 16.0
|
||||||
let rightInset: CGFloat = 16.0
|
let rightInset: CGFloat = 16.0
|
||||||
|
|
||||||
|
if component.hasLeftInset {
|
||||||
|
leftInset += 46.0
|
||||||
|
}
|
||||||
|
|
||||||
if component.selection != nil {
|
if component.selection != nil {
|
||||||
leftInset += 34.0
|
leftInset += 34.0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
||||||
self.customTitle = self.presentationData.strings.RequestPeer_ChooseGroupTitle
|
self.customTitle = self.presentationData.strings.RequestPeer_ChooseGroupTitle
|
||||||
case .channel:
|
case .channel:
|
||||||
self.customTitle = self.presentationData.strings.RequestPeer_ChooseChannelTitle
|
self.customTitle = self.presentationData.strings.RequestPeer_ChooseChannelTitle
|
||||||
|
case .createBot:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.customTitle = self.presentationData.strings.ChatImport_Title
|
self.customTitle = self.presentationData.strings.ChatImport_Title
|
||||||
|
|
|
||||||
|
|
@ -1210,6 +1210,10 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||||
emptyText = ""
|
emptyText = ""
|
||||||
}
|
}
|
||||||
emptyButtonText = self.presentationData.strings.RequestPeer_CreateNewGroup
|
emptyButtonText = self.presentationData.strings.RequestPeer_CreateNewGroup
|
||||||
|
case .createBot:
|
||||||
|
emptyTitle = ""
|
||||||
|
emptyText = ""
|
||||||
|
emptyButtonText = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
self.emptyTitleNode.attributedText = NSAttributedString(string: emptyTitle, font: Font.semibold(15.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
self.emptyTitleNode.attributedText = NSAttributedString(string: emptyTitle, font: Font.semibold(15.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||||
|
|
@ -1863,6 +1867,8 @@ private func stringForRequestPeerType(strings: PresentationStrings, peerType: Re
|
||||||
append(rightsString)
|
append(rightsString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case .createBot:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if lines.isEmpty {
|
if lines.isEmpty {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||||
"//submodules/TextFormat",
|
"//submodules/TextFormat",
|
||||||
"//submodules/PhoneNumberFormat",
|
"//submodules/PhoneNumberFormat",
|
||||||
"//submodules/ComposePollUI",
|
|
||||||
"//submodules/TelegramIntents",
|
"//submodules/TelegramIntents",
|
||||||
"//submodules/LegacyUI",
|
"//submodules/LegacyUI",
|
||||||
"//submodules/WebSearchUI",
|
"//submodules/WebSearchUI",
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import ChatEntityKeyboardInputNode
|
||||||
import ChatScheduleTimeController
|
import ChatScheduleTimeController
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import PhoneNumberFormat
|
import PhoneNumberFormat
|
||||||
import ComposePollUI
|
|
||||||
import TelegramIntents
|
import TelegramIntents
|
||||||
import LegacyUI
|
import LegacyUI
|
||||||
import WebSearchUI
|
import WebSearchUI
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
||||||
import ShareController
|
import ShareController
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ComposePollUI
|
|
||||||
import AlertUI
|
import AlertUI
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
||||||
import ShareController
|
import ShareController
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ComposePollUI
|
|
||||||
import AlertUI
|
import AlertUI
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
||||||
import ShareController
|
import ShareController
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ComposePollUI
|
|
||||||
import AlertUI
|
import AlertUI
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
||||||
import ShareController
|
import ShareController
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ComposePollUI
|
|
||||||
import AlertUI
|
import AlertUI
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
||||||
import ShareController
|
import ShareController
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ComposePollUI
|
|
||||||
import AlertUI
|
import AlertUI
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
||||||
import ShareController
|
import ShareController
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ComposePollUI
|
|
||||||
import AlertUI
|
import AlertUI
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ import DeviceLocationManager
|
||||||
import ShareController
|
import ShareController
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ComposePollUI
|
|
||||||
import ComposeTodoScreen
|
|
||||||
import AlertUI
|
import AlertUI
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
@ -4625,6 +4623,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||||
}
|
}
|
||||||
createNewGroupImpl = { [weak controller] in
|
createNewGroupImpl = { [weak controller] in
|
||||||
switch peerType {
|
switch peerType {
|
||||||
|
case .createBot:
|
||||||
|
break
|
||||||
case .user:
|
case .user:
|
||||||
break
|
break
|
||||||
case let .group(group):
|
case let .group(group):
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ import MediaEditorScreen
|
||||||
import CameraScreen
|
import CameraScreen
|
||||||
import ShareController
|
import ShareController
|
||||||
import ComposeTodoScreen
|
import ComposeTodoScreen
|
||||||
import ComposePollUI
|
import ComposePollScreen
|
||||||
import Photos
|
import Photos
|
||||||
import AttachmentFileController
|
import AttachmentFileController
|
||||||
|
|
||||||
|
|
@ -1291,26 +1291,6 @@ extension ChatControllerImpl {
|
||||||
self?.openCamera(cameraView: nil)
|
self?.openCamera(cameraView: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
controller.presentWebSearch = { [weak self, weak controller] mediaGroups, activateOnDisplay in
|
|
||||||
self?.presentWebSearch(editingMessage: false, attachment: true, activateOnDisplay: activateOnDisplay, present: { [weak controller] c, a in
|
|
||||||
controller?.present(c, in: .current)
|
|
||||||
if let webSearchController = c as? WebSearchController {
|
|
||||||
webSearchController.searchingUpdated = { [weak mediaGroups] searching in
|
|
||||||
if let mediaGroups = mediaGroups, mediaGroups.isNodeLoaded {
|
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
|
||||||
transition.updateAlpha(node: mediaGroups.displayNode, alpha: searching ? 0.0 : 1.0)
|
|
||||||
mediaGroups.displayNode.isUserInteractionEnabled = !searching
|
|
||||||
}
|
|
||||||
}
|
|
||||||
webSearchController.present(mediaGroups, in: .current)
|
|
||||||
webSearchController.dismissed = {
|
|
||||||
updateMediaPickerContext(mediaPickerContext)
|
|
||||||
}
|
|
||||||
controller?.webSearchController = webSearchController
|
|
||||||
updateMediaPickerContext(webSearchController.mediaPickerContext)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
controller.presentSchedulePicker = { [weak self] media, done in
|
controller.presentSchedulePicker = { [weak self] media, done in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time, repeatPeriod in
|
strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time, repeatPeriod in
|
||||||
|
|
@ -2049,9 +2029,15 @@ extension ChatControllerImpl {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
var attributes: [MessageAttribute] = []
|
||||||
|
if !poll.description.entities.isEmpty {
|
||||||
|
attributes.append(TextEntitiesMessageAttribute(entities: poll.description.entities))
|
||||||
|
}
|
||||||
|
|
||||||
let message: EnqueueMessage = .message(
|
let message: EnqueueMessage = .message(
|
||||||
text: "",
|
text: poll.description.string,
|
||||||
attributes: [],
|
attributes: attributes,
|
||||||
inlineStickers: [:],
|
inlineStickers: [:],
|
||||||
mediaReference: .standalone(media: TelegramMediaPoll(
|
mediaReference: .standalone(media: TelegramMediaPoll(
|
||||||
pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min...Int64.max)),
|
pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min...Int64.max)),
|
||||||
|
|
@ -2063,7 +2049,12 @@ extension ChatControllerImpl {
|
||||||
correctAnswers: poll.correctAnswers,
|
correctAnswers: poll.correctAnswers,
|
||||||
results: poll.results,
|
results: poll.results,
|
||||||
isClosed: false,
|
isClosed: false,
|
||||||
deadlineTimeout: poll.deadlineTimeout
|
deadlineTimeout: poll.deadlineTimeout,
|
||||||
|
openAnswers: poll.openAnswers,
|
||||||
|
revotingDisabled: poll.revotingDisabled,
|
||||||
|
shuffleAnswers: poll.shuffleAnswers,
|
||||||
|
hideResultsUntilClose: poll.hideResultsUntilClose,
|
||||||
|
attachedMedia: poll.media?.media
|
||||||
)),
|
)),
|
||||||
threadId: self.chatLocation.threadId,
|
threadId: self.chatLocation.threadId,
|
||||||
replyToMessageId: nil,
|
replyToMessageId: nil,
|
||||||
|
|
|
||||||
|
|
@ -1589,7 +1589,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||||
hasSelected = true
|
hasSelected = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if hasSelected, case .poll = activePoll.kind {
|
if hasSelected, !activePoll.revotingDisabled {
|
||||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_UnvotePoll, icon: { theme in
|
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_UnvotePoll, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unvote"), color: theme.actionSheet.primaryTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unvote"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue