Poll improvements

This commit is contained in:
Ali 2020-01-14 16:08:48 +04:00
parent d775a5f178
commit 0fa6de6410
59 changed files with 4583 additions and 3953 deletions

View File

@ -5274,3 +5274,7 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.StopQuiz" = "Stop Quiz";
"Conversation.StopQuizConfirmationTitle" = "If you stop this quiz now, nobody will be able to submit answers. This action cannot be undone.";
"Conversation.StopQuizConfirmation" = "Stop Quiz";
"Forward.ErrorDisabledForChat" = "Sorry, you can't forward messages to this chat.";
"Forward.ErrorPublicPollDisabledInChannels" = "Sorry, you can't forward public poll to the channel.";
"Forward.ErrorPublicQuizDisabledInChannels" = "Sorry, you can't forward public quiz to the channel.";

View File

@ -25,6 +25,8 @@ public struct ChatListNodePeersFilter: OptionSet {
public static let excludeDisabled = ChatListNodePeersFilter(rawValue: 1 << 10)
public static let includeSavedMessages = ChatListNodePeersFilter(rawValue: 1 << 11)
public static let excludeChannels = ChatListNodePeersFilter(rawValue: 1 << 12)
}
public final class PeerSelectionControllerParams {
@ -32,12 +34,14 @@ public final class PeerSelectionControllerParams {
public let filter: ChatListNodePeersFilter
public let hasContactSelector: Bool
public let title: String?
public let attemptSelection: ((Peer) -> Void)?
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasContactSelector: Bool = true, title: String? = nil) {
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasContactSelector: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil) {
self.context = context
self.filter = filter
self.hasContactSelector = hasContactSelector
self.title = title
self.attemptSelection = attemptSelection
}
}

View File

@ -837,6 +837,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
}
if filter.contains(.excludeChannels) {
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
return false
}
}
return true
}
@ -962,6 +968,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
openPeer(peer, false)
let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start()
self?.listNode.clearHighlightAnimated(true)
}, disabledPeerSelected: { _ in
}, togglePeerSelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in
self?.view.endEditing(true)

View File

@ -46,6 +46,7 @@ final class ChatListHighlightedLocation {
public final class ChatListNodeInteraction {
let activateSearch: () -> Void
let peerSelected: (Peer) -> Void
let disabledPeerSelected: (Peer) -> Void
let togglePeerSelected: (PeerId) -> Void
let messageSelected: (Peer, Message, Bool) -> Void
let groupSelected: (PeerGroupId) -> Void
@ -62,9 +63,10 @@ public final class ChatListNodeInteraction {
public var searchTextHighightState: String?
var highlightedChatLocation: ChatListHighlightedLocation?
public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void) {
public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void) {
self.activateSearch = activateSearch
self.peerSelected = peerSelected
self.disabledPeerSelected = disabledPeerSelected
self.togglePeerSelected = togglePeerSelected
self.messageSelected = messageSelected
self.groupSelected = groupSelected
@ -202,11 +204,22 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
enabled = false
}
}
if filter.contains(.excludeChannels) {
if let peer = peer.peers[peer.peerId] {
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
enabled = false
}
}
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
if let chatPeer = chatPeer {
nodeInteraction.peerSelected(chatPeer)
}
}, disabledAction: { _ in
if let chatPeer = chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer)
}
}), directionHint: entry.directionHint)
}
case let .HoleEntry(_, theme):
@ -242,10 +255,19 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
enabled = false
}
}
if filter.contains(.excludeChannels) {
if let peer = peer.chatMainPeer as? TelegramChannel, case .broadcast = peer.info {
enabled = false
}
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
if let chatPeer = chatPeer {
nodeInteraction.peerSelected(chatPeer)
}
}, disabledAction: { _ in
if let chatPeer = chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer)
}
}), directionHint: entry.directionHint)
}
case let .HoleEntry(_, theme):
@ -318,6 +340,7 @@ public final class ChatListNode: ListView {
}
public var peerSelected: ((PeerId, Bool, Bool) -> Void)?
public var disabledPeerSelected: ((Peer) -> Void)?
public var groupSelected: ((PeerGroupId) -> Void)?
public var addContact: ((String) -> Void)?
public var activateSearch: (() -> Void)?
@ -409,6 +432,10 @@ public final class ChatListNode: ListView {
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
peerSelected(peer.id, true, false)
}
}, disabledPeerSelected: { [weak self] peer in
if let strongSelf = self, let disabledPeerSelected = strongSelf.disabledPeerSelected {
disabledPeerSelected(peer)
}
}, togglePeerSelected: { [weak self] peerId in
self?.updateState { state in
var state = state
@ -584,6 +611,11 @@ public final class ChatListNode: ListView {
}
}
if filter.contains(.excludeChannels) {
if let peer = peer.chatMainPeer as? TelegramChannel, case .broadcast = peer.info {
}
}
if filter.contains(.onlyWriteable) && filter.contains(.excludeDisabled) {
if let peer = peer.peers[peer.peerId] {
if !canSendMessagesToPeer(peer) {

View File

@ -122,6 +122,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
let options: [ItemListPeerItemRevealOption]
let actionIcon: ContactsPeerItemActionIcon
let action: (ContactsPeerItemPeer) -> Void
let disabledAction: ((ContactsPeerItemPeer) -> Void)?
let setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)?
let deletePeer: ((PeerId) -> Void)?
let itemHighlighting: ContactItemHighlighting?
@ -133,7 +134,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
public let header: ListViewItemHeader?
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
self.presentationData = presentationData
self.style = style
self.sectionId = sectionId
@ -150,11 +151,12 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
self.options = options
self.actionIcon = actionIcon
self.action = action
self.disabledAction = disabledAction
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.deletePeer = deletePeer
self.header = header
self.itemHighlighting = itemHighlighting
self.selectable = enabled
self.selectable = enabled || disabledAction != nil
self.contextAction = contextAction
if let index = index {
@ -245,7 +247,12 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
}
public func selected(listView: ListView) {
if self.enabled {
self.action(self.peer)
} else {
listView.clearHighlightAnimated(true)
self.disabledAction?(self.peer)
}
}
static func mergeType(item: ContactsPeerItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) {

View File

@ -49,6 +49,7 @@ public final class HashtagSearchController: TelegramBaseController {
}
let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { peer in
}, disabledPeerSelected: { _ in
}, togglePeerSelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in
if let strongSelf = self {

View File

@ -75,8 +75,8 @@ private final class ShimmerEffectNode: ASDisplayNode {
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
let peakColor = foregroundColor.cgColor
var locations: [CGFloat] = [0.0, 0.2, 0.5, 0.8, 1.0]
let colors: [CGColor] = [transparentColor, transparentColor, peakColor, transparentColor, transparentColor]
var locations: [CGFloat] = [0.0, 0.5, 1.0]
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
@ -1312,6 +1312,7 @@ public final class ItemListPeerItemHeaderNode: ListViewItemHeaderNode {
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.maximumNumberOfLines = 1
self.textNode.truncationType = .middle
self.textNode.attributedText = NSAttributedString(string: text, font: titleFont, textColor: theme.list.sectionHeaderTextColor)
self.actionTextNode = ImmediateTextNode()

View File

@ -56,6 +56,7 @@ public struct ItemListBackButton: Equatable {
public enum ItemListControllerTitle: Equatable {
case text(String)
case textWithSubtitle(String, String)
case sectionControl([String], Int)
}
@ -287,6 +288,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
strongSelf.title = text
strongSelf.navigationItem.titleView = nil
strongSelf.segmentedTitleView = nil
case let .textWithSubtitle(title, subtitle):
strongSelf.title = ""
strongSelf.navigationItem.titleView = ItemListTextWithSubtitleTitleView(theme: controllerState.presentationData.theme, title: title, subtitle: subtitle)
strongSelf.segmentedTitleView = nil
case let .sectionControl(sections, index):
strongSelf.title = ""
if let segmentedTitleView = strongSelf.segmentedTitleView, segmentedTitleView.segments == sections {
@ -417,6 +422,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
strongSelf.segmentedTitleView?.theme = controllerState.presentationData.theme
if let titleView = strongSelf.navigationItem.titleView as? ItemListTextWithSubtitleTitleView {
titleView.updateTheme(theme: controllerState.presentationData.theme)
}
var items = strongSelf.navigationItem.rightBarButtonItems ?? []
for i in 0 ..< strongSelf.rightNavigationButtonTitleAndStyle.count {
if case .activity = strongSelf.rightNavigationButtonTitleAndStyle[i].1 {
@ -602,3 +611,68 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
})]
}
}
private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitleView {
private let titleNode: ImmediateTextNode
private let subtitleNode: ImmediateTextNode
private var validLayout: (CGSize, CGRect)?
init(theme: PresentationTheme, title: String, subtitle: String) {
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.titleNode.maximumNumberOfLines = 1
self.titleNode.isOpaque = false
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode = ImmediateTextNode()
self.subtitleNode.displaysAsynchronously = false
self.subtitleNode.maximumNumberOfLines = 1
self.subtitleNode.isOpaque = false
self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
super.init(frame: CGRect())
self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateTheme(theme: PresentationTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
}
}
override func layoutSubviews() {
super.layoutSubviews()
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
}
}
func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, clearBounds)
let titleSize = self.titleNode.updateLayout(size)
let subtitleSize = self.subtitleNode.updateLayout(size)
let spacing: CGFloat = 0.0
let contentHeight = titleSize.height + spacing + subtitleSize.height
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - contentHeight) / 2.0)), size: titleSize)
let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + spacing), size: subtitleSize)
self.titleNode.frame = titleFrame
self.subtitleNode.frame = subtitleFrame
}
func animateLayoutTransition() {
}
}

View File

@ -37,6 +37,17 @@ final class ItemCacheTable: Table {
return key
}
private func lowerBound(collectionId: ItemCacheCollectionId) -> ValueBoxKey {
let key = ValueBoxKey(length: 1 + 1)
key.setInt8(0, value: ItemCacheSection.items.rawValue)
key.setInt8(1, value: collectionId)
return key
}
private func upperBound(collectionId: ItemCacheCollectionId) -> ValueBoxKey {
return self.lowerBound(collectionId: collectionId).successor
}
private func itemIdToAccessIndexKey(id: ItemCacheEntryId) -> ValueBoxKey {
let key = ValueBoxKey(length: 1 + 1 + id.key.length)
key.setInt8(0, value: ItemCacheSection.accessIndexToItemId.rawValue)
@ -72,6 +83,10 @@ final class ItemCacheTable: Table {
self.valueBox.remove(self.table, key: self.itemKey(id: id), secure: false)
}
func removeAll(collectionId: ItemCacheCollectionId) {
self.valueBox.removeRange(self.table, start: self.lowerBound(collectionId: collectionId), end: self.upperBound(collectionId: collectionId))
}
override func clearMemoryCache() {
}

View File

@ -655,6 +655,11 @@ public final class Transaction {
return self.postbox?.retrieveItemCacheEntry(id: id)
}
public func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
assert(!self.disposed)
self.postbox?.clearItemCacheCollection(collectionId: collectionId)
}
public func operationLogGetNextEntryLocalIndex(peerId: PeerId, tag: PeerOperationLogTag) -> Int32 {
assert(!self.disposed)
if let postbox = self.postbox {
@ -2065,6 +2070,10 @@ public final class Postbox {
return self.itemCacheTable.retrieve(id: id, metaTable: self.itemCacheMetaTable)
}
func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
return self.itemCacheTable.removeAll(collectionId: collectionId)
}
fileprivate func removeItemCacheEntry(id: ItemCacheEntryId) {
self.itemCacheTable.remove(id: id, metaTable: self.itemCacheMetaTable)
}

View File

@ -393,6 +393,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in
transaction.clearNotices()
}).start()
if let context = arguments.context {
let _ = (context.account.postbox.transaction { transaction -> Void in
transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedPollResults)
}).start()
}
})
case let .reimport(theme):
return ItemListActionItem(presentationData: presentationData, title: "Reimport Application Data", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {

View File

@ -213,7 +213,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
gesture?.cancel()
})
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)

View File

@ -765,7 +765,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
gesture?.cancel()
})
let chatListPresentationData = ChatListPresentationData(theme: self.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)

View File

@ -350,7 +350,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
gesture?.cancel()
})
let chatListPresentationData = ChatListPresentationData(theme: self.previewTheme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)

View File

@ -246,6 +246,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-2112423005] = { return Api.Update.parse_updateTheme($0) }
dict[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($0) }
dict[1448076945] = { return Api.Update.parse_updateLoginToken($0) }
dict[1123585836] = { return Api.Update.parse_updateMessagePollVote($0) }
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
@ -254,7 +255,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[470789295] = { return Api.ChannelParticipant.parse_channelParticipantBanned($0) }
dict[-859915345] = { return Api.ChannelParticipant.parse_channelParticipantAdmin($0) }
dict[-2138237532] = { return Api.ChannelParticipant.parse_channelParticipantCreator($0) }
dict[-233638547] = { return Api.MessageUserVote.parse_messageUserVote($0) }
dict[-1567730343] = { return Api.MessageUserVote.parse_messageUserVote($0) }
dict[909603888] = { return Api.MessageUserVote.parse_messageUserVoteInputOption($0) }
dict[244310238] = { return Api.MessageUserVote.parse_messageUserVoteMultiple($0) }
dict[471043349] = { return Api.contacts.Blocked.parse_blocked($0) }
dict[-1878523231] = { return Api.contacts.Blocked.parse_blockedSlice($0) }
dict[-55902537] = { return Api.InputDialogPeer.parse_inputDialogPeer($0) }

View File

@ -5860,6 +5860,7 @@ public extension Api {
case updateTheme(theme: Api.Theme)
case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32)
case updateLoginToken
case updateMessagePollVote(pollId: Int64, userId: Int32, options: [Buffer])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -6496,6 +6497,18 @@ public extension Api {
buffer.appendInt32(1448076945)
}
break
case .updateMessagePollVote(let pollId, let userId, let options):
if boxed {
buffer.appendInt32(1123585836)
}
serializeInt64(pollId, buffer: buffer, boxed: false)
serializeInt32(userId, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(options.count))
for item in options {
serializeBytes(item, buffer: buffer, boxed: false)
}
break
}
}
@ -6654,6 +6667,8 @@ public extension Api {
return ("updateGeoLiveViewed", [("peer", peer), ("msgId", msgId)])
case .updateLoginToken:
return ("updateLoginToken", [])
case .updateMessagePollVote(let pollId, let userId, let options):
return ("updateMessagePollVote", [("pollId", pollId), ("userId", userId), ("options", options)])
}
}
@ -7931,6 +7946,25 @@ public extension Api {
public static func parse_updateLoginToken(_ reader: BufferReader) -> Update? {
return Api.Update.updateLoginToken
}
public static func parse_updateMessagePollVote(_ reader: BufferReader) -> Update? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int32?
_2 = reader.readInt32()
var _3: [Buffer]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.Update.updateMessagePollVote(pollId: _1!, userId: _2!, options: _3!)
}
else {
return nil
}
}
}
public enum PopularContact: TypeConstructorDescription {
@ -8190,24 +8224,50 @@ public extension Api {
}
public enum MessageUserVote: TypeConstructorDescription {
case messageUserVote(userId: Int32, option: Buffer)
case messageUserVote(userId: Int32, option: Buffer, date: Int32)
case messageUserVoteInputOption(userId: Int32, date: Int32)
case messageUserVoteMultiple(userId: Int32, options: [Buffer], date: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageUserVote(let userId, let option):
case .messageUserVote(let userId, let option, let date):
if boxed {
buffer.appendInt32(-233638547)
buffer.appendInt32(-1567730343)
}
serializeInt32(userId, buffer: buffer, boxed: false)
serializeBytes(option, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
break
case .messageUserVoteInputOption(let userId, let date):
if boxed {
buffer.appendInt32(909603888)
}
serializeInt32(userId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
break
case .messageUserVoteMultiple(let userId, let options, let date):
if boxed {
buffer.appendInt32(244310238)
}
serializeInt32(userId, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(options.count))
for item in options {
serializeBytes(item, buffer: buffer, boxed: false)
}
serializeInt32(date, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageUserVote(let userId, let option):
return ("messageUserVote", [("userId", userId), ("option", option)])
case .messageUserVote(let userId, let option, let date):
return ("messageUserVote", [("userId", userId), ("option", option), ("date", date)])
case .messageUserVoteInputOption(let userId, let date):
return ("messageUserVoteInputOption", [("userId", userId), ("date", date)])
case .messageUserVoteMultiple(let userId, let options, let date):
return ("messageUserVoteMultiple", [("userId", userId), ("options", options), ("date", date)])
}
}
@ -8216,10 +8276,46 @@ public extension Api {
_1 = reader.readInt32()
var _2: Buffer?
_2 = parseBytes(reader)
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.MessageUserVote.messageUserVote(userId: _1!, option: _2!, date: _3!)
}
else {
return nil
}
}
public static func parse_messageUserVoteInputOption(_ reader: BufferReader) -> MessageUserVote? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageUserVote.messageUserVote(userId: _1!, option: _2!)
return Api.MessageUserVote.messageUserVoteInputOption(userId: _1!, date: _2!)
}
else {
return nil
}
}
public static func parse_messageUserVoteMultiple(_ reader: BufferReader) -> MessageUserVote? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Buffer]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self)
}
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.MessageUserVote.messageUserVoteMultiple(userId: _1!, options: _2!, date: _3!)
}
else {
return nil

View File

@ -250,11 +250,17 @@ private final class PollResultsOptionContext {
})
var resultPeers: [RenderedPeer] = []
for vote in votes {
let peerId: PeerId
switch vote {
case let .messageUserVote(userId, _):
if let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)) {
resultPeers.append(RenderedPeer(peer: peer))
case let .messageUserVote(userId, _, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
case let .messageUserVoteInputOption(userId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
case let .messageUserVoteMultiple(userId, _, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
}
if let peer = transaction.getPeer(peerId) {
resultPeers.append(RenderedPeer(peer: peer))
}
}
if populateCache {

View File

@ -343,7 +343,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
primaryColor: UIColor(rgb: 0xffffff),
controlColor: UIColor(rgb: 0x4d4d4d)
),
mediaPlaceholderColor: UIColor(rgb: 0xffffff).withMultipliedBrightnessBy(0.05),
mediaPlaceholderColor: UIColor(rgb: 0xffffff).mixedWith(UIColor(rgb: 0x1c1c1d), alpha: 0.9),
scrollIndicatorColor: UIColor(rgb: 0xffffff, alpha: 0.3),
pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3),
inputClearButtonColor: UIColor(rgb: 0x8b9197),

View File

@ -154,7 +154,7 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme
backgroundColor: mainSecondaryTextColor?.withAlphaComponent(0.5),
strokeColor: mainSecondaryTextColor?.withAlphaComponent(0.5)
),
mediaPlaceholderColor: accentColor?.withMultiplied(hue: 1.019, saturation: 0.585, brightness: 0.23),
mediaPlaceholderColor: UIColor(rgb: 0xffffff).mixedWith(mainBackgroundColor ?? list.itemBlocksBackgroundColor, alpha: 0.9),
pageIndicatorInactiveColor: mainSecondaryTextColor?.withAlphaComponent(0.4),
inputClearButtonColor: mainSecondaryColor,
itemBarChart: list.itemBarChart.withUpdated(
@ -597,7 +597,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres
primaryColor: .white,
controlColor: UIColor(rgb: 0x4d4d4d)
),
mediaPlaceholderColor: accentColor.withMultiplied(hue: 1.019, saturation: 0.585, brightness: 0.23),
mediaPlaceholderColor: UIColor(rgb: 0xffffff).mixedWith(mainBackgroundColor, alpha: 0.9),
scrollIndicatorColor: UIColor(white: 1.0, alpha: 0.3),
pageIndicatorInactiveColor: mainSecondaryTextColor.withAlphaComponent(0.4),
inputClearButtonColor: mainSecondaryColor,

View File

@ -271,7 +271,7 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti
fileDescriptionColor: outgoingFileDescriptionColor,
fileDurationColor: outgoingFileDurationColor,
mediaPlaceholderColor: day ? accentColor?.withMultipliedBrightnessBy(0.95) : outgoingMediaPlaceholderColor,
polls: chat.message.outgoing.polls.withUpdated(radioButton: outgoingPollsButtonColor, radioProgress: outgoingPollsProgressColor, highlight: outgoingPollsProgressColor?.withAlphaComponent(0.12), separator: outgoingPollsButtonColor, bar: outgoingPollsProgressColor, barPositive: outgoingPollsProgressColor, barNegative: outgoingPollsProgressColor),
polls: chat.message.outgoing.polls.withUpdated(radioButton: outgoingPollsButtonColor, radioProgress: outgoingPollsProgressColor, highlight: outgoingPollsProgressColor?.withAlphaComponent(0.12), separator: outgoingPollsButtonColor, bar: outgoingPollsProgressColor, barIconForeground: .clear, barPositive: outgoingPollsProgressColor, barNegative: outgoingPollsProgressColor),
actionButtonsFillColor: chat.message.outgoing.actionButtonsFillColor.withUpdated(withWallpaper: serviceBackgroundColor),
actionButtonsStrokeColor: day ? chat.message.outgoing.actionButtonsStrokeColor.withUpdated(withoutWallpaper: accentColor) : nil,
actionButtonsTextColor: day ? chat.message.outgoing.actionButtonsTextColor.withUpdated(withoutWallpaper: accentColor) : nil,
@ -440,7 +440,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
primaryColor: UIColor(rgb: 0x000000),
controlColor: UIColor(rgb: 0xbcbcc0)
),
mediaPlaceholderColor: UIColor(rgb: 0xe4e4e4),
mediaPlaceholderColor: UIColor(rgb: 0xEFEFF4),
scrollIndicatorColor: UIColor(white: 0.0, alpha: 0.3),
pageIndicatorInactiveColor: UIColor(rgb: 0xe3e3e7),
inputClearButtonColor: UIColor(rgb: 0xcccccc),

