mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Poll improvements
This commit is contained in:
parent
d775a5f178
commit
0fa6de6410
@ -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.";
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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: {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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) }
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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: {
|
||||
|
@ -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,
|
||||
|
@ -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)*/
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -419,6 +419,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, openMessageReactions: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
218
submodules/TelegramUI/TelegramUI/ConfettiView.swift
Normal file
218
submodules/TelegramUI/TelegramUI/ConfettiView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -425,6 +425,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
}, openMessageReactions: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
Binary file not shown.
@ -1129,6 +1129,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}, openMessageReactions: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
Binary file not shown.
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user