diff --git a/TelegramUI/ChatInterfaceStateContextMenus.swift b/TelegramUI/ChatInterfaceStateContextMenus.swift index 4298d292c6..45141d08d8 100644 --- a/TelegramUI/ChatInterfaceStateContextMenus.swift +++ b/TelegramUI/ChatInterfaceStateContextMenus.swift @@ -400,7 +400,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: var activePoll: TelegramMediaPoll? for media in message.media { - if let poll = media as? TelegramMediaPoll, !poll.isClosed { + if let poll = media as? TelegramMediaPoll, !poll.isClosed, message.id.namespace == Namespaces.Message.Cloud, poll.pollId.namespace == Namespaces.Media.CloudPoll { activePoll = poll } } @@ -429,8 +429,34 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: interfaceInteraction.unpinMessage() }))) } + } + + if let _ = activePoll, messages[0].forwardInfo == nil { + var canStopPoll = false + if !messages[0].flags.contains(.Incoming) { + canStopPoll = true + } else { + var hasEditRights = false + if messages[0].id.namespace == Namespaces.Message.Cloud { + if message.id.peerId.namespace == Namespaces.Peer.SecretChat { + hasEditRights = false + } else if let author = message.author, author.id == account.peerId { + hasEditRights = true + } else if message.author?.id == message.id.peerId, let peer = message.peers[message.id.peerId] { + if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if peer.hasAdminRights(.canEditMessages) { + hasEditRights = true + } + } + } + } + + if hasEditRights { + canStopPoll = true + } + } - if let _ = activePoll, messages[0].forwardInfo == nil, !messages[0].flags.contains(.Incoming) { + if canStopPoll { actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .accent, title: chatPresentationInterfaceState.strings.Conversation_StopPoll, action: { interfaceInteraction.requestStopPollInMessage(messages[0].id) }))) diff --git a/TelegramUI/ChatMessagePollBubbleContentNode.swift b/TelegramUI/ChatMessagePollBubbleContentNode.swift index 326980bd0c..a5c4f1f212 100644 --- a/TelegramUI/ChatMessagePollBubbleContentNode.swift +++ b/TelegramUI/ChatMessagePollBubbleContentNode.swift @@ -203,13 +203,13 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode { } } -private let percentageFont = Font.bold(14.0) +private let percentageFont = Font.bold(14.5) private func generatePercentageImage(presentationData: ChatPresentationData, incoming: Bool, value: CGFloat) -> UIImage { return generateImage(CGSize(width: 42.0, height: 20.0), rotatedContext: { size, context in UIGraphicsPushContext(context) context.clear(CGRect(origin: CGPoint(), size: size)) - let percents = Int(value * 100.0) + let percents = Int(round(value * 100.0)) let string = NSAttributedString(string: "\(percents)%", font: percentageFont, textColor: incoming ? presentationData.theme.theme.chat.bubble.incomingPrimaryTextColor : presentationData.theme.theme.chat.bubble.outgoingPrimaryTextColor, paragraphAlignment: .right) string.draw(in: CGRect(origin: CGPoint(x: 0.0, y: 2.0), size: size)) UIGraphicsPopContext() @@ -300,7 +300,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { return { accountPeerId, presentationData, message, option, optionResult, constrainedWidth in let leftInset: CGFloat = 50.0 - let rightInset: CGFloat = 18.0 + let rightInset: CGFloat = 12.0 let incoming = message.effectivelyIncoming(accountPeerId) @@ -336,7 +336,11 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { node.addSubnode(titleNode) titleNode.isUserInteractionEnabled = false } - titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) + if titleLayout.hasRTL { + titleNode.frame = CGRect(origin: CGPoint(x: width - rightInset - titleLayout.size.width, y: 11.0), size: titleLayout.size) + } else { + titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) + } if shouldHaveRadioNode { let radioNode: ChatMessagePollOptionRadioNode @@ -368,11 +372,12 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { if let image = node.percentageImage { node.percentageNode.frame = CGRect(origin: CGPoint(x: leftInset - 7.0 - image.size.width, y: 12.0), size: image.size) if animated, let optionResult = optionResult { - let images = generatePercentageAnimationImages(presentationData: presentationData, incoming: incoming, from: previousResult?.absolute ?? 0.0, to: optionResult.absolute, duration: 0.4) + let percentageDuration = 0.27 + let images = generatePercentageAnimationImages(presentationData: presentationData, incoming: incoming, from: previousResult?.absolute ?? 0.0, to: optionResult.absolute, duration: percentageDuration) if !images.isEmpty { let animation = CAKeyframeAnimation(keyPath: "contents") animation.values = images.map { $0.cgImage! } - animation.duration = 0.4 * UIView.animationDurationFactor() + animation.duration = percentageDuration * UIView.animationDurationFactor() animation.calculationMode = kCAAnimationDiscrete node.percentageNode.layer.add(animation, forKey: "image") } @@ -776,7 +781,11 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.statusNode.removeFromSupernode() } - strongSelf.textNode.frame = textFrame + if textLayout.hasRTL { + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: resultSize.width - textFrame.size.width - textInsets.left - layoutConstants.text.bubbleInsets.right, y: textFrame.origin.y), size: textFrame.size) + } else { + strongSelf.textNode.frame = textFrame + } strongSelf.typeNode.frame = CGRect(origin: CGPoint(x: textFrame.minX, y: textFrame.maxY + titleTypeSpacing), size: typeLayout.size) let _ = votersApply() diff --git a/TelegramUI/LegacyAttachmentMenu.swift b/TelegramUI/LegacyAttachmentMenu.swift index 248ca22c7b..5126fc0a03 100644 --- a/TelegramUI/LegacyAttachmentMenu.swift +++ b/TelegramUI/LegacyAttachmentMenu.swift @@ -101,7 +101,7 @@ func legacyAttachmentMenu(account: Account, peer: Peer, editMediaOptions: Messag })! itemViews.append(locationItem) - if !(peer is TelegramSecretChat) && canSendMessagesToPeer(peer) { + if (peer is TelegramGroup || peer is TelegramChannel) && canSendMessagesToPeer(peer) { let pollItem = TGMenuSheetButtonItemView(title: strings.AttachmentMenu_Poll, type: TGMenuSheetButtonTypeDefault, action: { [weak controller] in controller?.dismiss(animated: true) openPoll() diff --git a/TelegramUI/ShareController.swift b/TelegramUI/ShareController.swift index 7df2d2b6b9..cb51915c45 100644 --- a/TelegramUI/ShareController.swift +++ b/TelegramUI/ShareController.swift @@ -79,7 +79,7 @@ private struct CollectableExternalShareItem { let mediaReference: AnyMediaReference? } -private func collectExternalShareItems(postbox: Postbox, collectableItems: [CollectableExternalShareItem]) -> Signal { +private func collectExternalShareItems(strings: PresentationStrings, postbox: Postbox, collectableItems: [CollectableExternalShareItem]) -> Signal { var signals: [Signal] = [] for item in collectableItems { if let mediaReference = item.mediaReference, let file = mediaReference.media as? TelegramMediaFile { @@ -135,6 +135,19 @@ private func collectExternalShareItems(postbox: Postbox, collectableItems: [Coll } } }) + } else if let mediaReference = item.mediaReference, let poll = mediaReference.media as? TelegramMediaPoll { + var text = "šŸ“Š \(poll.text)" + text.append("\n\(strings.MessagePoll_LabelAnonymous)") + for option in poll.options { + text.append("\n— \(option.text)") + } + let totalVoters = poll.results.totalVoters ?? 0 + if totalVoters == 0 { + text.append("\n\(strings.MessagePoll_NoVotes)") + } else { + text.append("\n\(strings.MessagePoll_VotedCount(totalVoters))") + } + signals.append(.single(.done(.text(text)))) } if let url = item.url, let parsedUrl = URL(string: url) { if signals.isEmpty { @@ -454,6 +467,9 @@ public final class ShareController: ViewController { selectedMedia = image } } + case let _ as TelegramMediaPoll: + selectedMedia = media + break loop default: break } @@ -468,7 +484,7 @@ public final class ShareController: ViewController { case .fromExternal: break } - return (collectExternalShareItems(postbox: strongSelf.account.postbox, collectableItems: collectableItems) |> deliverOnMainQueue) |> map { state in + return (collectExternalShareItems(strings: strongSelf.presentationData.strings, postbox: strongSelf.account.postbox, collectableItems: collectableItems) |> deliverOnMainQueue) |> map { state in switch state { case .progress: return .preparing