View File

@ -1065,7 +1065,7 @@ extension PresentationThemeChatBubblePolls: Codable {
highlight: try decodeColor(values, .highlight),
separator: try decodeColor(values, .separator),
bar: bar,
barIconForeground: (try? decodeColor(values, .barIconForeground)) ?? .white,
barIconForeground: (try? decodeColor(values, .barIconForeground)) ?? .clear,
barPositive: (try? decodeColor(values, .barPositive)) ?? bar,
barNegative: (try? decodeColor(values, .barNegative)) ?? bar
)

View File

@ -111,7 +111,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
recognizer.tapActionAtPoint = { [weak self] point in
if let strongSelf = self {
let tapAction = strongSelf.tapActionAtPoint(point, gesture: .tap)
let tapAction = strongSelf.tapActionAtPoint(point, gesture: .tap, isEstimating: true)
switch tapAction {
case .none:
break
@ -264,7 +264,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
}
}
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - self.offsetContainer.frame.minX - textNodeFrame.minX, y: point.y - self.offsetContainer.frame.minY - textNodeFrame.minY)) {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
@ -295,7 +295,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .tap:
let tapAction = self.tapActionAtPoint(location, gesture: gesture)
let tapAction = self.tapActionAtPoint(location, gesture: gesture, isEstimating: false)
switch tapAction {
case .none, .ignore:
break
@ -314,7 +314,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
}
case .longTap, .doubleTap:
if let item = self.item, self.backgroundNode.frame.contains(location) {
let tapAction = self.tapActionAtPoint(location, gesture: gesture)
let tapAction = self.tapActionAtPoint(location, gesture: gesture, isEstimating: false)
switch tapAction {
case .none, .ignore:
break

View File

@ -1834,6 +1834,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
})
})
}, openMessagePollResults: { [weak self] messageId, optionOpaqueIdentifier in
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in
return transaction.getMessage(messageId)
}
|> deliverOnMainQueue).start(next: { message in
guard let message = message else {
return
}
for media in message.media {
if let poll = media as? TelegramMediaPoll, poll.pollId.namespace == Namespaces.Media.CloudPoll {
strongSelf.push(pollResultsController(context: strongSelf.context, messageId: messageId, poll: poll, focusOnOptionWithOpaqueIdentifier: optionOpaqueIdentifier))
break
}
}
})
}, requestMessageUpdate: { [weak self] id in
if let strongSelf = self {
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
@ -6868,7 +6886,48 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
private func forwardMessages(messageIds: [MessageId], resetCurrent: Bool = false) {
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled, .includeSavedMessages]))
let _ = (self.context.account.postbox.transaction { transaction -> [Message] in
return messageIds.compactMap(transaction.getMessage)
}
|> deliverOnMainQueue).start(next: { [weak self] messages in
self?.forwardMessages(messages: messages, resetCurrent: resetCurrent)
})
}
private func forwardMessages(messages: [Message], resetCurrent: Bool) {
var filter: ChatListNodePeersFilter = [.onlyWriteable, .includeSavedMessages, .excludeDisabled]
var hasPublicPolls = false
var hasPublicQuiz = false
for message in messages {
for media in message.media {
if let poll = media as? TelegramMediaPoll, case .public = poll.publicity {
hasPublicPolls = true
if case .quiz = poll.kind {
hasPublicQuiz = true
}
filter.insert(.excludeChannels)
break
}
}
}
var attemptSelectionImpl: ((Peer) -> Void)?
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: filter, attemptSelection: { peer in
attemptSelectionImpl?(peer)
}))
let context = self.context
attemptSelectionImpl = { [weak controller] peer in
guard let controller = controller else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if hasPublicPolls {
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
controller.present(textAlertController(context: context, title: nil, text: hasPublicQuiz ? presentationData.strings.Forward_ErrorPublicQuizDisabledInChannels : presentationData.strings.Forward_ErrorPublicPollDisabledInChannels, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return
}
}
controller.present(textAlertController(context: context, title: nil, text: presentationData.strings.Forward_ErrorDisabledForChat, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
controller.peerSelected = { [weak self, weak controller] peerId in
guard let strongSelf = self, let strongController = controller else {
return
@ -6879,11 +6938,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if case .peer(peerId) = strongSelf.chatLocation, strongSelf.parentController == nil {
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(messageIds).withoutSelectionState() }) })
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(messages.map { $0.id }).withoutSelectionState() }) })
strongController.dismiss()
} else if peerId == strongSelf.context.account.peerId {
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in
return .forward(source: id, grouping: .auto, attributes: [])
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messages.map { message -> EnqueueMessage in
return .forward(source: message.id, grouping: .auto, attributes: [])
})
|> deliverOnMainQueue).start(next: { messageIds in
if let strongSelf = self {
@ -6919,9 +6978,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in
transaction.updatePeerChatInterfaceState(peerId, update: { currentState in
if let currentState = currentState as? ChatInterfaceState {
return currentState.withUpdatedForwardMessageIds(messageIds)
return currentState.withUpdatedForwardMessageIds(messages.map { $0.id })
} else {
return ChatInterfaceState().withUpdatedForwardMessageIds(messageIds)
return ChatInterfaceState().withUpdatedForwardMessageIds(messages.map { $0.id })
}
})
}) |> deliverOnMainQueue).start(completed: {

View File

@ -104,6 +104,7 @@ public final class ChatControllerInteraction {
let openMessageReactions: (MessageId) -> Void
let displaySwipeToReplyHint: () -> Void
let dismissReplyMarkupMessage: (Message) -> Void
let openMessagePollResults: (MessageId, Data) -> Void
let requestMessageUpdate: (MessageId) -> Void
let cancelInteractiveKeyboardGestures: () -> Void
@ -118,7 +119,7 @@ public final class ChatControllerInteraction {
var searchTextHighightState: (String, [MessageIndex])?
var seenOneTimeAnimatedMedia = Set<MessageId>()
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String?) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String?) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
self.openMessage = openMessage
self.openPeer = openPeer
self.openPeerMention = openPeerMention
@ -174,6 +175,7 @@ public final class ChatControllerInteraction {
self.openMessageReactions = openMessageReactions
self.displaySwipeToReplyHint = displaySwipeToReplyHint
self.dismissReplyMarkupMessage = dismissReplyMarkupMessage
self.openMessagePollResults = openMessagePollResults
self.requestMessageUpdate = requestMessageUpdate
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
@ -213,6 +215,7 @@ public final class ChatControllerInteraction {
}, openMessageReactions: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -2326,7 +2326,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
func animateQuizCorrectOptionSelected() {
class ConfettiView: UIView {
self.view.insertSubview(ConfettiView(frame: self.view.bounds), aboveSubview: self.historyNode.view)
/*class ConfettiView: UIView {
private let direction: Bool
private let confettiViewEmitterLayer = CAEmitterLayer()
private let confettiViewEmitterCell = CAEmitterCell()
@ -2442,6 +2444,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
self.view.insertSubview(ConfettiView(frame: self.view.bounds, direction: true), aboveSubview: self.historyNode.view)
self.view.insertSubview(ConfettiView(frame: self.view.bounds, direction: false), aboveSubview: self.historyNode.view)
self.view.insertSubview(ConfettiView(frame: self.view.bounds, direction: false), aboveSubview: self.historyNode.view)*/
}
}

View File

@ -244,7 +244,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
}
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
let textNodeFrame = self.labelNode.frame
if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)), gesture == .tap {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {

View File

@ -983,7 +983,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
return false
}
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {

View File

@ -66,6 +66,10 @@ class ChatMessageBackground: ASDisplayNode {
private let imageNode: ASImageNode
private let outlineImageNode: ASImageNode
var hasImage: Bool {
self.imageNode.image != nil
}
override init() {
self.imageNode = ASImageNode()
self.imageNode.displaysAsynchronously = false

View File

@ -16,6 +16,10 @@ final class ChatMessageBubbleBackdrop: ASDisplayNode {
private var maskView: UIImageView?
var hasImage: Bool {
return self.backgroundContent.contents != nil
}
override var frame: CGRect {
didSet {
if let maskView = self.maskView {

View File

@ -162,7 +162,7 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
return nil
}
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
return .none
}

View File

@ -271,14 +271,26 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
for node in subnodes {
if let contextNode = node as? ContextExtractedContentContainingNode {
if let contextSubnodes = contextNode.contentNode.subnodes {
for contextSubnode in contextSubnodes {
inner: for contextSubnode in contextSubnodes {
if contextSubnode !== self.accessoryItemNode {
contextSubnode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
if contextSubnode == self.backgroundNode {
if self.backgroundNode.hasImage && self.backgroundWallpaperNode.hasImage {
continue inner
}
}
contextSubnode.layer.allowsGroupOpacity = true
contextSubnode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak contextSubnode] _ in
contextSubnode?.layer.allowsGroupOpacity = false
})
}
}
}
} else if node !== self.accessoryItemNode {
node.layer.allowsGroupOpacity = true
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak node] _ in
node?.layer.allowsGroupOpacity = false
})
}
}
}
@ -334,7 +346,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
return .waitForSingleTap
}
for contentNode in strongSelf.contentNodes {
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: point.x - contentNode.frame.minX, y: point.y - contentNode.frame.minY), gesture: .tap)
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: point.x - contentNode.frame.minX, y: point.y - contentNode.frame.minY), gesture: .tap, isEstimating: true)
switch tapAction {
case .none:
if let _ = strongSelf.item?.controllerInteraction.tapMessage {
@ -2305,7 +2317,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
var foundTapAction = false
loop: for contentNode in self.contentNodes {
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture)
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture, isEstimating: false)
switch tapAction {
case .none, .ignore:
if let item = self.item, self.backgroundNode.frame.contains(CGPoint(x: self.frame.width - location.x, y: location.y)), let tapMessage = self.item?.controllerInteraction.tapMessage {
@ -2394,7 +2406,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
selectAll = false
}
tapMessage = contentNode.item?.message
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture)
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture, isEstimating: false)
switch tapAction {
case .none, .ignore:
break

View File

@ -207,7 +207,7 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
}
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.buttonNode.frame.contains(point) {
return .ignore
} else if self.bounds.contains(point), let item = self.item {

View File

@ -328,7 +328,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.buttonNode.frame.contains(point) {
return .openMessage
}

View File

@ -84,7 +84,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.bounds.contains(point) {
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
if content.instantPage != nil {

View File

@ -79,7 +79,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.bounds.contains(point) {
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
if content.instantPage != nil {

View File

@ -84,10 +84,10 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.bounds.contains(point) {
let contentNodeFrame = self.contentNode.frame
return self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture)
return self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture, isEstimating: isEstimating)
}
return .none
}

View File

@ -112,7 +112,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.bounds.contains(point) {
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
if content.instantPage != nil {

View File

@ -115,7 +115,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.bounds.contains(point) {
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
if content.instantPage != nil {

View File

@ -460,7 +460,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
return mediaHidden
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
return .none
}

View File

@ -355,7 +355,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
return self.interactiveImageNode.playMediaWithSound()
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
return .none
}

View File

@ -318,13 +318,20 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode {
}
private let percentageFont = Font.bold(14.5)
private let percentageSmallFont = Font.bold(12.5)
private func generatePercentageImage(presentationData: ChatPresentationData, incoming: Bool, value: Int) -> UIImage {
private func generatePercentageImage(presentationData: ChatPresentationData, incoming: Bool, value: Int, targetValue: Int) -> UIImage {
return generateImage(CGSize(width: 42.0, height: 20.0), rotatedContext: { size, context in
UIGraphicsPushContext(context)
context.clear(CGRect(origin: CGPoint(), size: size))
let string = NSAttributedString(string: "\(value)%", font: percentageFont, textColor: incoming ? presentationData.theme.theme.chat.message.incoming.primaryTextColor : presentationData.theme.theme.chat.message.outgoing.primaryTextColor, paragraphAlignment: .right)
string.draw(in: CGRect(origin: CGPoint(x: 0.0, y: 2.0), size: size))
let font: UIFont
if targetValue == 100 {
font = percentageSmallFont
} else {
font = percentageFont
}
let string = NSAttributedString(string: "\(value)%", font: font, textColor: incoming ? presentationData.theme.theme.chat.message.incoming.primaryTextColor : presentationData.theme.theme.chat.message.outgoing.primaryTextColor, paragraphAlignment: .right)
string.draw(in: CGRect(origin: CGPoint(x: 0.0, y: targetValue == 100 ? 3.0 : 2.0), size: size))
UIGraphicsPopContext()
})!
}
@ -335,7 +342,7 @@ private func generatePercentageAnimationImages(presentationData: ChatPresentatio
var images: [UIImage] = []
for i in 0 ..< numberOfFrames {
let t = CGFloat(i) / CGFloat(numberOfFrames)
images.append(generatePercentageImage(presentationData: presentationData, incoming: incoming, value: Int((1.0 - t) * CGFloat(fromValue) + t * CGFloat(toValue))))
images.append(generatePercentageImage(presentationData: presentationData, incoming: incoming, value: Int((1.0 - t) * CGFloat(fromValue) + t * CGFloat(toValue)), targetValue: toValue))
}
return images
}
@ -444,7 +451,8 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
var updatedPercentageImage: UIImage?
if currentResult != optionResult {
updatedPercentageImage = generatePercentageImage(presentationData: presentationData, incoming: incoming, value: optionResult?.percent ?? 0)
let value = optionResult?.percent ?? 0
updatedPercentageImage = generatePercentageImage(presentationData: presentationData, incoming: incoming, value: value, targetValue: value)
}
var resultIcon: UIImage?
@ -1238,10 +1246,15 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
private func updateSelection() {
guard let poll = self.poll else {
guard let item = self.item, let poll = self.poll else {
return
}
var disableAllActions = false
if Namespaces.Message.allScheduled.contains(item.message.id.namespace) {
disableAllActions = true
}
var hasSelection = false
switch poll.kind {
case .poll(true):
@ -1275,14 +1288,14 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
if hasSelection && !hasResults && poll.pollId.namespace == Namespaces.Media.CloudPoll {
if !disableAllActions && hasSelection && !hasResults && poll.pollId.namespace == Namespaces.Media.CloudPoll {
self.votersNode.isHidden = true
self.buttonViewResultsTextNode.isHidden = true
self.buttonSubmitInactiveTextNode.isHidden = hasSelectedOptions
self.buttonSubmitActiveTextNode.isHidden = !hasSelectedOptions
self.buttonNode.isHidden = !hasSelectedOptions
} else {
if case .public = poll.publicity, hasResults {
if case .public = poll.publicity, hasResults, !disableAllActions {
self.votersNode.isHidden = true
self.buttonViewResultsTextNode.isHidden = false
self.buttonNode.isHidden = false
@ -1313,7 +1326,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
@ -1335,26 +1348,15 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
} else {
for optionNode in self.optionNodes {
if optionNode.frame.contains(point) {
if optionNode.frame.contains(point), case .tap = gesture {
if optionNode.isUserInteractionEnabled {
return .ignore
} else if let result = optionNode.currentResult, let item = self.item, let poll = self.poll {
let string: String
switch poll.kind {
case .poll:
if result.count == 0 {
string = item.presentationData.strings.MessagePoll_NoVotes
} else {
string = item.presentationData.strings.MessagePoll_VotedCount(result.count)
} else if let result = optionNode.currentResult, let item = self.item, let option = optionNode.option {
if !isEstimating {
item.controllerInteraction.openMessagePollResults(item.message.id, option.opaqueIdentifier)
return .ignore
}
case .quiz:
if result.count == 0 {
string = item.presentationData.strings.MessagePoll_QuizNoUsers
} else {
string = item.presentationData.strings.MessagePoll_QuizCount(result.count)
}
}
return .tooltip(string, optionNode, optionNode.bounds.offsetBy(dx: 0.0, dy: 10.0))
return .openMessage
}
}
}

View File

@ -409,7 +409,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {

View File

@ -100,7 +100,7 @@ final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleContentNod
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.bounds.contains(point) {
if self.buttonNode.frame.contains(point) {
return .ignore

View File

@ -415,10 +415,10 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
return self.contentNode.playMediaWithSound()
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.bounds.contains(point) {
let contentNodeFrame = self.contentNode.frame
let result = self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture)
let result = self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture, isEstimating: isEstimating)
switch result {
case .none:
break

View File

@ -419,6 +419,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, openMessageReactions: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,

View File

@ -168,6 +168,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { _ in
}, disabledPeerSelected: { _ in
}, togglePeerSelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in
if let strongSelf = self {

View File

@ -241,9 +241,6 @@ public class ComposeController: ViewController {
private func activateSearch() {
if self.displayNavigationBar {
if let scrollToTop = self.scrollToTop {
scrollToTop()
}
if let searchContentNode = self.searchContentNode {
self.contactsNode.activateSearch(placeholderNode: searchContentNode.placeholderNode)
}

View File

@ -0,0 +1,218 @@
import Foundation
import UIKit
import Display
private struct Vector2 {
var x: Float
var y: Float
}
private final class NullActionClass: NSObject, CAAction {
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
}
}
private let nullAction = NullActionClass()
private final class ParticleLayer: CALayer {
let mass: Float
var velocity: Vector2
var angularVelocity: Float
var rotationAngle: Float = 0.0
init(image: CGImage, size: CGSize, position: CGPoint, mass: Float, velocity: Vector2, angularVelocity: Float) {
self.mass = mass
self.velocity = velocity
self.angularVelocity = angularVelocity
super.init()
self.contents = image
self.bounds = CGRect(origin: CGPoint(), size: size)
self.position = position
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func action(forKey event: String) -> CAAction? {
return nullAction
}
}
final class ConfettiView: UIView {
private var particles: [ParticleLayer] = []
private var displayLink: ConstantDisplayLinkAnimator?
private var localTime: Float = 0.0
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = false
let colors: [UIColor] = ([
0x56CE6B,
0xCD89D0,
0x1E9AFF,
0xFF8724
] as [UInt32]).map(UIColor.init(rgb:))
let imageSize = CGSize(width: 8.0, height: 8.0)
var images: [(CGImage, CGSize)] = []
for imageType in 0 ..< 2 {
for color in colors {
if imageType == 0 {
images.append((generateFilledCircleImage(diameter: imageSize.width, color: color)!.cgImage!, imageSize))
} else {
let spriteSize = CGSize(width: 2.0, height: 6.0)
images.append((generateImage(spriteSize, opaque: false, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.width)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: size.height - size.width), size: CGSize(width: size.width, height: size.width)))
context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.width / 2.0), size: CGSize(width: size.width, height: size.height - size.width)))
})!.cgImage!, spriteSize))
}
}
}
let imageCount = images.count
let originXRange = 0 ..< Int(frame.width)
let originYRange = Int(-frame.height) ..< Int(0)
let topMassRange: Range<Float> = 20.0 ..< 30.0
let velocityYRange = Float(3.0) ..< Float(5.0)
let angularVelocityRange = Float(1.0) ..< Float(6.0)
let sizeVariation = Float(0.8) ..< Float(1.6)
for i in 0 ..< 70 {
let (image, size) = images[i % imageCount]
let sizeScale = CGFloat(Float.random(in: sizeVariation))
let particle = ParticleLayer(image: image, size: CGSize(width: size.width * sizeScale, height: size.height * sizeScale), position: CGPoint(x: CGFloat(Int.random(in: originXRange)), y: CGFloat(Int.random(in: originYRange))), mass: Float.random(in: topMassRange), velocity: Vector2(x: 0.0, y: Float.random(in: velocityYRange)), angularVelocity: Float.random(in: angularVelocityRange))
self.particles.append(particle)
self.layer.addSublayer(particle)
}
let sideMassRange: Range<Float> = 80.0 ..< 90.0
let sideOriginYBase: Float = Float(frame.size.height * 8.5 / 10.0)
let sideOriginYVariation: Float = Float(frame.size.height / 12.0)
let sideOriginYRange = Float(sideOriginYBase - sideOriginYVariation) ..< Float(sideOriginYBase + sideOriginYVariation)
let sideOriginXRange = Float(0.0) ..< Float(100.0)
let sideOriginVelocityValueRange = Float(1.1) ..< Float(1.6)
let sideOriginVelocityValueScaling: Float = 900.0
let sideOriginVelocityBase: Float = Float.pi / 2.0 + atanf(Float(CGFloat(sideOriginYBase) / (frame.size.width * 0.5)))
let sideOriginVelocityVariation: Float = 0.25
let sideOriginVelocityAngleRange = Float(sideOriginVelocityBase - sideOriginVelocityVariation) ..< Float(sideOriginVelocityBase + sideOriginVelocityVariation)
for sideIndex in 0 ..< 2 {
let sideSign: Float = sideIndex == 0 ? 1.0 : -1.0
let originX: CGFloat = sideIndex == 0 ? -5.0 : (frame.width + 5.0)
for i in 0 ..< 40 {
let offsetX = CGFloat(Float.random(in: sideOriginXRange) * (-sideSign))
let velocityValue = Float.random(in: sideOriginVelocityValueRange) * sideOriginVelocityValueScaling
let velocityAngle = Float.random(in: sideOriginVelocityAngleRange)
let velocityX = sideSign * velocityValue * sinf(velocityAngle)
let velocityY = velocityValue * cosf(velocityAngle)
let (image, size) = images[i % imageCount]
let sizeScale = CGFloat(Float.random(in: sizeVariation))
let particle = ParticleLayer(image: image, size: CGSize(width: size.width * sizeScale, height: size.height * sizeScale), position: CGPoint(x: originX + offsetX, y: CGFloat(Float.random(in: sideOriginYRange))), mass: Float.random(in: sideMassRange), velocity: Vector2(x: velocityX, y: velocityY), angularVelocity: Float.random(in: angularVelocityRange))
self.particles.append(particle)
self.layer.addSublayer(particle)
}
}
self.displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.step()
})
self.displayLink?.isPaused = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func step() {
var haveParticlesAboveGround = false
let minPositionY: CGFloat = 0.0
let maxPositionY = self.bounds.height + 30.0
let minDampingX: CGFloat = 40.0
let maxDampingX: CGFloat = self.bounds.width - 40.0
let centerX: CGFloat = self.bounds.width / 2.0
let currentTime = self.localTime
let dt: Float = 1.0 / 60.0
let slowdownDt: Float
let slowdownStart: Float = 0.2
let slowdownDuration: Float = 1.0
let damping: Float
if currentTime >= slowdownStart && currentTime <= slowdownStart + slowdownDuration {
let slowdownTimestamp: Float = currentTime - slowdownStart
let slowdownRampInDuration: Float = 0.15
let slowdownRampOutDuration: Float = 0.6
let slowdownTransition: Float
if slowdownTimestamp < slowdownRampInDuration {
slowdownTransition = slowdownTimestamp / slowdownRampInDuration
} else if slowdownTimestamp >= slowdownDuration - slowdownRampOutDuration {
let reverseTransition = (slowdownTimestamp - (slowdownDuration - slowdownRampOutDuration)) / slowdownRampOutDuration
slowdownTransition = 1.0 - reverseTransition
} else {
slowdownTransition = 1.0
}
let slowdownFactor: Float = 0.3 * slowdownTransition + 1.0 * (1.0 - slowdownTransition)
slowdownDt = dt * slowdownFactor
let dampingFactor: Float = 0.97 * slowdownTransition + 1.0 * (1.0 - slowdownTransition)
damping = dampingFactor
} else {
slowdownDt = dt
damping = 1.0
}
self.localTime += 1.0 / 60.0
let g: Vector2 = Vector2(x: 0.0, y: 9.8)
CATransaction.begin()
CATransaction.setDisableActions(true)
var turbulenceVariation: [Float] = []
for _ in 0 ..< 20 {
turbulenceVariation.append(Float.random(in: -9.0 ..< 9.0))
}
let turbulenceVariationCount = turbulenceVariation.count
var index = 0
for particle in self.particles {
var position = particle.position
let localDt: Float = slowdownDt
position.x += CGFloat(particle.velocity.x * localDt)
position.y += CGFloat(particle.velocity.y * localDt)
particle.position = position
particle.rotationAngle += particle.angularVelocity * localDt
particle.transform = CATransform3DMakeRotation(CGFloat(particle.rotationAngle), 0.0, 0.0, 1.0)
let acceleration = g
var velocity = particle.velocity
velocity.x += acceleration.x * particle.mass * localDt
velocity.y += acceleration.y * particle.mass * localDt
velocity.x += turbulenceVariation[index % turbulenceVariationCount]
if position.y > minPositionY {
velocity.x *= damping
velocity.y *= damping
}
particle.velocity = velocity
index += 1
if position.y < maxPositionY {
haveParticlesAboveGround = true
}
}
CATransaction.commit()
if !haveParticlesAboveGround {
self.displayLink?.isPaused = true
self.removeFromSuperview()
}
}
}

View File

@ -334,6 +334,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
params.present(controller, nil)
return true
case let .document(file, immediateShare):
params.dismissInput()
let presentationData = params.context.sharedContext.currentPresentationData.with { $0 }
if immediateShare {
let controller = ShareController(context: params.context, subject: .media(.standalone(media: file)), immediateExternalShare: true)

View File

@ -120,6 +120,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, openMessageReactions: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))

View File

@ -425,6 +425,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
}, openMessageReactions: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -22,6 +22,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
public var peerSelected: ((PeerId) -> Void)?
private let filter: ChatListNodePeersFilter
private let attemptSelection: ((Peer) -> Void)?
public var inProgress: Bool = false {
didSet {
if self.inProgress != oldValue {
@ -58,6 +60,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
self.filter = params.filter
self.hasContactSelector = params.hasContactSelector
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.attemptSelection = params.attemptSelection
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
@ -139,6 +142,12 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
}
}
self.peerSelectionNode.requestOpenDisabledPeer = { [weak self] peer in
if let strongSelf = self {
strongSelf.attemptSelection?(peer)
}
}
self.peerSelectionNode.requestOpenPeerFromSearch = { [weak self] peer in
if let strongSelf = self {
let storedPeer = strongSelf.context.account.postbox.transaction { transaction -> Void in

View File

@ -47,6 +47,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
var requestActivateSearch: (() -> Void)?
var requestDeactivateSearch: (() -> Void)?
var requestOpenPeer: ((PeerId) -> Void)?
var requestOpenDisabledPeer: ((Peer) -> Void)?
var requestOpenPeerFromSearch: ((Peer) -> Void)?
var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)?
@ -102,6 +103,10 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self?.requestOpenPeer?(peerId)
}
self.chatListNode.disabledPeerSelected = { [weak self] peer in
self?.requestOpenDisabledPeer?(peer)
}
self.chatListNode.contentOffsetChanged = { [weak self] offset in
self?.contentOffsetChanged?(offset)
}

View File

@ -11,6 +11,7 @@ import ItemListPeerItem
import ItemListPeerActionItem
private let collapsedResultCount: Int = 10
private let collapsedInitialLimit: Int = 14
private final class PollResultsControllerArguments {
let context: AccountContext
@ -46,9 +47,21 @@ private enum PollResultsEntryId: Hashable {
case optionExpand(Int)
}
private enum PollResultsItemTag: ItemListItemTag, Equatable {
case firstOptionPeer(opaqueIdentifier: Data)
func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? PollResultsItemTag, self == other {
return true
} else {
return false
}
}
}
private enum PollResultsEntry: ItemListNodeEntry {
case text(String)
case optionPeer(optionId: Int, index: Int, peer: RenderedPeer, optionText: String, optionPercentage: Int, optionExpanded: Bool, opaqueIdentifier: Data, shimmeringAlternation: Int?)
case optionPeer(optionId: Int, index: Int, peer: RenderedPeer, optionText: String, optionCount: Int32, optionExpanded: Bool, opaqueIdentifier: Data, shimmeringAlternation: Int?, isFirstInOption: Bool)
case optionExpand(optionId: Int, opaqueIdentifier: Data, text: String, enabled: Bool)
var section: ItemListSectionId {
@ -124,15 +137,15 @@ private enum PollResultsEntry: ItemListNodeEntry {
switch self {
case let .text(text):
return ItemListTextItem(presentationData: presentationData, text: .large(text), sectionId: self.section)
case let .optionPeer(optionId, _, peer, optionText, optionPercentage, optionExpanded, opaqueIdentifier, shimmeringAlternation):
let header = ItemListPeerItemHeader(theme: presentationData.theme, strings: presentationData.strings, text: optionText, actionTitle: optionExpanded ? presentationData.strings.PollResults_Collapse : "\(optionPercentage)%", id: Int64(optionId), action: optionExpanded ? {
case let .optionPeer(optionId, _, peer, optionText, optionCount, optionExpanded, opaqueIdentifier, shimmeringAlternation, isFirstInOption):
let header = ItemListPeerItemHeader(theme: presentationData.theme, strings: presentationData.strings, text: optionText, actionTitle: optionExpanded ? presentationData.strings.PollResults_Collapse : presentationData.strings.MessagePoll_VotedCount(optionCount), id: Int64(optionId), action: optionExpanded ? {
arguments.collapseOption(opaqueIdentifier)
} : nil)
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .dayFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: ""), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.peers[peer.peerId]!, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: shimmeringAlternation == nil, sectionId: self.section, action: {
arguments.openPeer(peer)
}, setPeerIdWithRevealedOptions: { _, _ in
}, removePeer: { _ in
}, noInsets: true, header: header, shimmering: shimmeringAlternation.flatMap { ItemListPeerItemShimmering(alternationIndex: $0) })
}, noInsets: true, tag: isFirstInOption ? PollResultsItemTag.firstOptionPeer(opaqueIdentifier: opaqueIdentifier) : nil, header: header, shimmering: shimmeringAlternation.flatMap { ItemListPeerItemShimmering(alternationIndex: $0) })
case let .optionExpand(_, opaqueIdentifier, text, enabled):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: enabled ? {
arguments.expandOption(opaqueIdentifier)
@ -180,13 +193,19 @@ private func pollResultsControllerEntries(presentationData: PresentationData, po
for i in 0 ..< poll.options.count {
let percentage = optionPercentage.count > i ? optionPercentage[i] : 0
let option = poll.options[i]
let optionTextHeader = option.text.uppercased() + "\(percentage)%"
if isEmpty {
if let voterCount = optionVoterCount[i], voterCount != 0 {
let displayCount = min(collapsedResultCount, Int(voterCount))
let displayCount: Int
if Int(voterCount) > collapsedInitialLimit {
displayCount = collapsedResultCount
} else {
displayCount = Int(voterCount)
}
for peerIndex in 0 ..< displayCount {
let fakeUser = TelegramUser(id: PeerId(namespace: -1, id: 0), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
let peer = RenderedPeer(peer: fakeUser)
entries.append(.optionPeer(optionId: i, index: peerIndex, peer: peer, optionText: option.text, optionPercentage: percentage, optionExpanded: false, opaqueIdentifier: option.opaqueIdentifier, shimmeringAlternation: peerIndex % 2))
entries.append(.optionPeer(optionId: i, index: peerIndex, peer: peer, optionText: optionTextHeader, optionCount: voterCount, optionExpanded: false, opaqueIdentifier: option.opaqueIdentifier, shimmeringAlternation: peerIndex % 2, isFirstInOption: peerIndex == 0))
}
if displayCount < Int(voterCount) {
let remainingCount = Int(voterCount) - displayCount
@ -195,24 +214,25 @@ private func pollResultsControllerEntries(presentationData: PresentationData, po
}
} else {
if let optionState = resultsState.options[option.opaqueIdentifier], !optionState.peers.isEmpty {
var peerIndex = 0
var hasMore = false
let optionExpanded = state.expandedOptions.contains(option.opaqueIdentifier)
var peers = optionState.peers
var count = optionState.count
/*#if DEBUG
for _ in 0 ..< 10 {
peers += peers
}
count = max(count, peers.count)
#endif*/
let peers = optionState.peers
let count = optionState.count
let displayCount: Int
if peers.count > collapsedInitialLimit {
displayCount = collapsedResultCount
} else {
displayCount = peers.count
}
var peerIndex = 0
inner: for peer in peers {
if !optionExpanded && peerIndex >= collapsedResultCount {
if !optionExpanded && peerIndex >= displayCount {
break inner
}
entries.append(.optionPeer(optionId: i, index: peerIndex, peer: peer, optionText: option.text, optionPercentage: percentage, optionExpanded: optionExpanded, opaqueIdentifier: option.opaqueIdentifier, shimmeringAlternation: nil))
entries.append(.optionPeer(optionId: i, index: peerIndex, peer: peer, optionText: optionTextHeader, optionCount: Int32(count), optionExpanded: optionExpanded, opaqueIdentifier: option.opaqueIdentifier, shimmeringAlternation: nil, isFirstInOption: peerIndex == 0))
peerIndex += 1
}
@ -227,7 +247,7 @@ private func pollResultsControllerEntries(presentationData: PresentationData, po
return entries
}
public func pollResultsController(context: AccountContext, messageId: MessageId, poll: TelegramMediaPoll) -> ViewController {
public func pollResultsController(context: AccountContext, messageId: MessageId, poll: TelegramMediaPoll, focusOnOptionWithOpaqueIdentifier: Data? = nil) -> ViewController {
let statePromise = ValuePromise(PollResultsControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: PollResultsControllerState())
let updateState: ((PollResultsControllerState) -> PollResultsControllerState) -> Void = { f in
@ -293,8 +313,30 @@ public func pollResultsController(context: AccountContext, messageId: MessageId,
let previousWasEmptyValue = previousWasEmpty.swap(isEmpty)
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.PollResults_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: pollResultsControllerEntries(presentationData: presentationData, poll: poll, state: state, resultsState: resultsState), style: .blocks, focusItemTag: nil, ensureVisibleItemTag: nil, emptyStateItem: nil, crossfadeState: previousWasEmptyValue != nil && previousWasEmptyValue == true && isEmpty == false, animateChanges: false)
var totalVoters: Int32 = 0
if let totalVotersValue = poll.results.totalVoters {
totalVoters = totalVotersValue
}
let entries = pollResultsControllerEntries(presentationData: presentationData, poll: poll, state: state, resultsState: resultsState)
var initialScrollToItem: ListViewScrollToItem?
if let focusOnOptionWithOpaqueIdentifier = focusOnOptionWithOpaqueIdentifier, previousWasEmptyValue == nil {
loop: for i in 0 ..< entries.count {
switch entries[i] {
case let .optionPeer(optionPeer):
if optionPeer.opaqueIdentifier == focusOnOptionWithOpaqueIdentifier {
initialScrollToItem = ListViewScrollToItem(index: i, position: .top(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down)
break loop
}
default:
break
}
}
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .textWithSubtitle(presentationData.strings.PollResults_Title, presentationData.strings.MessagePoll_VotedCount(totalVoters)), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, focusItemTag: nil, emptyStateItem: nil, initialScrollToItem: initialScrollToItem, crossfadeState: previousWasEmptyValue != nil && previousWasEmptyValue == true && isEmpty == false, animateChanges: false)
return (controllerState, (listState, arguments))
}

View File

@ -1129,6 +1129,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, openMessageReactions: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -448,12 +448,12 @@ public final class WalletStrings: Equatable {
public var Wallet_SecureStorageReset_Title: String { return self._s[218]! }
public var Wallet_Receive_CommentHeader: String { return self._s[219]! }
public var Wallet_Info_ReceiveGrams: String { return self._s[220]! }
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
}
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)