mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
[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 {
|
||||
match = false
|
||||
}
|
||||
case .createBot:
|
||||
break
|
||||
}
|
||||
if match {
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -2328,6 +2328,8 @@ public final class ChatListNode: ListViewImpl {
|
|||
} else {
|
||||
match = false
|
||||
}
|
||||
case .createBot:
|
||||
break
|
||||
}
|
||||
if match {
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -56,19 +56,21 @@ public extension CheckNodeTheme {
|
|||
}
|
||||
}
|
||||
|
||||
public enum CheckNodeContent {
|
||||
case check
|
||||
public enum CheckNodeContent: Equatable {
|
||||
case check(isRectangle: Bool)
|
||||
case counter(Int)
|
||||
}
|
||||
|
||||
private final class CheckNodeParameters: NSObject {
|
||||
let isRectangle: Bool
|
||||
let theme: CheckNodeTheme
|
||||
let content: CheckNodeContent
|
||||
let animationProgress: CGFloat
|
||||
let selected: 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.content = content
|
||||
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.content = content
|
||||
|
||||
|
|
@ -161,7 +163,7 @@ public class CheckNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -200,7 +202,7 @@ public class InteractiveCheckNode: CheckNode {
|
|||
|
||||
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()
|
||||
|
||||
super.init(theme: theme, content: content)
|
||||
|
|
@ -250,7 +252,7 @@ public class CheckLayer: CALayer {
|
|||
|
||||
public override init() {
|
||||
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()
|
||||
|
||||
|
|
@ -270,7 +272,7 @@ public class CheckLayer: CALayer {
|
|||
self.isOpaque = false
|
||||
}
|
||||
|
||||
public init(theme: CheckNodeTheme, content: CheckNodeContent = .check) {
|
||||
public init(theme: CheckNodeTheme, content: CheckNodeContent = .check(isRectangle: false)) {
|
||||
self.theme = theme
|
||||
self.content = content
|
||||
|
||||
|
|
@ -364,7 +366,7 @@ public class CheckLayer: CALayer {
|
|||
CheckLayer.drawContents(
|
||||
context: context,
|
||||
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
|
||||
}
|
||||
|
|
@ -414,13 +416,24 @@ public class CheckLayer: CALayer {
|
|||
let fillProgress: CGFloat = parameters.animationProgress
|
||||
|
||||
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)
|
||||
|
||||
context.setBlendMode(.copy)
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ public final class GridMessageSelectionLayer: CALayer {
|
|||
public let checkLayer: CheckLayer
|
||||
|
||||
public init(theme: CheckNodeTheme) {
|
||||
self.checkLayer = CheckLayer(theme: theme, content: .check)
|
||||
self.checkLayer = CheckLayer(theme: theme, content: .check(isRectangle: false))
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
|
|||
|
|
@ -167,6 +167,12 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
}
|
||||
|
||||
public enum AssetsMode: Equatable {
|
||||
public enum PollMode: Equatable {
|
||||
case description
|
||||
case quizAnswer
|
||||
case option
|
||||
}
|
||||
|
||||
case `default`
|
||||
case wallpaper
|
||||
case story
|
||||
|
|
@ -174,6 +180,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
case cover
|
||||
case createSticker
|
||||
case createAvatar
|
||||
case poll(PollMode)
|
||||
}
|
||||
|
||||
case assets(PHAssetCollection?, AssetsMode)
|
||||
|
|
@ -2037,6 +2044,17 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
self.titleView.title = presentationData.strings.MediaPicker_AddImage
|
||||
case .cover:
|
||||
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 {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -139,7 +139,7 @@ public final class ShareStartAtTimestampNode: HighlightTrackingButtonNode {
|
|||
self.titleTextColor = titleTextColor
|
||||
self.checkNodeTheme = checkNodeTheme
|
||||
|
||||
self.checkNode = CheckNode(theme: checkNodeTheme, content: .check)
|
||||
self.checkNode = CheckNode(theme: checkNodeTheme, content: .check(isRectangle: false))
|
||||
self.checkNode.isUserInteractionEnabled = false
|
||||
|
||||
self.titleTextNode = TextNode()
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
|
|||
let diminsionsSize = dimensions.cgSizeValue
|
||||
return .single(.preparing(false))
|
||||
|> 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
|
||||
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) {
|
||||
return .single(.preparing(false))
|
||||
|> 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
|
||||
return .generic
|
||||
}
|
||||
|
|
@ -265,7 +265,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
|
|||
let imageData = scaledImage.jpegData(compressionQuality: 0.54)!
|
||||
return .single(.preparing(false))
|
||||
|> 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
|
||||
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> {
|
||||
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)
|
||||
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: source, encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|
||||
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|
||||
|> mapToSignal { next -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
|
||||
switch next {
|
||||
|
|
@ -103,7 +103,7 @@ public func standaloneUploadedImage(postbox: Postbox, network: Network, peerId:
|
|||
switch result {
|
||||
case let .encryptedFile(encryptedFileData):
|
||||
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:
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,6 +187,13 @@ public enum TelegramMediaPollKind: Equatable, PostboxCoding {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ public enum PresentationResourceKey: Int32 {
|
|||
case itemListDeleteIcon
|
||||
case itemListDeleteIndicatorIcon
|
||||
case itemListReorderIndicatorIcon
|
||||
case itemListAddIndicatorIcon
|
||||
case itemListLinkIcon
|
||||
case itemListAddPersonIcon
|
||||
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? {
|
||||
return theme.image(PresentationResourceKey.itemListLinkIcon.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: theme.list.itemAccentColor)
|
||||
|
|
|
|||
|
|
@ -1780,6 +1780,8 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
} else {
|
||||
attributedString = NSAttributedString(string: strings.Notification_CopyProtection_Request(peerName).string, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
case .managedBotCreated:
|
||||
attributedString = nil
|
||||
case .unknown:
|
||||
attributedString = nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ swift_library(
|
|||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/ActionSheetPeerItem:ActionSheetPeerItem",
|
||||
"//submodules/ComposePollUI:ComposePollUI",
|
||||
"//submodules/TelegramUI/Components/ComposePollScreen",
|
||||
"//submodules/AlertUI:AlertUI",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/TouchDownGesture:TouchDownGesture",
|
||||
|
|
|
|||
|
|
@ -524,48 +524,53 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
return messages
|
||||
}
|
||||
)
|
||||
globalMusic = .single(nil)
|
||||
|> then(
|
||||
context.engine.peers.resolvePeerByName(name: "lybot", referrer: nil)
|
||||
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
|
||||
guard case let .result(result) = result else {
|
||||
return .complete()
|
||||
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let searchBot = data["music_search_username"] as? String, !searchBot.isEmpty {
|
||||
globalMusic = .single(nil)
|
||||
|> then(
|
||||
context.engine.peers.resolvePeerByName(name: searchBot, referrer: nil)
|
||||
|> 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 {
|
||||
return .single(nil)
|
||||
}
|
||||
return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: query, offset: "")
|
||||
|> map { results -> ChatContextResultCollection? in
|
||||
return results?.results
|
||||
}
|
||||
|> `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
|
||||
|> mapToSignal { peer -> Signal<ChatContextResultCollection?, NoError> in
|
||||
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 results?.results
|
||||
}
|
||||
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
var hasSeparateCommentsButton = false
|
||||
|
||||
var addedPriceInfo = false
|
||||
var addedPollMedia = false
|
||||
|
||||
outer: for (message, itemAttributes) in item.content {
|
||||
for attribute in message.attributes {
|
||||
|
|
@ -283,7 +284,22 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
result.removeAll()
|
||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
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)))
|
||||
needReactions = false
|
||||
} else if let _ = media as? TelegramMediaTodo {
|
||||
|
|
@ -318,10 +334,12 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
isMediaInverted = updatingMedia.invertMediaAttribute != nil
|
||||
} else if let _ = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) {
|
||||
isMediaInverted = true
|
||||
} else if let _ = message.media.first(where: { $0 is TelegramMediaPoll }) {
|
||||
isMediaInverted = true
|
||||
}
|
||||
|
||||
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 {
|
||||
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
|
||||
needReactions = false
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
for media in item.message.media {
|
||||
if let telegramFile = media as? TelegramMediaFile {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -205,6 +205,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
if item.presentationData.isPreview {
|
||||
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
|
||||
let leftInset: CGFloat = 50.0
|
||||
let rightInset: CGFloat = 12.0
|
||||
let rightInset: CGFloat = 10.0
|
||||
|
||||
let incoming = message.effectivelyIncoming(context.account.peerId)
|
||||
|
||||
|
|
@ -559,7 +559,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
|||
|
||||
let shouldHaveRadioNode = optionResult == nil
|
||||
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
|
||||
} else {
|
||||
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
|
||||
}
|
||||
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
|
||||
if strokeColor.alpha.isZero {
|
||||
|
|
@ -689,9 +695,9 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
|||
))
|
||||
let titleNodeFrame: CGRect
|
||||
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 {
|
||||
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 {
|
||||
node.titleNode = titleNode
|
||||
|
|
@ -718,7 +724,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
|||
}
|
||||
let radioSize: CGFloat = 22.0
|
||||
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 {
|
||||
node.radioNode = nil
|
||||
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.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 {
|
||||
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 (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 (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
|
||||
case .format:
|
||||
title = strings.Conversation_FormatDate_Title
|
||||
case .poll:
|
||||
//TODO:localize
|
||||
title = "Deadline"
|
||||
}
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
|
|
@ -384,6 +387,8 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
|
|||
var repeatValueFrame = CGRect()
|
||||
if case .format = component.mode {
|
||||
contentHeight += 8.0
|
||||
} else if case .poll = component.mode {
|
||||
contentHeight += 8.0
|
||||
} else {
|
||||
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
|
||||
|
|
@ -491,6 +496,9 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
|
|||
}
|
||||
case .format:
|
||||
buttonTitle = component.currentTime != nil ? strings.Conversation_FormatDate_EditDate : strings.Conversation_FormatDate_AddDate
|
||||
case .poll:
|
||||
//TODO:localize
|
||||
buttonTitle = "Set Deadline"
|
||||
}
|
||||
|
||||
let buttonSideInset: CGFloat = 30.0
|
||||
|
|
@ -930,6 +938,7 @@ public class ChatScheduleTimeScreen: ViewControllerComponentContainer {
|
|||
case scheduledMessages(sendWhenOnlineAvailable: Bool)
|
||||
case reminders
|
||||
case format
|
||||
case poll
|
||||
}
|
||||
|
||||
public struct Result {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ComposePollUI",
|
||||
module_name = "ComposePollUI",
|
||||
name = "ComposePollScreen",
|
||||
module_name = "ComposePollScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
|
|
@ -21,7 +21,6 @@ swift_library(
|
|||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/ObjCRuntimeUtils",
|
||||
"//submodules/AttachmentUI",
|
||||
"//submodules/TextInputMenu",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
|
|
@ -43,6 +42,17 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
|
||||
"//submodules/TelegramUI/Components/ListComposePollOptionComponent",
|
||||
"//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: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/TelegramUI/Components/EmojiSuggestionsComponent",
|
||||
"//submodules/TelegramUI/Components/ListComposePollOptionComponent",
|
||||
"//submodules/ComposePollUI",
|
||||
"//submodules/TelegramUI/Components/ComposePollScreen",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1623,8 +1623,8 @@ final class ComposeTodoScreenComponent: Component {
|
|||
}
|
||||
if let input = self.validatedInput() {
|
||||
controller.completion(input)
|
||||
controller.dismiss()
|
||||
}
|
||||
controller.dismiss()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ public final class GlassBarButtonComponent: Component {
|
|||
|
||||
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)
|
||||
} else if case .glass = component.state {
|
||||
let glassBackgroundView: GlassBackgroundView
|
||||
|
|
@ -283,7 +283,7 @@ public final class GlassBarButtonComponent: Component {
|
|||
|
||||
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)
|
||||
} else if let glassBackgroundView = self.glassBackgroundView {
|
||||
self.glassBackgroundView = nil
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ public final class ListActionItemComponent: Component {
|
|||
if let current = self.checkLayer {
|
||||
checkLayer = current
|
||||
} 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.layer.addSublayer(checkLayer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,15 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/CheckNode",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/PhotoResources",
|
||||
"//submodules/LocationResources",
|
||||
"//submodules/SemanticStatusNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import UIKit
|
|||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import TelegramCore
|
||||
import CheckNode
|
||||
import ListSectionComponent
|
||||
import ComponentFlow
|
||||
|
|
@ -12,7 +13,13 @@ import MultilineTextComponent
|
|||
import PresentationDataUtils
|
||||
import LottieComponent
|
||||
import PlainButtonComponent
|
||||
import BundleIconComponent
|
||||
import SwiftSignalKit
|
||||
import PhotoResources
|
||||
import LocationResources
|
||||
import SemanticStatusNode
|
||||
import EmojiTextAttachmentView
|
||||
import TextFormat
|
||||
|
||||
public final class ListComposePollOptionComponent: Component {
|
||||
public enum Style {
|
||||
|
|
@ -34,10 +41,12 @@ public final class ListComposePollOptionComponent: Component {
|
|||
|
||||
public final class Selection: Equatable {
|
||||
public let isSelected: Bool
|
||||
public let isMultiSelection: Bool
|
||||
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.isMultiSelection = isMultiSelection
|
||||
self.toggle = toggle
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +54,34 @@ public final class ListComposePollOptionComponent: Component {
|
|||
if lhs.isSelected != rhs.isSelected {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -83,8 +120,11 @@ public final class ListComposePollOptionComponent: Component {
|
|||
public let resetText: ResetText?
|
||||
public let assumeIsEditing: Bool
|
||||
public let characterLimit: Int?
|
||||
public let hasLeftInset: Bool
|
||||
public let enableInlineAnimations: Bool
|
||||
public let canReorder: Bool
|
||||
public let canAdd: Bool
|
||||
public let attachment: Attachment?
|
||||
public let emptyLineHandling: TextFieldComponent.EmptyLineHandling
|
||||
public let returnKeyAction: (() -> Void)?
|
||||
public let backspaceKeyAction: (() -> Void)?
|
||||
|
|
@ -92,6 +132,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||
public let inputMode: InputMode?
|
||||
public let alwaysDisplayInputModeSelector: Bool
|
||||
public let toggleInputMode: (() -> Void)?
|
||||
public let attachAction: (() -> Void)?
|
||||
public let deleteAction: (() -> Void)?
|
||||
public let paste: ((TextFieldComponent.PasteData) -> Void)?
|
||||
public let tag: AnyObject?
|
||||
|
|
@ -108,7 +149,10 @@ public final class ListComposePollOptionComponent: Component {
|
|||
assumeIsEditing: Bool = false,
|
||||
characterLimit: Int,
|
||||
enableInlineAnimations: Bool = true,
|
||||
hasLeftInset: Bool = false,
|
||||
canReorder: Bool = false,
|
||||
canAdd: Bool = false,
|
||||
attachment: Attachment? = nil,
|
||||
emptyLineHandling: TextFieldComponent.EmptyLineHandling,
|
||||
returnKeyAction: (() -> Void)?,
|
||||
backspaceKeyAction: (() -> Void)?,
|
||||
|
|
@ -116,6 +160,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||
inputMode: InputMode?,
|
||||
alwaysDisplayInputModeSelector: Bool = false,
|
||||
toggleInputMode: (() -> Void)?,
|
||||
attachAction: (() -> Void)? = nil,
|
||||
deleteAction: (() -> Void)? = nil,
|
||||
paste: ((TextFieldComponent.PasteData) -> Void)? = nil,
|
||||
tag: AnyObject? = nil
|
||||
|
|
@ -131,7 +176,10 @@ public final class ListComposePollOptionComponent: Component {
|
|||
self.assumeIsEditing = assumeIsEditing
|
||||
self.characterLimit = characterLimit
|
||||
self.enableInlineAnimations = enableInlineAnimations
|
||||
self.hasLeftInset = hasLeftInset
|
||||
self.canReorder = canReorder
|
||||
self.canAdd = canAdd
|
||||
self.attachment = attachment
|
||||
self.emptyLineHandling = emptyLineHandling
|
||||
self.returnKeyAction = returnKeyAction
|
||||
self.backspaceKeyAction = backspaceKeyAction
|
||||
|
|
@ -139,6 +187,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||
self.inputMode = inputMode
|
||||
self.alwaysDisplayInputModeSelector = alwaysDisplayInputModeSelector
|
||||
self.toggleInputMode = toggleInputMode
|
||||
self.attachAction = attachAction
|
||||
self.deleteAction = deleteAction
|
||||
self.paste = paste
|
||||
self.tag = tag
|
||||
|
|
@ -178,9 +227,18 @@ public final class ListComposePollOptionComponent: Component {
|
|||
if lhs.enableInlineAnimations != rhs.enableInlineAnimations {
|
||||
return false
|
||||
}
|
||||
if lhs.hasLeftInset != rhs.hasLeftInset {
|
||||
return false
|
||||
}
|
||||
if lhs.canReorder != rhs.canReorder {
|
||||
return false
|
||||
}
|
||||
if lhs.canAdd != rhs.canAdd {
|
||||
return false
|
||||
}
|
||||
if lhs.attachment != rhs.attachment {
|
||||
return false
|
||||
}
|
||||
if lhs.emptyLineHandling != rhs.emptyLineHandling {
|
||||
return false
|
||||
}
|
||||
|
|
@ -202,6 +260,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||
private final class CheckView: HighlightTrackingButton {
|
||||
private var checkLayer: CheckLayer?
|
||||
private var theme: PresentationTheme?
|
||||
private var isRectangle = false
|
||||
|
||||
var action: (() -> Void)?
|
||||
|
||||
|
|
@ -251,22 +310,27 @@ public final class ListComposePollOptionComponent: Component {
|
|||
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
|
||||
if let current = self.checkLayer {
|
||||
checkLayer = current
|
||||
} 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.layer.addSublayer(checkLayer)
|
||||
}
|
||||
|
||||
|
||||
if self.theme !== theme {
|
||||
self.theme = theme
|
||||
|
||||
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.setSelected(isSelected, animated: !transition.animation.isImmediate)
|
||||
}
|
||||
|
|
@ -385,6 +449,13 @@ public final class ListComposePollOptionComponent: Component {
|
|||
|
||||
private var modeSelector: ComponentView<Empty>?
|
||||
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?
|
||||
|
||||
|
|
@ -396,6 +467,8 @@ public final class ListComposePollOptionComponent: Component {
|
|||
|
||||
private var customPlaceholder: ComponentView<Empty>?
|
||||
|
||||
private var appliedMedia: AnyMediaReference?
|
||||
|
||||
private var component: ListComposePollOptionComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var isUpdating: Bool = false
|
||||
|
|
@ -430,6 +503,25 @@ public final class ListComposePollOptionComponent: Component {
|
|||
|
||||
public override 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) {
|
||||
|
|
@ -551,6 +643,13 @@ public final class ListComposePollOptionComponent: Component {
|
|||
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 {
|
||||
self.isUpdating = true
|
||||
|
|
@ -570,6 +669,10 @@ public final class ListComposePollOptionComponent: Component {
|
|||
var rightInset: CGFloat = 16.0
|
||||
let modeSelectorSize = CGSize(width: 32.0, height: 32.0)
|
||||
|
||||
if component.hasLeftInset {
|
||||
leftInset += 46.0
|
||||
}
|
||||
|
||||
if component.selection != nil {
|
||||
leftInset += 34.0
|
||||
}
|
||||
|
|
@ -578,8 +681,8 @@ public final class ListComposePollOptionComponent: Component {
|
|||
rightInset += 34.0
|
||||
}
|
||||
|
||||
if component.canReorder {
|
||||
rightInset += 16.0
|
||||
if component.attachment != nil {
|
||||
rightInset += 28.0
|
||||
}
|
||||
|
||||
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 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 textFieldView.superview == nil {
|
||||
self.addSubview(textFieldView)
|
||||
|
|
@ -673,17 +857,17 @@ public final class ListComposePollOptionComponent: Component {
|
|||
}
|
||||
}
|
||||
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 {
|
||||
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.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 {
|
||||
transition.setPosition(view: checkView, position: checkFrame.center)
|
||||
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 {
|
||||
self.checkView = nil
|
||||
|
|
@ -692,40 +876,237 @@ public final class ListComposePollOptionComponent: Component {
|
|||
})
|
||||
}
|
||||
|
||||
var rightIconsInset: CGFloat = 0.0
|
||||
if component.canReorder, let externalState = component.externalState, externalState.hasText {
|
||||
var reorderIconTransition = transition
|
||||
let reorderIconView: UIImageView
|
||||
if let current = self.reorderIconView {
|
||||
reorderIconView = current
|
||||
var rightIconsInset: CGFloat = 16.0
|
||||
let minHeight: CGFloat = 52.0
|
||||
|
||||
if let attachment = component.attachment, attachment.alwaysDisplayAttachButton || component.externalState?.hasText == true {
|
||||
var attachButtonTransition = transition
|
||||
let attachButton: ComponentView<Empty>
|
||||
if let current = self.attachButton {
|
||||
attachButton = current
|
||||
} else {
|
||||
reorderIconTransition = reorderIconTransition.withAnimation(.none)
|
||||
reorderIconView = UIImageView()
|
||||
self.reorderIconView = reorderIconView
|
||||
self.addSubview(reorderIconView)
|
||||
attachButtonTransition = attachButtonTransition.withAnimation(.none)
|
||||
attachButton = ComponentView()
|
||||
self.attachButton = attachButton
|
||||
}
|
||||
reorderIconView.image = PresentationResourcesItemList.itemListReorderIndicatorIcon(component.theme)
|
||||
|
||||
var reorderIconSize = CGSize()
|
||||
if let icon = reorderIconView.image {
|
||||
reorderIconSize = icon.size
|
||||
|
||||
let attachButtonSize = attachButton.update(
|
||||
transition: attachButtonTransition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
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)
|
||||
reorderIconTransition.setPosition(view: reorderIconView, position: reorderIconFrame.center)
|
||||
reorderIconTransition.setBounds(view: reorderIconView, bounds: CGRect(origin: CGPoint(), size: reorderIconFrame.size))
|
||||
rightIconsInset += 42.0
|
||||
} else if let attachButton = self.attachButton {
|
||||
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
|
||||
} else if let reorderIconView = self.reorderIconView {
|
||||
self.reorderIconView = nil
|
||||
let animationSize = CGSize(width: 40.0, height: 40.0)
|
||||
let animationLayer: InlineStickerItemLayer
|
||||
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 {
|
||||
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
alphaTransition.setAlpha(view: reorderIconView, alpha: 0.0, completion: { [weak reorderIconView] _ in
|
||||
reorderIconView?.removeFromSuperview()
|
||||
alphaTransition.setAlpha(layer: animationLayer, alpha: 0.0, completion: { [weak animationLayer] _ in
|
||||
animationLayer?.removeFromSuperlayer()
|
||||
})
|
||||
alphaTransition.setScale(view: reorderIconView, scale: 0.001)
|
||||
alphaTransition.setScale(layer: animationLayer, scale: 0.001)
|
||||
} 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: {},
|
||||
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 {
|
||||
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
|
||||
|
|
@ -881,6 +1262,10 @@ public final class ListComposePollOptionComponent: Component {
|
|||
var leftInset: CGFloat = 16.0
|
||||
let rightInset: CGFloat = 16.0
|
||||
|
||||
if component.hasLeftInset {
|
||||
leftInset += 46.0
|
||||
}
|
||||
|
||||
if component.selection != nil {
|
||||
leftInset += 34.0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||
self.customTitle = self.presentationData.strings.RequestPeer_ChooseGroupTitle
|
||||
case .channel:
|
||||
self.customTitle = self.presentationData.strings.RequestPeer_ChooseChannelTitle
|
||||
case .createBot:
|
||||
break
|
||||
}
|
||||
} else {
|
||||
self.customTitle = self.presentationData.strings.ChatImport_Title
|
||||
|
|
|
|||
|
|
@ -1210,6 +1210,10 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
emptyText = ""
|
||||
}
|
||||
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)
|
||||
|
|
@ -1863,6 +1867,8 @@ private func stringForRequestPeerType(strings: PresentationStrings, peerType: Re
|
|||
append(rightsString)
|
||||
}
|
||||
}
|
||||
case .createBot:
|
||||
break
|
||||
}
|
||||
if lines.isEmpty {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/PhoneNumberFormat",
|
||||
"//submodules/ComposePollUI",
|
||||
"//submodules/TelegramIntents",
|
||||
"//submodules/LegacyUI",
|
||||
"//submodules/WebSearchUI",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import ChatEntityKeyboardInputNode
|
|||
import ChatScheduleTimeController
|
||||
import TextFormat
|
||||
import PhoneNumberFormat
|
||||
import ComposePollUI
|
||||
import TelegramIntents
|
||||
import LegacyUI
|
||||
import WebSearchUI
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
|||
import ShareController
|
||||
import UrlEscaping
|
||||
import ContextUI
|
||||
import ComposePollUI
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import UndoUI
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
|||
import ShareController
|
||||
import UrlEscaping
|
||||
import ContextUI
|
||||
import ComposePollUI
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import UndoUI
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
|||
import ShareController
|
||||
import UrlEscaping
|
||||
import ContextUI
|
||||
import ComposePollUI
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import UndoUI
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
|||
import ShareController
|
||||
import UrlEscaping
|
||||
import ContextUI
|
||||
import ComposePollUI
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import UndoUI
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
|||
import ShareController
|
||||
import UrlEscaping
|
||||
import ContextUI
|
||||
import ComposePollUI
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import UndoUI
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import DeviceLocationManager
|
|||
import ShareController
|
||||
import UrlEscaping
|
||||
import ContextUI
|
||||
import ComposePollUI
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import UndoUI
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ import DeviceLocationManager
|
|||
import ShareController
|
||||
import UrlEscaping
|
||||
import ContextUI
|
||||
import ComposePollUI
|
||||
import ComposeTodoScreen
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import UndoUI
|
||||
|
|
@ -4625,6 +4623,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
createNewGroupImpl = { [weak controller] in
|
||||
switch peerType {
|
||||
case .createBot:
|
||||
break
|
||||
case .user:
|
||||
break
|
||||
case let .group(group):
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import MediaEditorScreen
|
|||
import CameraScreen
|
||||
import ShareController
|
||||
import ComposeTodoScreen
|
||||
import ComposePollUI
|
||||
import ComposePollScreen
|
||||
import Photos
|
||||
import AttachmentFileController
|
||||
|
||||
|
|
@ -1291,26 +1291,6 @@ extension ChatControllerImpl {
|
|||
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
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time, repeatPeriod in
|
||||
|
|
@ -2049,9 +2029,15 @@ extension ChatControllerImpl {
|
|||
})
|
||||
}
|
||||
}, nil)
|
||||
|
||||
var attributes: [MessageAttribute] = []
|
||||
if !poll.description.entities.isEmpty {
|
||||
attributes.append(TextEntitiesMessageAttribute(entities: poll.description.entities))
|
||||
}
|
||||
|
||||
let message: EnqueueMessage = .message(
|
||||
text: "",
|
||||
attributes: [],
|
||||
text: poll.description.string,
|
||||
attributes: attributes,
|
||||
inlineStickers: [:],
|
||||
mediaReference: .standalone(media: TelegramMediaPoll(
|
||||
pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min...Int64.max)),
|
||||
|
|
@ -2063,7 +2049,12 @@ extension ChatControllerImpl {
|
|||
correctAnswers: poll.correctAnswers,
|
||||
results: poll.results,
|
||||
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,
|
||||
replyToMessageId: nil,
|
||||
|
|
|
|||
|
|
@ -1589,7 +1589,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||
hasSelected = true
|
||||
}
|
||||
}
|
||||
if hasSelected, case .poll = activePoll.kind {
|
||||
if hasSelected, !activePoll.revotingDisabled {
|
||||
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)
|
||||
}, action: { _, f in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue